From 7fcf5cbcc17dd580265b5a85d146db79c9c96780 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Thu, 11 Sep 2014 13:39:48 -0400 Subject: [PATCH 01/21] new stuff --- lib/jira.js | 417 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 290 insertions(+), 127 deletions(-) diff --git a/lib/jira.js b/lib/jira.js index bd71c0a1..0ff1ffb8 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -49,7 +49,7 @@ // * `Jira API Version`: Known to work with `2` and `2.0.alpha1` // * `verbose`: Log some info to the console, usually for debugging // * `strictSSL`: Set to false if you have self-signed certs or something non-trustworthy -// * `oauth`: A dictionary of `consumer_key`, `consumer_secret`, `access_token` and `access_token_secret` to be used for OAuth authentication. +// * `oauth`: A disctionary of `consumer_key`, `consumer_secret`, `access_token` and `access_token_secret` to be used for OAuth authentication. // // ## Implemented APIs ## // @@ -65,6 +65,7 @@ // * Versions // * Pulling versions // * Adding a new version +// * Updating a version // * Pulling unresolved issues count for a specific version // * Rapid Views // * Find based on project name @@ -98,6 +99,7 @@ // ## Changelog ## // // +// * _0.10.0 Add Update Version (thanks to [floralvikings](https://github.com/floralvikings))_ // * _0.9.0 Add OAuth Support and New Estimates on addWorklog (thanks to // [nagyv](https://github.com/nagyv))_ // * _0.8.2 Fix URL Format Issues (thanks to @@ -132,13 +134,14 @@ var url = require('url'), OAuth = require("oauth"); -var JiraApi = exports.JiraApi = function(protocol, host, port, username, password, apiVersion, verbose, strictSSL, oauth) { +var JiraApi = exports.JiraApi = function(protocol, host, port, username, password, apiVersion, agileApiVersion, verbose, strictSSL, oauth) { this.protocol = protocol; this.host = host; this.port = port; this.username = username; this.password = password; this.apiVersion = apiVersion; + this.agileApiVersion = agileApiVersion; // Default strictSSL to true (previous behavior) but now allow it to be // modified if (strictSSL == null) { @@ -203,7 +206,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * issue: an object of the issue // // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290709) - this.findIssue = function(issueNumber, callback) { + this.findIssue = function(issueNumber, params, callback) { var options = { rejectUnauthorized: this.strictSSL, @@ -211,6 +214,10 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor method: 'GET' }; + if (params) { + options.uri += ('?fields=' + params); + } + this.doRequest(options, function(error, response, body) { if (error) { @@ -249,6 +256,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * count: count of unresolved issues for requested version // // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id288524) + this.getUnresolvedIssueCount = function(version, callback) { var options = { rejectUnauthorized: this.strictSSL, @@ -290,6 +298,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * project: the json object representing the entire project // // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289232) + this.getProject = function(project, callback) { var options = { @@ -321,6 +330,49 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; + // ## Find all Rapid Views ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error: string of the error + // * rapidViews: array of views + + this.getAllRapidViews = function(callback) { + + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/rapidview', 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; + + this.doRequest(options, function(error, response) { + + if (error) { + callback(error, null); + return; + } + + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); + return; + } + + if (response.body !== null) { + var rapidViews = response.body.views; + callback(null, rapidViews); + } + + }); + }; + // ## Find the Rapid View for a specified project ## // ### Takes ### // @@ -341,7 +393,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor var options = { rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapidviews/list', 'rest/greenhopper/'), + uri: this.makeUri('/rapidviews/list', 'rest/greenhopper/', this.agileApiVersion), method: 'GET', json: true }; @@ -365,18 +417,76 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor if (response.body !== null) { var rapidViews = response.body.views; + var matchedViews = []; for (var i = 0; i < rapidViews.length; i++) { if(rapidViews[i].name.toLowerCase() === projectName.toLowerCase()) { - callback(null, rapidViews[i]); - return; + matchedViews.push(rapidViews[i]); } } + callback(null, matchedViews[0]); } }); }; - // ## Get a list of Sprints belonging to a Rapid View ## + // ## Get a list of Active Sprints belonging to a Rapid View ## + // ### Takes ### + // + // * rapidViewId: the id for the rapid view + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string with the error + // * sprints: the ?array? of sprints + /** + * Returns a list of active sprints belonging to a Rapid View. + * + * @param rapidView ID + * @param callback + */ + this.getActiveSprintsForRapidView = function(rapidViewId, callback) { + + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json:true + }; + + this.doRequest(options, function(error, response) { + + if (error) { + callback(error, null); + return; + } + + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during sprints search.'); + return; + } + + if (response.body) { + var sprints = response.body.sprints; + var activeSprints = []; + for (var i = 0; i < sprints.length; i++) { + if (sprints[i].state && sprints[i].state === 'ACTIVE') { + activeSprints.push(sprints[i]); + } + } + callback(null, activeSprints); + return; + } + + }); + }; + + // ## Get the last Sprint belonging to a Rapid View ## // ### Takes ### // // * rapidViewId: the id for the rapid view @@ -387,16 +497,16 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * error: string with the error // * sprints: the ?array? of sprints /** - * Returns a list of sprints belonging to a Rapid View. + * Returns last sprint belonging to a Rapid View. * - * @param rapidViewId + * @param rapidView ID * @param callback */ this.getLastSprintForRapidView = function(rapidViewId, callback) { var options = { rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/'), + uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), method: 'GET', json:true }; @@ -441,15 +551,74 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor /** * Returns sprint and issues information * - * @param rapidViewId - * @param sprintId + * @param rapidView ID + * @param sprint ID * @param callback */ - this.getSprintIssues = function getSprintIssues(rapidViewId, sprintId, callback) { + this.getSprintIssues = function (rapidViewId, sprintId, callback) { var options = { rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapid/charts/sprintreport?rapidViewId=' + rapidViewId + '&sprintId=' + sprintId, 'rest/greenhopper/'), + uri: this.makeUri('/rapid/charts/sprintreport?rapidViewId=' + rapidViewId + '&sprintId=' + sprintId, 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; + + this.doRequest(options, function(error, response) { + + if (error) { + callback(error, null); + return; + } + + if( response.statusCode === 404 ) { + callback('Invalid URL'); + return; + } + + if( response.statusCode !== 200 ) { + callback(response.statusCode + ': Unable to connect to JIRA during sprints search'); + return; + } + + if(response.body !== null) { + callback(null, response.body); + } else { + callback('No body'); + } + + }); + + }; + + // ## Get all data for a rapidView + // ### Takes ### + // + // * rapidViewId: the id for the rapid view + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string with the error + // * results: the object with the issues and additional sprint information + /** + * Returns all data + * + * @param rapidView ID + * @param callback + */ + this.getAllRapidViewData = function (rapidViewId, sprintIds, callback) { + + var sprints = ''; + if (sprintIds.length) { + sprintIds.forEach(function (id) { + sprints += ('&activeSprints=' + id); + }); + } + + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/xboard/work/allData/?rapidViewId=' + rapidViewId + sprints, 'rest/greenhopper/', this.agileApiVersion), method: 'GET', json: true }; @@ -505,7 +674,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor var options = { rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/sprint/' + sprintId + '/issues/add', 'rest/greenhopper/'), + uri: this.makeUri('/sprint/' + sprintId + '/issues/add', 'rest/greenhopper/', this.agileApiVersion), method: 'PUT', followAllRedirects: true, json:true, @@ -563,7 +732,8 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor * } * * @param link - * @param callback + * @param errorCallback + * @param successCallback */ this.issueLink = function(link, callback) { @@ -608,6 +778,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * versions: array of the versions for a product // // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289653) + this.getVersions = function(project, callback) { var options = { @@ -780,6 +951,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * issues: array of issues for the user // // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id333082) + // this.searchJira = function(searchString, optional, callback) { // backwards compatibility optional = optional || {}; @@ -896,9 +1068,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id296043) // this.getUsersIssues = function(username, open, callback) { - if (username.indexOf("@") > -1) { - username = username.replace("@", '\\u0040'); - } var jql = "assignee = " + username; var openText = ' AND status in (Open, "In Progress", Reopened)'; if (open) { jql += openText; } @@ -947,18 +1116,17 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; - - // ## Delete issue to Jira ## - // ### Takes ### - // - // * issueId: the Id of the issue to delete - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success object - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290791) +// ## Delete issue to Jira ## +// ### Takes ### +// +// * issueId: the Id of the issue to delete +// * callback: for when it's done +// +// ### Returns ### +// * error string +// * success object +// +// [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290791) this.deleteIssue = function(issueNum, callback) { var options = { rejectUnauthorized: this.strictSSL, @@ -984,7 +1152,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; - // ## Update issue in Jira ## // ### Takes ### // @@ -1252,7 +1419,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor } if (response.statusCode === 200) { - callback(null, body); + callback(null, body.transitions); return; } if (response.statusCode === 404) { @@ -1264,7 +1431,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; - // ## Transition issue in Jira ## // ### Takes ### // @@ -1355,7 +1521,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; - // ## Add a comment to an issue ## // ### Takes ### // * issueId: Issue to add a comment to @@ -1393,12 +1558,9 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor if (response.statusCode === 400) { callback("Invalid Fields: " + JSON.stringify(body)); return; - }; - - callback(response.statusCode + ': Error while adding comment'); + } }); }; - // ## Add a worklog to a project ## // ### Takes ### // * issueId: Issue to add a worklog to @@ -1439,8 +1601,8 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor */ this.addWorklog = function(issueId, worklog, newEstimate, callback) { if(typeof callback == 'undefined') { - callback = newEstimate; - newEstimate = false; + callback = newEstimate + newEstimate = false } var options = { rejectUnauthorized: this.strictSSL, @@ -1475,7 +1637,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; - // ## List all Issue Types ## // ### Takes ### // @@ -1696,6 +1857,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; + // ## Describe the currently authenticated user ## // ### Takes ### // @@ -1720,6 +1882,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor * } * } */ + this.getCurrentUser = function(callback) { var options = { rejectUnauthorized: this.strictSSL, @@ -1728,7 +1891,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor json: true }; - this.doRequest(options, function(error, response, body) { + this.request(options, function(error, response, body) { if (error) { callback(error, null); @@ -1746,94 +1909,94 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }; // ## Retrieve the backlog of a certain Rapid View ## - // ### Takes ### - // * rapidViewId: rapid view id - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * backlog object - /* - * Backlog item is in the format: - * { - * "sprintMarkersMigrated": true, - * "issues": [ - * { - * "id": 67890, - * "key": "KEY-1234", - * "summary": "Issue Summary", - * ... - * } - * ], - * "rankCustomFieldId": 12345, - * "sprints": [ - * { - * "id": 123, - * "name": "Sprint Name", - * "state": "FUTURE", - * ... - * } - * ], - * "supportsPages": true, - * "projects": [ - * { - * "id": 567, - * "key": "KEY", - * "name": "Project Name" - * } - * ], - * "epicData": { - * "epics": [ - * { - * "id": 9876, - * "key": "KEY-4554", - * "typeName": "Epic", - * ... - * } - * ], - * "canEditEpics": true, - * "supportsPages": true - * }, - * "canManageSprints": true, - * "maxIssuesExceeded": false, - * "queryResultLimit": 2147483647, - * "versionData": { - * "versionsPerProject": { - * "567": [ - * { - * "id": 8282, - * "name": "Version Name", - * ... - * } - * ] - * }, - * "canCreateVersion": true - * } - * } - */ - this.getBacklogForRapidView = function(rapidViewId, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/xboard/plan/backlog/data?rapidViewId=' + rapidViewId, 'rest/greenhopper/'), - method: 'GET', - json: true - }; + // ### Takes ### + // * rapidViewId: rapid view id + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * backlog object + /* + * Backlog item is in the format: + * { + * "sprintMarkersMigrated": true, + * "issues": [ + * { + * "id": 67890, + * "key": "KEY-1234", + * "summary": "Issue Summary", + * ... + * } + * ], + * "rankCustomFieldId": 12345, + * "sprints": [ + * { + * "id": 123, + * "name": "Sprint Name", + * "state": "FUTURE", + * ... + * } + * ], + * "supportsPages": true, + * "projects": [ + * { + * "id": 567, + * "key": "KEY", + * "name": "Project Name" + * } + * ], + * "epicData": { + * "epics": [ + * { + * "id": 9876, + * "key": "KEY-4554", + * "typeName": "Epic", + * ... + * } + * ], + * "canEditEpics": true, + * "supportsPages": true + * }, + * "canManageSprints": true, + * "maxIssuesExceeded": false, + * "queryResultLimit": 2147483647, + * "versionData": { + * "versionsPerProject": { + * "567": [ + * { + * "id": 8282, + * "name": "Version Name", + * ... + * } + * ] + * }, + * "canCreateVersion": true + * } + * } + */ + this.getBacklogForRapidView = function(rapidViewId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/xboard/plan/backlog/data?rapidViewId=' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; - this.doRequest(options, function(error, response) { - if (error) { - callback(error, null); + this.doRequest(options, function(error, response) { + if (error) { + callback(error, null); - return; - } + return; + } - if (response.statusCode === 200) { - callback(null, response.body); + if (response.statusCode === 200) { + callback(null, response.body); - return; - } + return; + } - callback(response.statusCode + ': Error while retrieving backlog'); - }); - }; + callback(response.statusCode + ': Error while retrieving backlog'); + }); + }; }).call(JiraApi.prototype); From 094ff9073d8239448d025ff33b8636cc39b72266 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sun, 14 Sep 2014 17:12:33 -0400 Subject: [PATCH 02/21] method to get rapid view configuration --- lib/jira.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lib/jira.js b/lib/jira.js index 0ff1ffb8..43033faf 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -429,6 +429,44 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; + /** + * Gets the Rapid View configuration. + * + * @param rapidViewId + * @param callback + */ + this.getRapidViewConfig = function(rapidViewId, callback) { + + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('rapidviewconfig/editmodel?rapidViewId=' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; + + this.doRequest(options, function(error, response, body) { + + if (error) { + callback(error, null); + return; + } + + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); + return; + } + + body = JSON.parse(body); + callback(null, body); + + }); + }; + // ## Get a list of Active Sprints belonging to a Rapid View ## // ### Takes ### // From 55f64fb07ea1b8d067610674f7345051af9a9ce9 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sun, 14 Sep 2014 17:20:48 -0400 Subject: [PATCH 03/21] fix getRapidViewConfig method --- lib/jira.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/jira.js b/lib/jira.js index 43033faf..86d3d7aa 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -439,7 +439,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor var options = { rejectUnauthorized: this.strictSSL, - uri: this.makeUri('rapidviewconfig/editmodel?rapidViewId=' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), + uri: this.makeUri('/rapidviewconfig/editmodel?rapidViewId=' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), method: 'GET', json: true }; @@ -461,8 +461,9 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor return; } - body = JSON.parse(body); - callback(null, body); + if (response.body) { + callback(null, response.body); + } }); }; From c641bf0c750dabac9f102263490fa2482cb06f99 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sat, 18 Oct 2014 12:59:48 -0400 Subject: [PATCH 04/21] revert to current parent --- lib/jira.js | 458 +++++++++++++++------------------------------------- 1 file changed, 128 insertions(+), 330 deletions(-) diff --git a/lib/jira.js b/lib/jira.js index 466674df..83e4e84d 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -49,7 +49,7 @@ // * `Jira API Version`: Known to work with `2` and `2.0.alpha1` // * `verbose`: Log some info to the console, usually for debugging // * `strictSSL`: Set to false if you have self-signed certs or something non-trustworthy -// * `oauth`: A disctionary of `consumer_key`, `consumer_secret`, `access_token` and `access_token_secret` to be used for OAuth authentication. +// * `oauth`: A dictionary of `consumer_key`, `consumer_secret`, `access_token` and `access_token_secret` to be used for OAuth authentication. // // ## Implemented APIs ## // @@ -65,7 +65,6 @@ // * Versions // * Pulling versions // * Adding a new version -// * Updating a version // * Pulling unresolved issues count for a specific version // * Rapid Views // * Find based on project name @@ -99,7 +98,6 @@ // ## Changelog ## // // -// * _0.10.0 Add Update Version (thanks to [floralvikings](https://github.com/floralvikings))_ // * _0.9.0 Add OAuth Support and New Estimates on addWorklog (thanks to // [nagyv](https://github.com/nagyv))_ // * _0.8.2 Fix URL Format Issues (thanks to @@ -134,14 +132,13 @@ var url = require('url'), OAuth = require("oauth"); -var JiraApi = exports.JiraApi = function(protocol, host, port, username, password, apiVersion, agileApiVersion, verbose, strictSSL, oauth) { +var JiraApi = exports.JiraApi = function(protocol, host, port, username, password, apiVersion, verbose, strictSSL, oauth) { this.protocol = protocol; this.host = host; this.port = port; this.username = username; this.password = password; this.apiVersion = apiVersion; - this.agileApiVersion = agileApiVersion; // Default strictSSL to true (previous behavior) but now allow it to be // modified if (strictSSL == null) { @@ -206,7 +203,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * issue: an object of the issue // // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290709) - this.findIssue = function(issueNumber, params, callback) { + this.findIssue = function(issueNumber, callback) { var options = { rejectUnauthorized: this.strictSSL, @@ -214,10 +211,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor method: 'GET' }; - if (params) { - options.uri += ('?fields=' + params); - } - this.doRequest(options, function(error, response, body) { if (error) { @@ -256,7 +249,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * count: count of unresolved issues for requested version // // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id288524) - this.getUnresolvedIssueCount = function(version, callback) { var options = { rejectUnauthorized: this.strictSSL, @@ -298,7 +290,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * project: the json object representing the entire project // // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289232) - this.getProject = function(project, callback) { var options = { @@ -330,49 +321,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; - // ## Find all Rapid Views ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error: string of the error - // * rapidViews: array of views - - this.getAllRapidViews = function(callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapidview', 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); - return; - } - - if (response.body !== null) { - var rapidViews = response.body.views; - callback(null, rapidViews); - } - - }); - }; - // ## Find the Rapid View for a specified project ## // ### Takes ### // @@ -393,7 +341,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor var options = { rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapidviews/list', 'rest/greenhopper/', this.agileApiVersion), + uri: this.makeUri('/rapidviews/list', 'rest/greenhopper/'), method: 'GET', json: true }; @@ -417,58 +365,18 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor if (response.body !== null) { var rapidViews = response.body.views; - var matchedViews = []; for (var i = 0; i < rapidViews.length; i++) { if(rapidViews[i].name.toLowerCase() === projectName.toLowerCase()) { - matchedViews.push(rapidViews[i]); + callback(null, rapidViews[i]); + return; } } - callback(null, matchedViews[0]); } }); }; - /** - * Gets the Rapid View configuration. - * - * @param rapidViewId - * @param callback - */ - this.getRapidViewConfig = function(rapidViewId, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapidviewconfig/editmodel?rapidViewId=' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); - return; - } - - if (response.body) { - callback(null, response.body); - } - - }); - }; - - // ## Get a list of Active Sprints belonging to a Rapid View ## + // ## Get a list of Sprints belonging to a Rapid View ## // ### Takes ### // // * rapidViewId: the id for the rapid view @@ -479,73 +387,16 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * error: string with the error // * sprints: the ?array? of sprints /** - * Returns a list of active sprints belonging to a Rapid View. + * Returns a list of sprints belonging to a Rapid View. * - * @param rapidView ID - * @param callback - */ - this.getActiveSprintsForRapidView = function(rapidViewId, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json:true - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during sprints search.'); - return; - } - - if (response.body) { - var sprints = response.body.sprints; - var activeSprints = []; - for (var i = 0; i < sprints.length; i++) { - if (sprints[i].state && sprints[i].state === 'ACTIVE') { - activeSprints.push(sprints[i]); - } - } - callback(null, activeSprints); - return; - } - - }); - }; - - // ## Get the last Sprint belonging to a Rapid View ## - // ### Takes ### - // - // * rapidViewId: the id for the rapid view - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string with the error - // * sprints: the ?array? of sprints - /** - * Returns last sprint belonging to a Rapid View. - * - * @param rapidView ID + * @param rapidViewId * @param callback */ this.getLastSprintForRapidView = function(rapidViewId, callback) { var options = { rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), + uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/'), method: 'GET', json:true }; @@ -590,74 +441,15 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor /** * Returns sprint and issues information * - * @param rapidView ID - * @param sprint ID - * @param callback - */ - this.getSprintIssues = function (rapidViewId, sprintId, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapid/charts/sprintreport?rapidViewId=' + rapidViewId + '&sprintId=' + sprintId, 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if( response.statusCode === 404 ) { - callback('Invalid URL'); - return; - } - - if( response.statusCode !== 200 ) { - callback(response.statusCode + ': Unable to connect to JIRA during sprints search'); - return; - } - - if(response.body !== null) { - callback(null, response.body); - } else { - callback('No body'); - } - - }); - - }; - - // ## Get all data for a rapidView - // ### Takes ### - // - // * rapidViewId: the id for the rapid view - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string with the error - // * results: the object with the issues and additional sprint information - /** - * Returns all data - * - * @param rapidView ID + * @param rapidViewId + * @param sprintId * @param callback */ - this.getAllRapidViewData = function (rapidViewId, sprintIds, callback) { - - var sprints = ''; - if (sprintIds.length) { - sprintIds.forEach(function (id) { - sprints += ('&activeSprints=' + id); - }); - } + this.getSprintIssues = function getSprintIssues(rapidViewId, sprintId, callback) { var options = { rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/xboard/work/allData/?rapidViewId=' + rapidViewId + sprints, 'rest/greenhopper/', this.agileApiVersion), + uri: this.makeUri('/rapid/charts/sprintreport?rapidViewId=' + rapidViewId + '&sprintId=' + sprintId, 'rest/greenhopper/'), method: 'GET', json: true }; @@ -713,7 +505,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor var options = { rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/sprint/' + sprintId + '/issues/add', 'rest/greenhopper/', this.agileApiVersion), + uri: this.makeUri('/sprint/' + sprintId + '/issues/add', 'rest/greenhopper/'), method: 'PUT', followAllRedirects: true, json:true, @@ -771,8 +563,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor * } * * @param link - * @param errorCallback - * @param successCallback + * @param callback */ this.issueLink = function(link, callback) { @@ -898,7 +689,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * versions: array of the versions for a product // // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289653) - this.getVersions = function(project, callback) { var options = { @@ -1071,7 +861,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // * issues: array of issues for the user // // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id333082) - // this.searchJira = function(searchString, optional, callback) { // backwards compatibility optional = optional || {}; @@ -1188,6 +977,9 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id296043) // this.getUsersIssues = function(username, open, callback) { + if (username.indexOf("@") > -1) { + username = username.replace("@", '\\u0040'); + } var jql = "assignee = " + username; var openText = ' AND status in (Open, "In Progress", Reopened)'; if (open) { jql += openText; } @@ -1236,17 +1028,18 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; -// ## Delete issue to Jira ## -// ### Takes ### -// -// * issueId: the Id of the issue to delete -// * callback: for when it's done -// -// ### Returns ### -// * error string -// * success object -// -// [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290791) + + // ## Delete issue to Jira ## + // ### Takes ### + // + // * issueId: the Id of the issue to delete + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success object + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290791) this.deleteIssue = function(issueNum, callback) { var options = { rejectUnauthorized: this.strictSSL, @@ -1272,6 +1065,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; + // ## Update issue in Jira ## // ### Takes ### // @@ -1539,7 +1333,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor } if (response.statusCode === 200) { - callback(null, body.transitions); + callback(null, body); return; } if (response.statusCode === 404) { @@ -1551,6 +1345,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; + // ## Transition issue in Jira ## // ### Takes ### // @@ -1641,6 +1436,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; + // ## Add a comment to an issue ## // ### Takes ### // * issueId: Issue to add a comment to @@ -1678,9 +1474,12 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor if (response.statusCode === 400) { callback("Invalid Fields: " + JSON.stringify(body)); return; - } + }; + + callback(response.statusCode + ': Error while adding comment'); }); }; + // ## Add a worklog to a project ## // ### Takes ### // * issueId: Issue to add a worklog to @@ -1721,8 +1520,8 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor */ this.addWorklog = function(issueId, worklog, newEstimate, callback) { if(typeof callback == 'undefined') { - callback = newEstimate - newEstimate = false + callback = newEstimate; + newEstimate = false; } var options = { rejectUnauthorized: this.strictSSL, @@ -1757,6 +1556,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; + // ## List all Issue Types ## // ### Takes ### // @@ -1977,7 +1777,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }); }; - // ## Describe the currently authenticated user ## // ### Takes ### // @@ -2002,7 +1801,6 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor * } * } */ - this.getCurrentUser = function(callback) { var options = { rejectUnauthorized: this.strictSSL, @@ -2011,7 +1809,7 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor json: true }; - this.request(options, function(error, response, body) { + this.doRequest(options, function(error, response, body) { if (error) { callback(error, null); @@ -2029,94 +1827,94 @@ var JiraApi = exports.JiraApi = function(protocol, host, port, username, passwor }; // ## Retrieve the backlog of a certain Rapid View ## - // ### Takes ### - // * rapidViewId: rapid view id - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * backlog object - /* - * Backlog item is in the format: - * { - * "sprintMarkersMigrated": true, - * "issues": [ - * { - * "id": 67890, - * "key": "KEY-1234", - * "summary": "Issue Summary", - * ... - * } - * ], - * "rankCustomFieldId": 12345, - * "sprints": [ - * { - * "id": 123, - * "name": "Sprint Name", - * "state": "FUTURE", - * ... - * } - * ], - * "supportsPages": true, - * "projects": [ - * { - * "id": 567, - * "key": "KEY", - * "name": "Project Name" - * } - * ], - * "epicData": { - * "epics": [ - * { - * "id": 9876, - * "key": "KEY-4554", - * "typeName": "Epic", - * ... - * } - * ], - * "canEditEpics": true, - * "supportsPages": true - * }, - * "canManageSprints": true, - * "maxIssuesExceeded": false, - * "queryResultLimit": 2147483647, - * "versionData": { - * "versionsPerProject": { - * "567": [ - * { - * "id": 8282, - * "name": "Version Name", - * ... - * } - * ] - * }, - * "canCreateVersion": true - * } - * } - */ - this.getBacklogForRapidView = function(rapidViewId, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/xboard/plan/backlog/data?rapidViewId=' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; + // ### Takes ### + // * rapidViewId: rapid view id + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * backlog object + /* + * Backlog item is in the format: + * { + * "sprintMarkersMigrated": true, + * "issues": [ + * { + * "id": 67890, + * "key": "KEY-1234", + * "summary": "Issue Summary", + * ... + * } + * ], + * "rankCustomFieldId": 12345, + * "sprints": [ + * { + * "id": 123, + * "name": "Sprint Name", + * "state": "FUTURE", + * ... + * } + * ], + * "supportsPages": true, + * "projects": [ + * { + * "id": 567, + * "key": "KEY", + * "name": "Project Name" + * } + * ], + * "epicData": { + * "epics": [ + * { + * "id": 9876, + * "key": "KEY-4554", + * "typeName": "Epic", + * ... + * } + * ], + * "canEditEpics": true, + * "supportsPages": true + * }, + * "canManageSprints": true, + * "maxIssuesExceeded": false, + * "queryResultLimit": 2147483647, + * "versionData": { + * "versionsPerProject": { + * "567": [ + * { + * "id": 8282, + * "name": "Version Name", + * ... + * } + * ] + * }, + * "canCreateVersion": true + * } + * } + */ + this.getBacklogForRapidView = function(rapidViewId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/xboard/plan/backlog/data?rapidViewId=' + rapidViewId, 'rest/greenhopper/'), + method: 'GET', + json: true + }; - this.doRequest(options, function(error, response) { - if (error) { - callback(error, null); + this.doRequest(options, function(error, response) { + if (error) { + callback(error, null); - return; - } + return; + } - if (response.statusCode === 200) { - callback(null, response.body); + if (response.statusCode === 200) { + callback(null, response.body); - return; - } + return; + } - callback(response.statusCode + ': Error while retrieving backlog'); - }); - }; + callback(response.statusCode + ': Error while retrieving backlog'); + }); + }; -}).call(JiraApi.prototype); +}).call(JiraApi.prototype); \ No newline at end of file From b8d56aee2877bbbc2c4902b0590b460330446731 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sat, 18 Oct 2014 13:46:28 -0400 Subject: [PATCH 05/21] fixed lint --- Gruntfile.js | 1 + docs/jira.html | 2570 +++++++++++++++++++------------------ lib/jira.js | 3269 ++++++++++++++++++++++-------------------------- 3 files changed, 2785 insertions(+), 3055 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index fc980bca..d0e72fb2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -42,6 +42,7 @@ module.exports = function (grunt) { unused: true, boss: true, browser: true, + plusplus: true, predef: ['module', 'require', 'console', 'exports'] } } diff --git a/docs/jira.html b/docs/jira.html index a4675abe..c73489d5 100644 --- a/docs/jira.html +++ b/docs/jira.html @@ -11,6 +11,27 @@
+ +
    @@ -42,8 +63,8 @@

    Installation

    JiraApi = require('jira').JiraApi;
     
     var jira = new JiraApi('https', config.host, config.port, config.user, config.password, '2.0.alpha1');
    -jira.findIssue(issueNumber, function(error, issue) {
    -    console.log('Status: ' + issue.fields.status.name);
    +jira.findIssue(issueNumber, function(error, issue) {
    +    console.log('Status: ' + issue.fields.status.name);
     });
     

    Currently there is no explicit login call necessary as each API call uses Basic Authentication to authenticate.

    Options

    @@ -57,7 +78,7 @@

    Options

  • Jira API Version<string>: Known to work with 2 and 2.0.alpha1
  • verbose<bool>: Log some info to the console, usually for debugging
  • strictSSL<bool>: Set to false if you have self-signed certs or something non-trustworthy
  • -
  • oauth: A disctionary of consumer_key, consumer_secret, access_token and access_token_secret to be used for OAuth authentication.
  • +
  • oauth: A dictionary of consumer_key, consumer_secret, access_token and access_token_secret to be used for OAuth authentication.

Implemented APIs

    @@ -114,7 +135,7 @@

    Implemented APIs

-

TODO

+

TO-DO

  • Refactor currently implemented APIs to be more Object Oriented
  • Refactor to make use of built-in node.js events and classes
  • @@ -155,17 +176,17 @@

    Changelog

var url = require('url'),
-    logger = console,
-    OAuth = require("oauth");
-
-
-var JiraApi = exports.JiraApi = function(protocol, host, port, username, password, apiVersion, verbose, strictSSL, oauth) {
-    this.protocol = protocol;
-    this.host = host;
-    this.port = port;
-    this.username = username;
-    this.password = password;
-    this.apiVersion = apiVersion;
+ logger = console, + OAuth = require("oauth"), + JiraApi; +JiraApi = exports.JiraApi = function (protocol, host, port, username, password, apiVersion, verbose, strictSSL, oauth) { + 'use strict'; + this.protocol = protocol; + this.host = host; + this.port = port; + this.username = username; + this.password = password; + this.apiVersion = apiVersion; @@ -181,10 +202,10 @@

Changelog

-
    if (strictSSL == null) {
-        strictSSL = true;
-    }
-    this.strictSSL = strictSSL;
+
  if (strictSSL === undefined) {
+    strictSSL = true;
+  }
+  this.strictSSL = strictSSL;
@@ -199,8 +220,12 @@

Changelog

-
    this.request = require('request');
-    if (verbose !== true) { logger = { log: function() {} }; }
+
  this.request = require('request');
+  if (verbose !== true) {
+    logger = {
+      log: function () {return; }
+    };
+  }
@@ -216,46 +241,36 @@

Changelog

-
    this.makeUri = function(pathname, altBase, altApiVersion) {
-        var basePath = 'rest/api/';
-        if (altBase != null) {
-            basePath = altBase;
-        }
-
-        var apiVersion = this.apiVersion;
-        if (altApiVersion != null) {
-          apiVersion = altApiVersion;
-        }
-
-        var uri = url.format({
-            protocol: this.protocol,
-            hostname: this.host,
-            port: this.port,
-            pathname: basePath + apiVersion + pathname
-        });
-        return decodeURIComponent(uri);
-    };
-
-    this.doRequest = function(options, callback) {
-        if(oauth && oauth.consumer_key && oauth.consumer_secret) {
-          options.oauth = {
-            consumer_key: oauth.consumer_key,
-            consumer_secret: oauth.consumer_secret,
-            token: oauth.access_token,
-            token_secret: oauth.access_token_secret
-          };
-        } else {
-          options.auth = {
-            'user': this.username,
-            'pass': this.password
-          };
-        }
-        this.request(options, callback);
-    };
-
+            
  this.makeUri = function (pathname, altBase, altApiVersion) {
+    var basePath = altBase || 'rest/api/',
+      apiVer = altApiVersion || this.apiVersion,
+      uri = url.format({
+        protocol: this.protocol,
+        hostname: this.host,
+        port: this.port,
+        pathname: basePath + apiVer + pathname
+      });
+    return decodeURIComponent(uri);
+  };
+  this.doRequest = function (options, callback) {
+    if (oauth && oauth.consumer_key && oauth.consumer_secret) {
+      options.oauth = {
+        consumer_key: oauth.consumer_key,
+        consumer_secret: oauth.consumer_secret,
+        token: oauth.access_token,
+        token_secret: oauth.access_token_secret
+      };
+    } else {
+      options.auth = {
+        'user': this.username,
+        'pass': this.password
+      };
+    }
+    this.request(options, callback);
+  };
 };
-
-(function() {
+(function () { + 'use strict';
@@ -281,39 +296,32 @@

Returns

-
    this.findIssue = function(issueNumber, callback) {
-
-        var options = {
-            rejectUnauthorized: this.strictSSL,
-            uri: this.makeUri('/issue/' + issueNumber),
-            method: 'GET'
-        };
-
-        this.doRequest(options, function(error, response, body) {
-
-            if (error) {
-                callback(error, null);
-                return;
-            }
-
-            if (response.statusCode === 404) {
-                callback('Invalid issue number.');
-                return;
-            }
-
-            if (response.statusCode !== 200) {
-                callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.');
-                return;
-            }
-
-            if (body === undefined) {
-                callback('Response body was undefined.');
-            }
-
-            callback(null, JSON.parse(body));
-
-        });
-    };
+
  this.findIssue = function (issueNumber, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/issue/' + issueNumber),
+      method: 'GET'
+    };
+    this.doRequest(options, function (error, response, body) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Invalid issue number.');
+        return;
+      }
+      if (response.statusCode !== 200) {
+        callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.');
+        return;
+      }
+      if (body === undefined) {
+        callback('Response body was undefined.');
+        return;
+      }
+      callback(null, JSON.parse(body));
+    });
+  };
@@ -339,36 +347,29 @@

Returns

-
-    this.getUnresolvedIssueCount = function(version, callback) {
-        var options = {
-            rejectUnauthorized: this.strictSSL,
-            uri: this.makeUri('/version/' + version + '/unresolvedIssueCount'),
-            method: 'GET'
-        };
-
-        this.doRequest(options, function(error, response, body) {
-
-            if (error) {
-                callback(error, null);
-                return;
-            }
-
-            if (response.statusCode === 404) {
-                callback('Invalid version.');
-                return;
-            }
-
-            if (response.statusCode !== 200) {
-                callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.');
-                return;
-            }
-
-            body = JSON.parse(body);
-            callback(null, body.issuesUnresolvedCount);
-
-        });
-    };
+
  this.getUnresolvedIssueCount = function (version, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/version/' + version + '/unresolvedIssueCount'),
+      method: 'GET'
+    };
+    this.doRequest(options, function (error, response, body) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Invalid version.');
+        return;
+      }
+      if (response.statusCode !== 200) {
+        callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.');
+        return;
+      }
+      body = JSON.parse(body);
+      callback(null, body.issuesUnresolvedCount);
+    });
+  };
@@ -394,37 +395,29 @@

Returns

-
-    this.getProject = function(project, callback) {
-
-        var options = {
-            rejectUnauthorized: this.strictSSL,
-            uri: this.makeUri('/project/' + project),
-            method: 'GET'
-        };
-
-        this.doRequest(options, function(error, response, body) {
-
-            if (error) {
-                callback(error, null);
-                return;
-            }
-
-            if (response.statusCode === 404) {
-                callback('Invalid project.');
-                return;
-            }
-
-            if (response.statusCode !== 200) {
-                callback(response.statusCode + ': Unable to connect to JIRA during getProject.');
-                return;
-            }
-
-            body = JSON.parse(body);
-            callback(null, body);
-
-        });
-    };
+
  this.getProject = function (project, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/project/' + project),
+      method: 'GET'
+    };
+    this.doRequest(options, function (error, response, body) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Invalid project.');
+        return;
+      }
+      if (response.statusCode !== 200) {
+        callback(response.statusCode + ': Unable to connect to JIRA during getProject.');
+        return;
+      }
+      body = JSON.parse(body);
+      callback(null, body);
+    });
+  };
@@ -449,51 +442,45 @@

Returns

-
-    /**
-     * Finds the Rapid View that belongs to a specified project.
-     *
-     * @param projectName
-     * @param callback
-     */
-    this.findRapidView = function(projectName, callback) {
-
-        var options = {
-          rejectUnauthorized: this.strictSSL,
-          uri: this.makeUri('/rapidviews/list', 'rest/greenhopper/'),
-          method: 'GET',
-          json: true
-        };
-
-        this.doRequest(options, function(error, response) {
-
-          if (error) {
-              callback(error, null);
-              return;
-          }
-
-          if (response.statusCode === 404) {
-            callback('Invalid URL');
-            return;
-          }
-
-          if (response.statusCode !== 200) {
-            callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.');
+            
  /**
+   * Finds the Rapid View that belongs to a specified project.
+   *
+   * @param projectName
+   * @param callback
+   */
+  this.findRapidView = function (projectName, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/rapidviews/list', 'rest/greenhopper/'),
+      method: 'GET',
+      json: true
+    },
+      rapidViews,
+      i;
+    this.doRequest(options, function (error, response) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Invalid URL');
+        return;
+      }
+      if (response.statusCode !== 200) {
+        callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.');
+        return;
+      }
+      if (response.body !== null) {
+        rapidViews = response.body.views;
+        for (i = 0; i < rapidViews.length; i++) {
+          if (rapidViews[i].name.toLowerCase() === projectName.toLowerCase()) {
+            callback(null, rapidViews[i]);
             return;
           }
-
-          if (response.body !== null) {
-            var rapidViews = response.body.views;
-            for (var i = 0; i < rapidViews.length; i++) {
-              if(rapidViews[i].name.toLowerCase() === projectName.toLowerCase()) {
-                callback(null, rapidViews[i]);
-                return;
-              }
-            }
-          }
-
-      });
-    };
+ } + } + }); + };
@@ -518,46 +505,39 @@

Returns

-
    /**
-     * Returns a list of sprints belonging to a Rapid View.
-     *
-     * @param rapidView ID
-     * @param callback
-     */
-    this.getLastSprintForRapidView = function(rapidViewId, callback) {
-
-        var options = {
-          rejectUnauthorized: this.strictSSL,
-          uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/'),
-          method: 'GET',
-          json:true
-        };
-
-        this.doRequest(options, function(error, response) {
-
-          if (error) {
-              callback(error, null);
-              return;
-          }
-
-          if (response.statusCode === 404) {
-            callback('Invalid URL');
-            return;
-          }
-
-          if (response.statusCode !== 200) {
-            callback(response.statusCode + ': Unable to connect to JIRA during sprints search.');
-            return;
-          }
-
-          if (response.body !== null) {
-            var sprints = response.body.sprints;
-            callback(null, sprints.pop());
-            return;
-          }
-
-        });
-    };
+
  /**
+   * Returns a list of sprints belonging to a Rapid View.
+   *
+   * @param rapidViewId
+   * @param callback
+   */
+  this.getLastSprintForRapidView = function (rapidViewId, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/'),
+      method: 'GET',
+      json: true
+    };
+    this.doRequest(options, function (error, response) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Invalid URL');
+        return;
+      }
+      if (response.statusCode !== 200) {
+        callback(response.statusCode + ': Unable to connect to JIRA during sprints search.');
+        return;
+      }
+      if (response.body !== null) {
+        var sprints = response.body.sprints;
+        callback(null, sprints.pop());
+        return;
+      }
+    });
+  };
@@ -583,48 +563,40 @@

Returns

-
    /**
-     * Returns sprint and issues information
-     *
-     * @param rapidView ID
-     * @param sprint ID
-     * @param callback
-     */
-    this.getSprintIssues = function getSprintIssues(rapidViewId, sprintId, callback) {
-
-      var options = {
-        rejectUnauthorized: this.strictSSL,
-        uri: this.makeUri('/rapid/charts/sprintreport?rapidViewId=' + rapidViewId + '&sprintId=' + sprintId, 'rest/greenhopper/'),
-        method: 'GET',
-        json: true
-      };
-
-      this.doRequest(options, function(error, response) {
-
-        if (error) {
-            callback(error, null);
-            return;
-        }
-
-        if( response.statusCode === 404 ) {
-          callback('Invalid URL');
-          return;
-        }
-
-        if( response.statusCode !== 200 ) {
-          callback(response.statusCode + ': Unable to connect to JIRA during sprints search');
-          return;
-        }
-
-        if(response.body !== null) {
-          callback(null, response.body);
-        } else {
-          callback('No body');
-        }
-
-      });
-
-    };
+
  /**
+   * Returns sprint and issues information
+   *
+   * @param rapidViewId
+   * @param sprintId
+   * @param callback
+   */
+  this.getSprintIssues = function getSprintIssues(rapidViewId, sprintId, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/rapid/charts/sprintreport?rapidViewId=' + rapidViewId + '&sprintId=' + sprintId, 'rest/greenhopper/'),
+      method: 'GET',
+      json: true
+    };
+    this.doRequest(options, function (error, response) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Invalid URL');
+        return;
+      }
+      if (response.statusCode !== 200) {
+        callback(response.statusCode + ': Unable to connect to JIRA during sprints search');
+        return;
+      }
+      if (response.body !== null) {
+        callback(null, response.body);
+      } else {
+        callback('No body');
+      }
+    });
+  };
@@ -650,47 +622,40 @@

Returns

-
    /**
-     * Adds a given issue to a project's current sprint
-     *
-     * @param issueId
-     * @param sprintId
-     * @param callback
-     */
-    this.addIssueToSprint = function(issueId, sprintId, callback) {
-
-        var options = {
-          rejectUnauthorized: this.strictSSL,
-          uri: this.makeUri('/sprint/' + sprintId + '/issues/add', 'rest/greenhopper/'),
-          method: 'PUT',
-          followAllRedirects: true,
-          json:true,
-          body: {
-            issueKeys: [issueId]
-          }
-        };
-
-        logger.log(options.uri);
-
-        this.doRequest(options, function(error, response) {
-
-          if (error) {
-              callback(error, null);
-              return;
-          }
-
-          if (response.statusCode === 404) {
-            callback('Invalid URL');
-            return;
-          }
-
-          if (response.statusCode !== 204) {
-            callback(response.statusCode + ': Unable to connect to JIRA to add to sprint.');
-            return;
-          }
-
-        });
-    };
+
  /**
+   * Adds a given issue to a project's current sprint
+   *
+   * @param issueId
+   * @param sprintId
+   * @param callback
+   */
+  this.addIssueToSprint = function (issueId, sprintId, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/sprint/' + sprintId + '/issues/add', 'rest/greenhopper/'),
+      method: 'PUT',
+      followAllRedirects: true,
+      json: true,
+      body: {
+        issueKeys: [issueId]
+      }
+    };
+    logger.log(options.uri);
+    this.doRequest(options, function (error, response) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Invalid URL');
+        return;
+      }
+      if (response.statusCode !== 204) {
+        callback(response.statusCode + ': Unable to connect to JIRA to add to sprint.');
+        return;
+      }
+    });
+  };
@@ -715,58 +680,113 @@

Returns

-
    /**
-     * Creates an issue link between two issues. Link should follow the below format:
-     *
-     * {
-     *   'linkType': 'Duplicate',
-     *   'fromIssueKey': 'HSP-1',
-     *   'toIssueKey': 'MKY-1',
-     *   'comment': {
-     *     'body': 'Linked related issue!',
-     *     'visibility': {
-     *       'type': 'GROUP',
-     *       'value': 'jira-users'
-     *     }
-     *   }
-     * }
-     *
-     * @param link
-     * @param errorCallback
-     * @param successCallback
-     */
-    this.issueLink = function(link, callback) {
-
-        var options = {
-            rejectUnauthorized: this.strictSSL,
-            uri: this.makeUri('/issueLink'),
-            method: 'POST',
-            followAllRedirects: true,
-            json: true,
-            body: link
-        };
-
-        this.doRequest(options, function(error, response) {
-
-            if (error) {
-                callback(error, null);
-                return;
-            }
-
-            if (response.statusCode === 404) {
-                callback('Invalid project.');
-                return;
-            }
-
-            if (response.statusCode !== 200) {
-                callback(response.statusCode + ': Unable to connect to JIRA during issueLink.');
-                return;
-            }
-
-            callback(null);
-
-        });
-    };
+
  /**
+   * Creates an issue link between two issues. Link should follow the below format:
+   *
+   * {
+   *   'linkType': 'Duplicate',
+   *   'fromIssueKey': 'HSP-1',
+   *   'toIssueKey': 'MKY-1',
+   *   'comment': {
+   *     'body': 'Linked related issue!',
+   *     'visibility': {
+   *       'type': 'GROUP',
+   *       'value': 'jira-users'
+   *     }
+   *   }
+   * }
+   *
+   * @param link
+   * @param callback
+   */
+  this.issueLink = function (link, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/issueLink'),
+      method: 'POST',
+      followAllRedirects: true,
+      json: true,
+      body: link
+    };
+    this.doRequest(options, function (error, response) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Invalid project.');
+        return;
+      }
+      if (response.statusCode !== 200) {
+        callback(response.statusCode + ': Unable to connect to JIRA during issueLink.');
+        return;
+      }
+      callback(null);
+    });
+  };
+  /**
+   * Retrieves the remote links associated with the given issue.
+   *
+   * @param issueNumber - The internal id or key of the issue
+   * @param callback
+   */
+  this.getRemoteLinks = function getRemoteLinks(issueNumber, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/issue/' + issueNumber + '/remotelink'),
+      method: 'GET',
+      json: true
+    };
+    this.doRequest(options, function (error, response) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Invalid issue number.');
+        return;
+      }
+      if (response.statusCode !== 200) {
+        callback(response.statusCode + ': Unable to connect to JIRA during request.');
+        return;
+      }
+      callback(null, response.body);
+    });
+  };
+  /**
+   * Retrieves the remote links associated with the given issue.
+   *
+   * @param issueNumber - The internal id (not the issue key) of the issue
+   * @param callback
+   */
+  this.createRemoteLink = function createRemoteLink(issueNumber, remoteLink, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/issue/' + issueNumber + '/remotelink'),
+      method: 'POST',
+      json: true,
+      body: remoteLink
+    };
+    this.doRequest(options, function (error, response) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Cannot create remote link. Invalid issue.');
+        return;
+      }
+      if (response.statusCode === 400) {
+        callback('Cannot create remote link. ' + response.body.errors.title);
+        return;
+      }
+      if (response.statusCode !== 200) {
+        callback(response.statusCode + ': Unable to connect to JIRA during request.');
+        return;
+      }
+      callback(null, response.body);
+    });
+  };
@@ -792,37 +812,29 @@

Returns

-
-    this.getVersions = function(project, callback) {
-
-        var options = {
-            rejectUnauthorized: this.strictSSL,
-            uri: this.makeUri('/project/' + project + '/versions'),
-            method: 'GET'
-        };
-
-        this.doRequest(options, function(error, response, body) {
-
-            if (error) {
-                callback(error, null);
-                return;
-            }
-
-            if (response.statusCode === 404) {
-                callback('Invalid project.');
-                return;
-            }
-
-            if (response.statusCode !== 200) {
-                callback(response.statusCode + ': Unable to connect to JIRA during getVersions.');
-                return;
-            }
-
-            body = JSON.parse(body);
-            callback(null, body);
-
-        });
-    };
+
  this.getVersions = function (project, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/project/' + project + '/versions'),
+      method: 'GET'
+    };
+    this.doRequest(options, function (error, response, body) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Invalid project.');
+        return;
+      }
+      if (response.statusCode !== 200) {
+        callback(response.statusCode + ': Unable to connect to JIRA during getVersions.');
+        return;
+      }
+      body = JSON.parse(body);
+      callback(null, body);
+    });
+  };
@@ -848,52 +860,45 @@

Returns

-
    /* {
-     *    "description": "An excellent version",
-     *    "name": "New Version 1",
-     *    "archived": false,
-     *    "released": true,
-     *    "releaseDate": "2010-07-05",
-     *    "userReleaseDate": "5/Jul/2010",
-     *    "project": "PXA"
-     * }
-     */
-    this.createVersion = function(version, callback) {
-
-        var options = {
-            rejectUnauthorized: this.strictSSL,
-            uri: this.makeUri('/version'),
-            method: 'POST',
-            followAllRedirects: true,
-            json: true,
-            body: version
-        };
-        this.doRequest(options, function(error, response, body) {
-
-            if (error) {
-                callback(error, null);
-                return;
-            }
-
-            if (response.statusCode === 404) {
-                callback('Version does not exist or the currently authenticated user does not have permission to view it');
-                return;
-            }
-
-            if (response.statusCode === 403) {
-                callback('The currently authenticated user does not have permission to edit the version');
-                return;
-            }
-
-            if (response.statusCode !== 201) {
-                callback(response.statusCode + ': Unable to connect to JIRA during createVersion.');
-                return;
-            }
-
-            callback(null, body);
-
-        });
-    };
+
  /* {
+   *    "description": "An excellent version",
+   *    "name": "New Version 1",
+   *    "archived": false,
+   *    "released": true,
+   *    "releaseDate": "2010-07-05",
+   *    "userReleaseDate": "5/Jul/2010",
+   *    "project": "PXA"
+   * }
+   */
+  this.createVersion = function (version, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/version'),
+      method: 'POST',
+      followAllRedirects: true,
+      json: true,
+      body: version
+    };
+    this.doRequest(options, function (error, response, body) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Version does not exist or the currently authenticated user does not have permission to view it');
+        return;
+      }
+      if (response.statusCode === 403) {
+        callback('The currently authenticated user does not have permission to edit the version');
+        return;
+      }
+      if (response.statusCode !== 201) {
+        callback(response.statusCode + ': Unable to connect to JIRA during createVersion.');
+        return;
+      }
+      callback(null, body);
+    });
+  };
@@ -904,6 +909,71 @@

Returns

+

Update a version

+

Takes

+
    +
  • version: an object of the new version
  • +
  • callback: for when it’s done
  • +
+

Returns

+
    +
  • error: error text
  • +
  • version: should be the same version you passed up
  • +
+

Jira Doc

+ + + +
  /* {
+   *    "id": The ID of the version being updated. Required.
+   *    "description": "An excellent version",
+   *    "name": "New Version 1",
+   *    "archived": false,
+   *    "released": true,
+   *    "releaseDate": "2010-07-05",
+   *    "userReleaseDate": "5/Jul/2010",
+   *    "project": "PXA"
+   * }
+   */
+  this.updateVersion = function (version, callback) {
+    var options = {
+      rejectUnauthorized: this.strictSSL,
+      uri: this.makeUri('/version/' + version.id),
+      method: 'PUT',
+      followAllRedirects: true,
+      json: true,
+      body: version
+    };
+    this.doRequest(options, function (error, response, body) {
+      if (error) {
+        callback(error, null);
+        return;
+      }
+      if (response.statusCode === 404) {
+        callback('Version does not exist or the currently authenticated user does not have permission to view it');
+        return;
+      }
+      if (response.statusCode === 403) {
+        callback('The currently authenticated user does not have permission to edit the version');
+        return;
+      }
+      if (response.statusCode !== 200) {
+        callback(response.statusCode + ': Unable to connect to JIRA during updateVersion.');
+        return;
+      }
+      callback(null, body);
+    });
+  };
+ + + + +
  • +
    + +
    + +

    Pass a search query to Jira

    Takes

      @@ -931,70 +1001,65 @@

      Returns

    -
        this.searchJira = function(searchString, optional, callback) {
    +
      this.searchJira = function (searchString, optional, callback) {
  • -
  • +
  • - +

    backwards compatibility

    -
            optional = optional || {};
    -        if (Array.isArray(optional)) {
    -            optional = { fields: optional };
    -        }
    -
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/search'),
    -            method: 'POST',
    -            json: true,
    -            followAllRedirects: true,
    -            body: {
    -                jql: searchString,
    -                startAt: optional.startAt || 0,
    -                maxResults: optional.maxResults || 50,
    -                fields: optional.fields || ["summary", "status", "assignee", "description"]
    -            }
    -        };
    -
    -        this.doRequest(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 400) {
    -                callback('Problem with the JQL query');
    -                return;
    -            }
    -
    -            if (response.statusCode !== 200) {
    -                callback(response.statusCode + ': Unable to connect to JIRA during search.');
    -                return;
    -            }
    -
    -            callback(null, body);
    -
    -        });
    -    };
    +
        optional = optional || {};
    +    if (Array.isArray(optional)) {
    +      optional = {
    +        fields: optional
    +      };
    +    }
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/search'),
    +      method: 'POST',
    +      json: true,
    +      followAllRedirects: true,
    +      body: {
    +        jql: searchString,
    +        startAt: optional.startAt || 0,
    +        maxResults: optional.maxResults || 50,
    +        fields: optional.fields || ["summary", "status", "assignee", "description"]
    +      }
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 400) {
    +        callback('Problem with the JQL query');
    +        return;
    +      }
    +      if (response.statusCode !== 200) {
    +        callback(response.statusCode + ': Unable to connect to JIRA during search.');
    +        return;
    +      }
    +      callback(null, body);
    +    });
    +  };
  • -
  • +
  • - +

    Search user on Jira

    Takes

    @@ -1015,55 +1080,43 @@

    Returns

    -
        this.searchUsers = function(username, startAt, maxResults, includeActive, includeInactive, callback) {
    -        startAt = (startAt !== undefined) ? startAt : 0;
    -        maxResults = (maxResults !== undefined) ? maxResults : 50;
    -        includeActive = (includeActive !== undefined) ? includeActive : true;
    -        includeInactive = (includeInactive !== undefined) ? includeInactive : false;
    -
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri(
    -                '/user/search?username=' + username +
    -                '&startAt=' + startAt +
    -                '&maxResults=' + maxResults +
    -                '&includeActive=' + includeActive +
    -                '&includeInactive=' + includeInactive),
    -            method: 'GET',
    -            json: true,
    -            followAllRedirects: true
    -        };
    -
    -        this.doRequest(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 400) {
    -                callback('Unable to search');
    -                return;
    -            }
    -
    -            if (response.statusCode !== 200) {
    -                callback(response.statusCode + ': Unable to connect to JIRA during search.');
    -                return;
    -            }
    -
    -            callback(null, body);
    -
    -        });
    -    };
    +
      this.searchUsers = function (username, startAt, maxResults, includeActive, includeInactive, callback) {
    +    startAt = (startAt !== undefined) ? startAt : 0;
    +    maxResults = (maxResults !== undefined) ? maxResults : 50;
    +    includeActive = (includeActive !== undefined) ? includeActive : true;
    +    includeInactive = (includeInactive !== undefined) ? includeInactive : false;
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/user/search?username=' + username + '&startAt=' + startAt + '&maxResults=' + maxResults + '&includeActive=' + includeActive + '&includeInactive=' + includeInactive),
    +      method: 'GET',
    +      json: true,
    +      followAllRedirects: true
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 400) {
    +        callback('Unable to search');
    +        return;
    +      }
    +      if (response.statusCode !== 200) {
    +        callback(response.statusCode + ': Unable to connect to JIRA during search.');
    +        return;
    +      }
    +      callback(null, body);
    +    });
    +  };
  • -
  • +
  • - +

    Takes

    @@ -1081,21 +1134,26 @@

    Returns

    -
        this.getUsersIssues = function(username, open, callback) {
    -        var jql = "assignee = " + username;
    -        var openText = ' AND status in (Open, "In Progress", Reopened)';
    -        if (open) { jql += openText; }
    -        this.searchJira(jql, {}, callback);
    -    };
    +
      this.getUsersIssues = function (username, open, callback) {
    +    if (username.indexOf("@") > -1) {
    +      username = username.replace("@", '\\u0040');
    +    }
    +    var jql = "assignee = " + username,
    +      openText = ' AND status in (Open, "In Progress", Reopened)';
    +    if (open) {
    +      jql += openText;
    +    }
    +    this.searchJira(jql, {}, callback);
    +  };
  • -
  • +
  • - +

    Add issue to Jira

    Takes

    @@ -1112,46 +1170,40 @@

    Returns

    -
        this.addNewIssue = function(issue, callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/issue'),
    -            method: 'POST',
    -            followAllRedirects: true,
    -            json: true,
    -            body: issue
    -        };
    -
    -        this.doRequest(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 400) {
    -                callback(body);
    -                return;
    -            }
    -
    -            if ((response.statusCode !== 200) && (response.statusCode !== 201)) {
    -                callback(response.statusCode + ': Unable to connect to JIRA during search.');
    -                return;
    -            }
    -
    -            callback(null, body);
    -
    -        });
    -    };
    +
      this.addNewIssue = function (issue, callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/issue'),
    +      method: 'POST',
    +      followAllRedirects: true,
    +      json: true,
    +      body: issue
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 400) {
    +        callback(body);
    +        return;
    +      }
    +      if ((response.statusCode !== 200) && (response.statusCode !== 201)) {
    +        callback(response.statusCode + ': Unable to connect to JIRA during search.');
    +        return;
    +      }
    +      callback(null, body);
    +    });
    +  };
  • -
  • +
  • - +

    Delete issue to Jira

    Takes

    @@ -1168,40 +1220,35 @@

    Returns

    -
        this.deleteIssue = function(issueNum, callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/issue/' + issueNum),
    -            method: 'DELETE',
    -            followAllRedirects: true,
    -            json: true
    -        };
    -
    -        this.doRequest(options, function(error, response) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 204) {
    -                callback(null, "Success");
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while deleting');
    -
    -        });
    -    };
    +
      this.deleteIssue = function (issueNum, callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/issue/' + issueNum),
    +      method: 'DELETE',
    +      followAllRedirects: true,
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 204) {
    +        callback(null, "Success");
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while deleting');
    +    });
    +  };
  • -
  • +
  • - +

    Update issue in Jira

    Takes

    @@ -1219,41 +1266,36 @@

    Returns

    -
        this.updateIssue = function(issueNum, issueUpdate, callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/issue/' + issueNum),
    -            body: issueUpdate,
    -            method: 'PUT',
    -            followAllRedirects: true,
    -            json: true
    -        };
    -
    -        this.doRequest(options, function(error, response) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 200 || response.statusCode === 204) {
    -                callback(null, "Success");
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while updating');
    -
    -        });
    -    };
    +
      this.updateIssue = function (issueNum, issueUpdate, callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/issue/' + issueNum),
    +      body: issueUpdate,
    +      method: 'PUT',
    +      followAllRedirects: true,
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 200 || response.statusCode === 204) {
    +        callback(null, "Success");
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while updating');
    +    });
    +  };
  • -
  • +
  • - +

    List Components

    Takes

    @@ -1270,66 +1312,61 @@

    Returns

    -
        /*
    -     * [{
    -     *     "self": "http://localhostname:8090/jira/rest/api/2.0/component/1234",
    -     *     "id": "1234",
    -     *     "name": "name",
    -     *     "description": "Description.",
    -     *     "assigneeType": "PROJECT_DEFAULT",
    -     *     "assignee": {
    -     *         "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com",
    -     *         "name": "user@domain.com",
    -     *         "displayName": "SE Support",
    -     *         "active": true
    -     *     },
    -     *     "realAssigneeType": "PROJECT_DEFAULT",
    -     *     "realAssignee": {
    -     *         "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com",
    -     *         "name": "user@domain.com",
    -     *         "displayName": "User name",
    -     *         "active": true
    -     *     },
    -     *     "isAssigneeTypeValid": true
    -     * }]
    -     */
    -    this.listComponents = function(project, callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/project/' + project + '/components'),
    -            method: 'GET',
    -            json: true
    -        };
    -
    -        this.doRequest(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 200) {
    -                callback(null, body);
    -                return;
    -            }
    -            if (response.statusCode === 404) {
    -                callback("Project not found");
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while updating');
    -
    -        });
    -    };
    +
      /*
    +   * [{
    +   *     "self": "http://localhostname:8090/jira/rest/api/2.0/component/1234",
    +   *     "id": "1234",
    +   *     "name": "name",
    +   *     "description": "Description.",
    +   *     "assigneeType": "PROJECT_DEFAULT",
    +   *     "assignee": {
    +   *         "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com",
    +   *         "name": "user@domain.com",
    +   *         "displayName": "SE Support",
    +   *         "active": true
    +   *     },
    +   *     "realAssigneeType": "PROJECT_DEFAULT",
    +   *     "realAssignee": {
    +   *         "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com",
    +   *         "name": "user@domain.com",
    +   *         "displayName": "User name",
    +   *         "active": true
    +   *     },
    +   *     "isAssigneeTypeValid": true
    +   * }]
    +   */
    +  this.listComponents = function (project, callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/project/' + project + '/components'),
    +      method: 'GET',
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 200) {
    +        callback(null, body);
    +        return;
    +      }
    +      if (response.statusCode === 404) {
    +        callback("Project not found");
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while updating');
    +    });
    +  };
  • -
  • +
  • - +

    List listFields

    Takes

    @@ -1345,57 +1382,52 @@

    Returns

    -
        /*
    -     * [{
    -     *    "id": "field",
    -     *    "name": "Field",
    -     *    "custom": false,
    -     *    "orderable": true,
    -     *    "navigable": true,
    -     *    "searchable": true,
    -     *    "schema": {
    -     *        "type": "string",
    -     *        "system": "field"
    -     *    }
    -     * }]
    -     */
    -    this.listFields = function(callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/field'),
    -            method: 'GET',
    -            json: true
    -        };
    -
    -        this.doRequest(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 200) {
    -                callback(null, body);
    -                return;
    -            }
    -            if (response.statusCode === 404) {
    -                callback("Not found");
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while updating');
    -
    -        });
    -    };
    +
      /*
    +   * [{
    +   *    "id": "field",
    +   *    "name": "Field",
    +   *    "custom": false,
    +   *    "orderable": true,
    +   *    "navigable": true,
    +   *    "searchable": true,
    +   *    "schema": {
    +   *        "type": "string",
    +   *        "system": "field"
    +   *    }
    +   * }]
    +   */
    +  this.listFields = function (callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/field'),
    +      method: 'GET',
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 200) {
    +        callback(null, body);
    +        return;
    +      }
    +      if (response.statusCode === 404) {
    +        callback("Not found");
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while updating');
    +    });
    +  };
  • -
  • +
  • - +

    List listPriorities

    Takes

    @@ -1411,52 +1443,47 @@

    Returns

    -
        /*
    -     * [{
    -     *    "self": "http://localhostname:8090/jira/rest/api/2.0/priority/1",
    -     *    "statusColor": "#ff3300",
    -     *    "description": "Crashes, loss of data, severe memory leak.",
    -     *    "name": "Major",
    -     *    "id": "2"
    -     * }]
    -     */
    -    this.listPriorities = function(callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/priority'),
    -            method: 'GET',
    -            json: true
    -        };
    -
    -        this.doRequest(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 200) {
    -                callback(null, body);
    -                return;
    -            }
    -            if (response.statusCode === 404) {
    -                callback("Not found");
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while updating');
    -
    -        });
    -    };
    +
      /*
    +   * [{
    +   *    "self": "http://localhostname:8090/jira/rest/api/2.0/priority/1",
    +   *    "statusColor": "#ff3300",
    +   *    "description": "Crashes, loss of data, severe memory leak.",
    +   *    "name": "Major",
    +   *    "id": "2"
    +   * }]
    +   */
    +  this.listPriorities = function (callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/priority'),
    +      method: 'GET',
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 200) {
    +        callback(null, body);
    +        return;
    +      }
    +      if (response.statusCode === 404) {
    +        callback("Not found");
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while updating');
    +    });
    +  };
  • -
  • +
  • - +

    List Transitions

    Takes

    @@ -1473,80 +1500,75 @@

    Returns

    -
        /*
    -     *  {
    -     *  "expand": "transitions",
    -     *  "transitions": [
    -     *      {
    -     *          "id": "2",
    -     *          "name": "Close Issue",
    -     *          "to": {
    -     *              "self": "http://localhostname:8090/jira/rest/api/2.0/status/10000",
    -     *              "description": "The issue is currently being worked on.",
    -     *              "iconUrl": "http://localhostname:8090/jira/images/icons/progress.gif",
    -     *              "name": "In Progress",
    -     *              "id": "10000"
    -     *          },
    -     *          "fields": {
    -     *              "summary": {
    -     *                  "required": false,
    -     *                  "schema": {
    -     *                      "type": "array",
    -     *                      "items": "option",
    -     *                      "custom": "com.atlassian.jira.plugin.system.customfieldtypes:multiselect",
    -     *                      "customId": 10001
    -     *                  },
    -     *                  "name": "My Multi Select",
    -     *                  "operations": [
    -     *                      "set",
    -     *                      "add"
    -     *                  ],
    -     *                  "allowedValues": [
    -     *                      "red",
    -     *                      "blue"
    -     *                  ]
    -     *              }
    -     *          }
    -     *      }
    -     *  ]}
    -     */
    -    this.listTransitions = function(issueId, callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/issue/' + issueId + '/transitions?expand=transitions.fields'),
    -            method: 'GET',
    -            json: true
    -        };
    -
    -        this.doRequest(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 200) {
    -                callback(null, body.transitions);
    -                return;
    -            }
    -            if (response.statusCode === 404) {
    -                callback("Issue not found");
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while updating');
    -
    -        });
    -    };
    +
      /*
    +   *  {
    +   *  "expand": "transitions",
    +   *  "transitions": [
    +   *      {
    +   *          "id": "2",
    +   *          "name": "Close Issue",
    +   *          "to": {
    +   *              "self": "http://localhostname:8090/jira/rest/api/2.0/status/10000",
    +   *              "description": "The issue is currently being worked on.",
    +   *              "iconUrl": "http://localhostname:8090/jira/images/icons/progress.gif",
    +   *              "name": "In Progress",
    +   *              "id": "10000"
    +   *          },
    +   *          "fields": {
    +   *              "summary": {
    +   *                  "required": false,
    +   *                  "schema": {
    +   *                      "type": "array",
    +   *                      "items": "option",
    +   *                      "custom": "com.atlassian.jira.plugin.system.customfieldtypes:multiselect",
    +   *                      "customId": 10001
    +   *                  },
    +   *                  "name": "My Multi Select",
    +   *                  "operations": [
    +   *                      "set",
    +   *                      "add"
    +   *                  ],
    +   *                  "allowedValues": [
    +   *                      "red",
    +   *                      "blue"
    +   *                  ]
    +   *              }
    +   *          }
    +   *      }
    +   *  ]}
    +   */
    +  this.listTransitions = function (issueId, callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/issue/' + issueId + '/transitions?expand=transitions.fields'),
    +      method: 'GET',
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 200) {
    +        callback(null, body);
    +        return;
    +      }
    +      if (response.statusCode === 404) {
    +        callback("Issue not found");
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while updating');
    +    });
    +  };
  • -
  • +
  • - +

    Transition issue in Jira

    Takes

    @@ -1564,41 +1586,36 @@

    Returns

    -
        this.transitionIssue = function(issueNum, issueTransition, callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/issue/' + issueNum + '/transitions'),
    -            body: issueTransition,
    -            method: 'POST',
    -            followAllRedirects: true,
    -            json: true
    -        };
    -
    -        this.doRequest(options, function(error, response) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 204) {
    -                callback(null, "Success");
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while updating');
    -
    -        });
    -    };
    +
      this.transitionIssue = function (issueNum, issueTransition, callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/issue/' + issueNum + '/transitions'),
    +      body: issueTransition,
    +      method: 'POST',
    +      followAllRedirects: true,
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 204) {
    +        callback(null, "Success");
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while updating');
    +    });
    +  };
  • -
  • +
  • - +

    List all Viewable Projects

    Takes

    @@ -1614,56 +1631,51 @@

    Returns

    -
        /*
    -     * Result items are in the format:
    -     * {
    -     *      "self": "http://www.example.com/jira/rest/api/2/project/ABC",
    -     *      "id": "10001",
    -     *      "key": "ABC",
    -     *      "name": "Alphabetical",
    -     *      "avatarUrls": {
    -     *          "16x16": "http://www.example.com/jira/secure/projectavatar?size=small&pid=10001",
    -     *          "48x48": "http://www.example.com/jira/secure/projectavatar?size=large&pid=10001"
    -     *      }
    -     * }
    -     */
    -    this.listProjects = function(callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/project'),
    -            method: 'GET',
    -            json: true
    -        };
    -
    -        this.doRequest(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 200) {
    -                callback(null, body);
    -                return;
    -            }
    -            if (response.statusCode === 500) {
    -                callback(response.statusCode + ': Error while retrieving list.');
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while updating');
    -
    -        });
    -    };
    +
      /*
    +   * Result items are in the format:
    +   * {
    +   *      "self": "http://www.example.com/jira/rest/api/2/project/ABC",
    +   *      "id": "10001",
    +   *      "key": "ABC",
    +   *      "name": "Alphabetical",
    +   *      "avatarUrls": {
    +   *          "16x16": "http://www.example.com/jira/secure/projectavatar?size=small&pid=10001",
    +   *          "48x48": "http://www.example.com/jira/secure/projectavatar?size=large&pid=10001"
    +   *      }
    +   * }
    +   */
    +  this.listProjects = function (callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/project'),
    +      method: 'GET',
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 200) {
    +        callback(null, body);
    +        return;
    +      }
    +      if (response.statusCode === 500) {
    +        callback(response.statusCode + ': Error while retrieving list.');
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while updating');
    +    });
    +  };
  • -
  • +
  • - +

    Add a comment to an issue

    Takes

    @@ -1681,44 +1693,42 @@

    Returns

    -
        this.addComment = function(issueId, comment, callback){
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/issue/' + issueId + '/comment'),
    -            body: {
    -              "body": comment
    -            },
    -            method: 'POST',
    -            followAllRedirects: true,
    -            json: true
    -        };
    -
    -        this.doRequest(options, function(error, response, body) {
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 201) {
    -                callback(null, "Success");
    -                return;
    -            }
    -
    -            if (response.statusCode === 400) {
    -                callback("Invalid Fields: " + JSON.stringify(body));
    -                return;
    -            }
    -        });
    -    };
    +
      this.addComment = function (issueId, comment, callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/issue/' + issueId + '/comment'),
    +      body: {
    +        "body": comment
    +      },
    +      method: 'POST',
    +      followAllRedirects: true,
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 201) {
    +        callback(null, "Success");
    +        return;
    +      }
    +      if (response.statusCode === 400) {
    +        callback("Invalid Fields: " + JSON.stringify(body));
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while adding comment');
    +    });
    +  };
  • -
  • +
  • - +

    Add a worklog to a project

    Takes

    @@ -1736,80 +1746,75 @@

    Returns

    -
        /*
    -     * Worklog item is in the format:
    -     *  {
    -     *      "self": "http://www.example.com/jira/rest/api/2.0/issue/10010/worklog/10000",
    -     *      "author": {
    -     *          "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred",
    -     *          "name": "fred",
    -     *          "displayName": "Fred F. User",
    -     *          "active": false
    -     *      },
    -     *      "updateAuthor": {
    -     *          "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred",
    -     *          "name": "fred",
    -     *          "displayName": "Fred F. User",
    -     *          "active": false
    -     *      },
    -     *      "comment": "I did some work here.",
    -     *      "visibility": {
    -     *          "type": "group",
    -     *          "value": "jira-developers"
    -     *      },
    -     *      "started": "2012-11-22T04:19:46.736-0600",
    -     *      "timeSpent": "3h 20m",
    -     *      "timeSpentSeconds": 12000,
    -     *      "id": "100028"
    -     *  }
    -     */
    -    this.addWorklog = function(issueId, worklog, newEstimate, callback) {
    -        if(typeof callback == 'undefined') {
    -          callback = newEstimate
    -          newEstimate = false
    -        }
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/issue/' + issueId + '/worklog' + (newEstimate ? "?adjustEstimate=new&newEstimate=" + newEstimate : "")),
    -            body: worklog,
    -            method: 'POST',
    -            followAllRedirects: true,
    -            json: true
    -        };
    -
    -        this.doRequest(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 201) {
    -                callback(null, "Success");
    -                return;
    -            }
    -            if (response.statusCode === 400) {
    -                callback("Invalid Fields: " + JSON.stringify(body));
    -                return;
    -            }
    -            if (response.statusCode === 403) {
    -                callback("Insufficient Permissions");
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while updating');
    -
    -        });
    -    };
    +
      /*
    +   * Worklog item is in the format:
    +   *  {
    +   *      "self": "http://www.example.com/jira/rest/api/2.0/issue/10010/worklog/10000",
    +   *      "author": {
    +   *          "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred",
    +   *          "name": "fred",
    +   *          "displayName": "Fred F. User",
    +   *          "active": false
    +   *      },
    +   *      "updateAuthor": {
    +   *          "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred",
    +   *          "name": "fred",
    +   *          "displayName": "Fred F. User",
    +   *          "active": false
    +   *      },
    +   *      "comment": "I did some work here.",
    +   *      "visibility": {
    +   *          "type": "group",
    +   *          "value": "jira-developers"
    +   *      },
    +   *      "started": "2012-11-22T04:19:46.736-0600",
    +   *      "timeSpent": "3h 20m",
    +   *      "timeSpentSeconds": 12000,
    +   *      "id": "100028"
    +   *  }
    +   */
    +  this.addWorklog = function (issueId, worklog, newEstimate, callback) {
    +    if (callback === undefined) {
    +      callback = newEstimate;
    +      newEstimate = false;
    +    }
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/issue/' + issueId + '/worklog' + (newEstimate ? "?adjustEstimate=new&newEstimate=" + newEstimate : "")),
    +      body: worklog,
    +      method: 'POST',
    +      followAllRedirects: true,
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 201) {
    +        callback(null, "Success");
    +        return;
    +      }
    +      if (response.statusCode === 400) {
    +        callback("Invalid Fields: " + JSON.stringify(body));
    +        return;
    +      }
    +      if (response.statusCode === 403) {
    +        callback("Insufficient Permissions");
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while updating');
    +    });
    +  };
  • -
  • +
  • - +

    List all Issue Types

    Takes

    @@ -1825,50 +1830,45 @@

    Returns

    -
        /*
    -     * Result items are in the format:
    -     * {
    -     *  "self": "http://localhostname:8090/jira/rest/api/2.0/issueType/3",
    -     *  "id": "3",
    -     *  "description": "A task that needs to be done.",
    -     *  "iconUrl": "http://localhostname:8090/jira/images/icons/task.gif",
    -     *  "name": "Task",
    -     *  "subtask": false
    -     * }
    -     */
    -    this.listIssueTypes = function(callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/issuetype'),
    -            method: 'GET',
    -            json: true
    -        };
    -
    -        this.doRequest(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 200) {
    -                callback(null, body);
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while retrieving issue types');
    -
    -        });
    -    };
    +
      /*
    +   * Result items are in the format:
    +   * {
    +   *  "self": "http://localhostname:8090/jira/rest/api/2.0/issueType/3",
    +   *  "id": "3",
    +   *  "description": "A task that needs to be done.",
    +   *  "iconUrl": "http://localhostname:8090/jira/images/icons/task.gif",
    +   *  "name": "Task",
    +   *  "subtask": false
    +   * }
    +   */
    +  this.listIssueTypes = function (callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/issuetype'),
    +      method: 'GET',
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 200) {
    +        callback(null, body);
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while retrieving issue types');
    +    });
    +  };
  • -
  • +
  • - +

    Register a webhook

    Takes

    @@ -1885,55 +1885,50 @@

    Returns

    -
        /*
    -     * Success object in the format:
    -     * {
    -     *   name: 'my first webhook via rest',
    -     *   events: [],
    -     *   url: 'http://www.example.com/webhooks',
    -     *   filter: '',
    -     *   excludeIssueDetails: false,
    -     *   enabled: true,
    -     *   self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5',
    -     *   lastUpdatedUser: 'user',
    -     *   lastUpdatedDisplayName: 'User Name',
    -     *   lastUpdated: 1383247225784
    -     * }
    -     */
    -    this.registerWebhook = function(webhook, callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'),
    -            method: 'POST',
    -            json: true,
    -            body: webhook
    -        };
    -
    -        this.request(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 201) {
    -                callback(null, body);
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while registering new webhook');
    -
    -        });
    -    };
    +
      /*
    +   * Success object in the format:
    +   * {
    +   *   name: 'my first webhook via rest',
    +   *   events: [],
    +   *   url: 'http://www.example.com/webhooks',
    +   *   filter: '',
    +   *   excludeIssueDetails: false,
    +   *   enabled: true,
    +   *   self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5',
    +   *   lastUpdatedUser: 'user',
    +   *   lastUpdatedDisplayName: 'User Name',
    +   *   lastUpdated: 1383247225784
    +   * }
    +   */
    +  this.registerWebhook = function (webhook, callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'),
    +      method: 'POST',
    +      json: true,
    +      body: webhook
    +    };
    +    this.request(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 201) {
    +        callback(null, body);
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while registering new webhook');
    +    });
    +  };
  • -
  • +
  • - +

    List all registered webhooks

    Takes

    @@ -1949,54 +1944,49 @@

    Returns

    -
        /*
    -     * Webhook object in the format:
    -     * {
    -     *   name: 'my first webhook via rest',
    -     *   events: [],
    -     *   url: 'http://www.example.com/webhooks',
    -     *   filter: '',
    -     *   excludeIssueDetails: false,
    -     *   enabled: true,
    -     *   self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5',
    -     *   lastUpdatedUser: 'user',
    -     *   lastUpdatedDisplayName: 'User Name',
    -     *   lastUpdated: 1383247225784
    -     * }
    -     */
    -    this.listWebhooks = function(callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'),
    -            method: 'GET',
    -            json: true
    -        };
    -
    -        this.request(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 200) {
    -                callback(null, body);
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while listing webhooks');
    -
    -        });
    -    };
    +
      /*
    +   * Webhook object in the format:
    +   * {
    +   *   name: 'my first webhook via rest',
    +   *   events: [],
    +   *   url: 'http://www.example.com/webhooks',
    +   *   filter: '',
    +   *   excludeIssueDetails: false,
    +   *   enabled: true,
    +   *   self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5',
    +   *   lastUpdatedUser: 'user',
    +   *   lastUpdatedDisplayName: 'User Name',
    +   *   lastUpdated: 1383247225784
    +   * }
    +   */
    +  this.listWebhooks = function (callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'),
    +      method: 'GET',
    +      json: true
    +    };
    +    this.request(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 200) {
    +        callback(null, body);
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while listing webhooks');
    +    });
    +  };
  • -
  • +
  • - +

    Get a webhook by its ID

    Takes

    @@ -2013,39 +2003,34 @@

    Returns

    -
        this.getWebhook = function(webhookID, callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'),
    -            method: 'GET',
    -            json: true
    -        };
    -
    -        this.request(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 200) {
    -                callback(null, body);
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while getting webhook');
    -
    -        });
    -    };
    +
      this.getWebhook = function (webhookID, callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'),
    +      method: 'GET',
    +      json: true
    +    };
    +    this.request(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 200) {
    +        callback(null, body);
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while getting webhook');
    +    });
    +  };
  • -
  • +
  • - +

    Delete a registered webhook

    Takes

    @@ -2062,39 +2047,34 @@

    Returns

    -
        this.deleteWebhook = function(webhookID, callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'),
    -            method: 'DELETE',
    -            json: true
    -        };
    -
    -        this.request(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 204) {
    -                callback(null, "Success");
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while deleting webhook');
    -
    -        });
    -    };
    +
      this.deleteWebhook = function (webhookID, callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'),
    +      method: 'DELETE',
    +      json: true
    +    };
    +    this.request(options, function (error, response) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 204) {
    +        callback(null, "Success");
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while deleting webhook');
    +    });
    +  };
  • -
  • +
  • - +

    Describe the currently authenticated user

    Takes

    @@ -2110,54 +2090,48 @@

    Returns

    -
        /*
    -     * User object in the format:
    -     * {
    -     *   self: 'http://localhost:8090/rest/api/latest/user?username=user',
    -     *   name: 'user',
    -     *   loginInfo:
    -     *   {
    -     *     failedLoginCount: 2,
    -     *     loginCount: 114,
    -     *     lastFailedLoginTime: '2013-10-29T13:33:26.702+0000',
    -     *     previousLoginTime: '2013-10-31T20:30:51.924+0000'
    -     *   }
    -     * }
    -     */
    -
    -    this.getCurrentUser = function(callback) {
    -        var options = {
    -            rejectUnauthorized: this.strictSSL,
    -            uri: this.makeUri('/session', 'rest/auth/', '1'),
    -            method: 'GET',
    -            json: true
    -        };
    -
    -        this.request(options, function(error, response, body) {
    -
    -            if (error) {
    -                callback(error, null);
    -                return;
    -            }
    -
    -            if (response.statusCode === 200) {
    -                callback(null, body);
    -                return;
    -            }
    -
    -            callback(response.statusCode + ': Error while getting current user');
    -
    -        });
    -    };
    +
      /*
    +   * User object in the format:
    +   * {
    +   *   self: 'http://localhost:8090/rest/api/latest/user?username=user',
    +   *   name: 'user',
    +   *   loginInfo:
    +   *   {
    +   *     failedLoginCount: 2,
    +   *     loginCount: 114,
    +   *     lastFailedLoginTime: '2013-10-29T13:33:26.702+0000',
    +   *     previousLoginTime: '2013-10-31T20:30:51.924+0000'
    +   *   }
    +   * }
    +   */
    +  this.getCurrentUser = function (callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/session', 'rest/auth/', '1'),
    +      method: 'GET',
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response, body) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 200) {
    +        callback(null, body);
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while getting current user');
    +    });
    +  };
  • -
  • +
  • - +

    Retrieve the backlog of a certain Rapid View

    Takes

    @@ -2173,90 +2147,84 @@

    Returns

    -
    	/*
    -	 * Backlog item is in the format:
    -	 *  {
    -	 *      "sprintMarkersMigrated": true,
    -	 *      "issues": [
    -	 *          {
    -	 *              "id": 67890,
    -	 *              "key": "KEY-1234",
    -	 *              "summary": "Issue Summary",
    -	 *              ...
    -	 *          }
    -	 *      ],
    -	 *      "rankCustomFieldId": 12345,
    -	 *      "sprints": [
    -	 *          {
    -	 *              "id": 123,
    -	 *              "name": "Sprint Name",
    -	 *              "state": "FUTURE",
    -	 *              ...
    -	 *          }
    -	 *      ],
    -	 *      "supportsPages": true,
    -	 *      "projects": [
    -	 *          {
    -	 *              "id": 567,
    -	 *              "key": "KEY",
    -	 *              "name": "Project Name"
    -	 *          }
    -	 *      ],
    -	 *      "epicData": {
    -	 *          "epics": [
    -	 *              {
    -	 *                  "id": 9876,
    -	 *                  "key": "KEY-4554",
    -	 *                  "typeName": "Epic",
    -	 *                  ...
    -	 *              }
    -	 *          ],
    -	 *          "canEditEpics": true,
    -	 *          "supportsPages": true
    -	 *      },
    -	 *      "canManageSprints": true,
    -	 *      "maxIssuesExceeded": false,
    -	 *      "queryResultLimit": 2147483647,
    -	 *      "versionData": {
    -	 *          "versionsPerProject": {
    -	 *              "567": [
    -	 *                  {
    -	 *                      "id": 8282,
    -	 *                      "name": "Version Name",
    -	 *                      ...
    -	 *                  }
    -	 *              ]
    -	 *          },
    -	 *          "canCreateVersion": true
    -	 *      }
    -	 *  }
    -	 */
    -	this.getBacklogForRapidView = function(rapidViewId, callback) {
    -		var options = {
    -			rejectUnauthorized: this.strictSSL,
    -			uri: this.makeUri('/xboard/plan/backlog/data?rapidViewId=' + rapidViewId, 'rest/greenhopper/'),
    -			method: 'GET',
    -			json: true
    -		};
    -
    -		this.doRequest(options, function(error, response) {
    -			if (error) {
    -				callback(error, null);
    -
    -				return;
    -			}
    -
    -			if (response.statusCode === 200) {
    -				callback(null, response.body);
    -
    -				return;
    -			}
    -
    -			callback(response.statusCode + ': Error while retrieving backlog');
    -		});
    -	};
    -
    -}).call(JiraApi.prototype);
    +
      /*
    +   * Backlog item is in the format:
    +   *  {
    +   *      "sprintMarkersMigrated": true,
    +   *      "issues": [
    +   *          {
    +   *              "id": 67890,
    +   *              "key": "KEY-1234",
    +   *              "summary": "Issue Summary",
    +   *              ...
    +   *          }
    +   *      ],
    +   *      "rankCustomFieldId": 12345,
    +   *      "sprints": [
    +   *          {
    +   *              "id": 123,
    +   *              "name": "Sprint Name",
    +   *              "state": "FUTURE",
    +   *              ...
    +   *          }
    +   *      ],
    +   *      "supportsPages": true,
    +   *      "projects": [
    +   *          {
    +   *              "id": 567,
    +   *              "key": "KEY",
    +   *              "name": "Project Name"
    +   *          }
    +   *      ],
    +   *      "epicData": {
    +   *          "epics": [
    +   *              {
    +   *                  "id": 9876,
    +   *                  "key": "KEY-4554",
    +   *                  "typeName": "Epic",
    +   *                  ...
    +   *              }
    +   *          ],
    +   *          "canEditEpics": true,
    +   *          "supportsPages": true
    +   *      },
    +   *      "canManageSprints": true,
    +   *      "maxIssuesExceeded": false,
    +   *      "queryResultLimit": 2147483647,
    +   *      "versionData": {
    +   *          "versionsPerProject": {
    +   *              "567": [
    +   *                  {
    +   *                      "id": 8282,
    +   *                      "name": "Version Name",
    +   *                      ...
    +   *                  }
    +   *              ]
    +   *          },
    +   *          "canCreateVersion": true
    +   *      }
    +   *  }
    +   */
    +  this.getBacklogForRapidView = function (rapidViewId, callback) {
    +    var options = {
    +      rejectUnauthorized: this.strictSSL,
    +      uri: this.makeUri('/xboard/plan/backlog/data?rapidViewId=' + rapidViewId, 'rest/greenhopper/'),
    +      method: 'GET',
    +      json: true
    +    };
    +    this.doRequest(options, function (error, response) {
    +      if (error) {
    +        callback(error, null);
    +        return;
    +      }
    +      if (response.statusCode === 200) {
    +        callback(null, response.body);
    +        return;
    +      }
    +      callback(response.statusCode + ': Error while retrieving backlog');
    +    });
    +  };
    +}(JiraApi.prototype));
  • diff --git a/lib/jira.js b/lib/jira.js index 83e4e84d..efa0b667 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -90,7 +90,7 @@ // * Users // * Search // -// ## TODO ## +// ## TO-DO ## // // * Refactor currently implemented APIs to be more Object Oriented // * Refactor to make use of built-in node.js events and classes @@ -128,1793 +128,1554 @@ // * _0.0.3 Added APIs and Docco documentation_ // * _0.0.2 Initial version_ var url = require('url'), - logger = console, - OAuth = require("oauth"); - - -var JiraApi = exports.JiraApi = function(protocol, host, port, username, password, apiVersion, verbose, strictSSL, oauth) { - this.protocol = protocol; - this.host = host; - this.port = port; - this.username = username; - this.password = password; - this.apiVersion = apiVersion; - // Default strictSSL to true (previous behavior) but now allow it to be - // modified - if (strictSSL == null) { - strictSSL = true; - } - this.strictSSL = strictSSL; - // This is so we can fake during unit tests - this.request = require('request'); - if (verbose !== true) { logger = { log: function() {} }; } - - // This is the same almost every time, refactored to make changing it - // later, easier - this.makeUri = function(pathname, altBase, altApiVersion) { - var basePath = 'rest/api/'; - if (altBase != null) { - basePath = altBase; - } - - var apiVersion = this.apiVersion; - if (altApiVersion != null) { - apiVersion = altApiVersion; - } - - var uri = url.format({ - protocol: this.protocol, - hostname: this.host, - port: this.port, - pathname: basePath + apiVersion + pathname - }); - return decodeURIComponent(uri); + logger = console, + OAuth = require("oauth"), + JiraApi; +JiraApi = exports.JiraApi = function (protocol, host, port, username, password, apiVersion, verbose, strictSSL, oauth) { + 'use strict'; + this.protocol = protocol; + this.host = host; + this.port = port; + this.username = username; + this.password = password; + this.apiVersion = apiVersion; + // Default strictSSL to true (previous behavior) but now allow it to be + // modified + if (strictSSL === undefined) { + strictSSL = true; + } + this.strictSSL = strictSSL; + // This is so we can fake during unit tests + this.request = require('request'); + if (verbose !== true) { + logger = { + log: function () {return; } }; - - this.doRequest = function(options, callback) { - if(oauth && oauth.consumer_key && oauth.consumer_secret) { - options.oauth = { - consumer_key: oauth.consumer_key, - consumer_secret: oauth.consumer_secret, - token: oauth.access_token, - token_secret: oauth.access_token_secret - }; - } else { - options.auth = { - 'user': this.username, - 'pass': this.password - }; - } - this.request(options, callback); - }; - + } + // This is the same almost every time, refactored to make changing it + // later, easier + this.makeUri = function (pathname, altBase, altApiVersion) { + var basePath = altBase || 'rest/api/', + apiVer = altApiVersion || this.apiVersion, + uri = url.format({ + protocol: this.protocol, + hostname: this.host, + port: this.port, + pathname: basePath + apiVer + pathname + }); + return decodeURIComponent(uri); + }; + this.doRequest = function (options, callback) { + if (oauth && oauth.consumer_key && oauth.consumer_secret) { + options.oauth = { + consumer_key: oauth.consumer_key, + consumer_secret: oauth.consumer_secret, + token: oauth.access_token, + token_secret: oauth.access_token_secret + }; + } else { + options.auth = { + 'user': this.username, + 'pass': this.password + }; + } + this.request(options, callback); + }; }; - -(function() { - // ## Find an issue in jira ## - // ### Takes ### - // - // * issueNumber: the issueNumber to find - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string of the error - // * issue: an object of the issue - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290709) - this.findIssue = function(issueNumber, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNumber), - method: 'GET' - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid issue number.'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.'); - return; - } - - if (body === undefined) { - callback('Response body was undefined.'); - return; - } - - callback(null, JSON.parse(body)); - - }); +(function () { + 'use strict'; + // ## Find an issue in jira ## + // ### Takes ### + // + // * issueNumber: the issueNumber to find + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string of the error + // * issue: an object of the issue + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290709) + this.findIssue = function (issueNumber, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNumber), + method: 'GET' }; - - // ## Get the unresolved issue count ## - // ### Takes ### - // - // * version: version of your product that you want issues against - // * callback: function for when it's done - // - // ### Returns ### - // * error: string with the error code - // * count: count of unresolved issues for requested version - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id288524) - this.getUnresolvedIssueCount = function(version, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/version/' + version + '/unresolvedIssueCount'), - method: 'GET' - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid version.'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.'); - return; - } - - body = JSON.parse(body); - callback(null, body.issuesUnresolvedCount); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid issue number.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.'); + return; + } + if (body === undefined) { + callback('Response body was undefined.'); + return; + } + callback(null, JSON.parse(body)); + }); + }; + // ## Get the unresolved issue count ## + // ### Takes ### + // + // * version: version of your product that you want issues against + // * callback: function for when it's done + // + // ### Returns ### + // * error: string with the error code + // * count: count of unresolved issues for requested version + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id288524) + this.getUnresolvedIssueCount = function (version, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/version/' + version + '/unresolvedIssueCount'), + method: 'GET' }; - - // ## Get the Project by project key ## - // ### Takes ### - // - // * project: key for the project - // * callback: for when it's done - // - // ### Returns ### - // * error: string of the error - // * project: the json object representing the entire project - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289232) - this.getProject = function(project, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/project/' + project), - method: 'GET' - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid project.'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during getProject.'); - return; - } - - body = JSON.parse(body); - callback(null, body); - - }); - }; - - // ## Find the Rapid View for a specified project ## - // ### Takes ### - // - // * projectName: name for the project - // * callback: for when it's done - // - // ### Returns ### - // * error: string of the error - // * rapidView: rapid view matching the projectName - - /** - * Finds the Rapid View that belongs to a specified project. - * - * @param projectName - * @param callback - */ - this.findRapidView = function(projectName, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapidviews/list', 'rest/greenhopper/'), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); - return; - } - - if (response.body !== null) { - var rapidViews = response.body.views; - for (var i = 0; i < rapidViews.length; i++) { - if(rapidViews[i].name.toLowerCase() === projectName.toLowerCase()) { - callback(null, rapidViews[i]); - return; - } - } - } - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid version.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.'); + return; + } + body = JSON.parse(body); + callback(null, body.issuesUnresolvedCount); + }); + }; + // ## Get the Project by project key ## + // ### Takes ### + // + // * project: key for the project + // * callback: for when it's done + // + // ### Returns ### + // * error: string of the error + // * project: the json object representing the entire project + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289232) + this.getProject = function (project, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/project/' + project), + method: 'GET' }; - - // ## Get a list of Sprints belonging to a Rapid View ## - // ### Takes ### - // - // * rapidViewId: the id for the rapid view - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string with the error - // * sprints: the ?array? of sprints - /** - * Returns a list of sprints belonging to a Rapid View. - * - * @param rapidViewId - * @param callback - */ - this.getLastSprintForRapidView = function(rapidViewId, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/'), - method: 'GET', - json:true - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during sprints search.'); - return; - } - - if (response.body !== null) { - var sprints = response.body.sprints; - callback(null, sprints.pop()); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid project.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during getProject.'); + return; + } + body = JSON.parse(body); + callback(null, body); + }); + }; + // ## Find the Rapid View for a specified project ## + // ### Takes ### + // + // * projectName: name for the project + // * callback: for when it's done + // + // ### Returns ### + // * error: string of the error + // * rapidView: rapid view matching the projectName + /** + * Finds the Rapid View that belongs to a specified project. + * + * @param projectName + * @param callback + */ + this.findRapidView = function (projectName, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/rapidviews/list', 'rest/greenhopper/'), + method: 'GET', + json: true + }, + rapidViews, + i; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); + return; + } + if (response.body !== null) { + rapidViews = response.body.views; + for (i = 0; i < rapidViews.length; i++) { + if (rapidViews[i].name.toLowerCase() === projectName.toLowerCase()) { + callback(null, rapidViews[i]); return; } - - }); - }; - - // ## Get the issues for a rapidView / sprint## - // ### Takes ### - // - // * rapidViewId: the id for the rapid view - // * sprintId: the id for the sprint - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string with the error - // * results: the object with the issues and additional sprint information - /** - * Returns sprint and issues information - * - * @param rapidViewId - * @param sprintId - * @param callback - */ - this.getSprintIssues = function getSprintIssues(rapidViewId, sprintId, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapid/charts/sprintreport?rapidViewId=' + rapidViewId + '&sprintId=' + sprintId, 'rest/greenhopper/'), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if( response.statusCode === 404 ) { - callback('Invalid URL'); - return; - } - - if( response.statusCode !== 200 ) { - callback(response.statusCode + ': Unable to connect to JIRA during sprints search'); - return; - } - - if(response.body !== null) { - callback(null, response.body); - } else { - callback('No body'); } - - }); - + } + }); + }; + // ## Get a list of Sprints belonging to a Rapid View ## + // ### Takes ### + // + // * rapidViewId: the id for the rapid view + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string with the error + // * sprints: the ?array? of sprints + /** + * Returns a list of sprints belonging to a Rapid View. + * + * @param rapidViewId + * @param callback + */ + this.getLastSprintForRapidView = function (rapidViewId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/'), + method: 'GET', + json: true }; - - // ## Add an issue to the project's current sprint ## - // ### Takes ### - // - // * issueId: the id of the existing issue - // * sprintId: the id of the sprint to add it to - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string of the error - // - // - // **does this callback if there's success?** - /** - * Adds a given issue to a project's current sprint - * - * @param issueId - * @param sprintId - * @param callback - */ - this.addIssueToSprint = function(issueId, sprintId, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/sprint/' + sprintId + '/issues/add', 'rest/greenhopper/'), - method: 'PUT', - followAllRedirects: true, - json:true, - body: { - issueKeys: [issueId] - } - }; - - logger.log(options.uri); - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - - if (response.statusCode !== 204) { - callback(response.statusCode + ': Unable to connect to JIRA to add to sprint.'); - return; - } - - }); + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during sprints search.'); + return; + } + if (response.body !== null) { + var sprints = response.body.sprints; + callback(null, sprints.pop()); + return; + } + }); + }; + // ## Get the issues for a rapidView / sprint## + // ### Takes ### + // + // * rapidViewId: the id for the rapid view + // * sprintId: the id for the sprint + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string with the error + // * results: the object with the issues and additional sprint information + /** + * Returns sprint and issues information + * + * @param rapidViewId + * @param sprintId + * @param callback + */ + this.getSprintIssues = function getSprintIssues(rapidViewId, sprintId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/rapid/charts/sprintreport?rapidViewId=' + rapidViewId + '&sprintId=' + sprintId, 'rest/greenhopper/'), + method: 'GET', + json: true }; - - // ## Create an issue link between two issues ## - // ### Takes ### - // - // * link: a link object - // * callback: for when it's done - // - // ### Returns ### - // * error: string if there was an issue, null if success - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id296682) - /** - * Creates an issue link between two issues. Link should follow the below format: - * - * { - * 'linkType': 'Duplicate', - * 'fromIssueKey': 'HSP-1', - * 'toIssueKey': 'MKY-1', - * 'comment': { - * 'body': 'Linked related issue!', - * 'visibility': { - * 'type': 'GROUP', - * 'value': 'jira-users' - * } - * } - * } - * - * @param link - * @param callback - */ - this.issueLink = function(link, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issueLink'), - method: 'POST', - followAllRedirects: true, - json: true, - body: link - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid project.'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during issueLink.'); - return; - } - - callback(null); - - }); + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during sprints search'); + return; + } + if (response.body !== null) { + callback(null, response.body); + } else { + callback('No body'); + } + }); + }; + // ## Add an issue to the project's current sprint ## + // ### Takes ### + // + // * issueId: the id of the existing issue + // * sprintId: the id of the sprint to add it to + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string of the error + // + // + // **does this callback if there's success?** + /** + * Adds a given issue to a project's current sprint + * + * @param issueId + * @param sprintId + * @param callback + */ + this.addIssueToSprint = function (issueId, sprintId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/sprint/' + sprintId + '/issues/add', 'rest/greenhopper/'), + method: 'PUT', + followAllRedirects: true, + json: true, + body: { + issueKeys: [issueId] + } }; - - /** - * Retrieves the remote links associated with the given issue. - * - * @param issueNumber - The internal id or key of the issue - * @param callback - */ - this.getRemoteLinks = function getRemoteLinks(issueNumber, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNumber + '/remotelink'), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid issue number.'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during request.'); - return; - } - - callback(null, response.body); - - }); + logger.log(options.uri); + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 204) { + callback(response.statusCode + ': Unable to connect to JIRA to add to sprint.'); + return; + } + }); + }; + // ## Create an issue link between two issues ## + // ### Takes ### + // + // * link: a link object + // * callback: for when it's done + // + // ### Returns ### + // * error: string if there was an issue, null if success + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id296682) + /** + * Creates an issue link between two issues. Link should follow the below format: + * + * { + * 'linkType': 'Duplicate', + * 'fromIssueKey': 'HSP-1', + * 'toIssueKey': 'MKY-1', + * 'comment': { + * 'body': 'Linked related issue!', + * 'visibility': { + * 'type': 'GROUP', + * 'value': 'jira-users' + * } + * } + * } + * + * @param link + * @param callback + */ + this.issueLink = function (link, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issueLink'), + method: 'POST', + followAllRedirects: true, + json: true, + body: link }; - - /** - * Retrieves the remote links associated with the given issue. - * - * @param issueNumber - The internal id (not the issue key) of the issue - * @param callback - */ - this.createRemoteLink = function createRemoteLink(issueNumber, remoteLink, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNumber + '/remotelink'), - method: 'POST', - json: true, - body: remoteLink - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Cannot create remote link. Invalid issue.'); - return; - } - - if (response.statusCode === 400) { - callback('Cannot create remote link. ' + response.body.errors.title); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during request.'); - return; - } - - callback(null, response.body); - - }); + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid project.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during issueLink.'); + return; + } + callback(null); + }); + }; + /** + * Retrieves the remote links associated with the given issue. + * + * @param issueNumber - The internal id or key of the issue + * @param callback + */ + this.getRemoteLinks = function getRemoteLinks(issueNumber, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNumber + '/remotelink'), + method: 'GET', + json: true }; - - - // ## Get Versions for a project ## - // ### Takes ### - // * project: A project key - // * callback: for when it's done - // - // ### Returns ### - // * error: a string with the error - // * versions: array of the versions for a product - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289653) - this.getVersions = function(project, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/project/' + project + '/versions'), - method: 'GET' - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Invalid project.'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during getVersions.'); - return; - } - - body = JSON.parse(body); - callback(null, body); - - }); + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid issue number.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during request.'); + return; + } + callback(null, response.body); + }); + }; + /** + * Retrieves the remote links associated with the given issue. + * + * @param issueNumber - The internal id (not the issue key) of the issue + * @param callback + */ + this.createRemoteLink = function createRemoteLink(issueNumber, remoteLink, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNumber + '/remotelink'), + method: 'POST', + json: true, + body: remoteLink }; - - // ## Create a version ## - // ### Takes ### - // - // * version: an object of the new version - // * callback: for when it's done - // - // ### Returns ### - // - // * error: error text - // * version: should be the same version you passed up - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id288232) - // - /* { - * "description": "An excellent version", - * "name": "New Version 1", - * "archived": false, - * "released": true, - * "releaseDate": "2010-07-05", - * "userReleaseDate": "5/Jul/2010", - * "project": "PXA" - * } - */ - this.createVersion = function(version, callback) { - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/version'), - method: 'POST', - followAllRedirects: true, - json: true, - body: version - }; - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Version does not exist or the currently authenticated user does not have permission to view it'); - return; - } - - if (response.statusCode === 403) { - callback('The currently authenticated user does not have permission to edit the version'); - return; - } - - if (response.statusCode !== 201) { - callback(response.statusCode + ': Unable to connect to JIRA during createVersion.'); - return; - } - - callback(null, body); - - }); + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Cannot create remote link. Invalid issue.'); + return; + } + if (response.statusCode === 400) { + callback('Cannot create remote link. ' + response.body.errors.title); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during request.'); + return; + } + callback(null, response.body); + }); + }; + // ## Get Versions for a project ## + // ### Takes ### + // * project: A project key + // * callback: for when it's done + // + // ### Returns ### + // * error: a string with the error + // * versions: array of the versions for a product + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289653) + this.getVersions = function (project, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/project/' + project + '/versions'), + method: 'GET' }; - - // ## Update a version ## - // ### Takes ### - // - // * version: an object of the new version - // * callback: for when it's done - // - // ### Returns ### - // - // * error: error text - // * version: should be the same version you passed up - // - // [Jira Doc](https://docs.atlassian.com/jira/REST/latest/#d2e510) - // - /* { - * "id": The ID of the version being updated. Required. - * "description": "An excellent version", - * "name": "New Version 1", - * "archived": false, - * "released": true, - * "releaseDate": "2010-07-05", - * "userReleaseDate": "5/Jul/2010", - * "project": "PXA" - * } - */ - this.updateVersion = function(version, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/version/'+version.id), - method: 'PUT', - followAllRedirects: true, - json: true, - body: version - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 404) { - callback('Version does not exist or the currently authenticated user does not have permission to view it'); - return; - } - - if (response.statusCode === 403) { - callback('The currently authenticated user does not have permission to edit the version'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during updateVersion.'); - return; - } - - callback(null, body); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid project.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during getVersions.'); + return; + } + body = JSON.parse(body); + callback(null, body); + }); + }; + // ## Create a version ## + // ### Takes ### + // + // * version: an object of the new version + // * callback: for when it's done + // + // ### Returns ### + // + // * error: error text + // * version: should be the same version you passed up + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id288232) + // + /* { + * "description": "An excellent version", + * "name": "New Version 1", + * "archived": false, + * "released": true, + * "releaseDate": "2010-07-05", + * "userReleaseDate": "5/Jul/2010", + * "project": "PXA" + * } + */ + this.createVersion = function (version, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/version'), + method: 'POST', + followAllRedirects: true, + json: true, + body: version }; - - // ## Pass a search query to Jira ## - // ### Takes ### - // - // * searchString: jira query string - // * optional: object containing any of the following properties - // * startAt: optional index number (default 0) - // * maxResults: optional max results number (default 50) - // * fields: optional array of desired fields, defaults when null: - // * "summary" - // * "status" - // * "assignee" - // * "description" - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string if there's an error - // * issues: array of issues for the user - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id333082) - this.searchJira = function(searchString, optional, callback) { - // backwards compatibility - optional = optional || {}; - if (Array.isArray(optional)) { - optional = { fields: optional }; - } - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/search'), - method: 'POST', - json: true, - followAllRedirects: true, - body: { - jql: searchString, - startAt: optional.startAt || 0, - maxResults: optional.maxResults || 50, - fields: optional.fields || ["summary", "status", "assignee", "description"] - } - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 400) { - callback('Problem with the JQL query'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during search.'); - return; - } - - callback(null, body); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Version does not exist or the currently authenticated user does not have permission to view it'); + return; + } + if (response.statusCode === 403) { + callback('The currently authenticated user does not have permission to edit the version'); + return; + } + if (response.statusCode !== 201) { + callback(response.statusCode + ': Unable to connect to JIRA during createVersion.'); + return; + } + callback(null, body); + }); + }; + // ## Update a version ## + // ### Takes ### + // + // * version: an object of the new version + // * callback: for when it's done + // + // ### Returns ### + // + // * error: error text + // * version: should be the same version you passed up + // + // [Jira Doc](https://docs.atlassian.com/jira/REST/latest/#d2e510) + // + /* { + * "id": The ID of the version being updated. Required. + * "description": "An excellent version", + * "name": "New Version 1", + * "archived": false, + * "released": true, + * "releaseDate": "2010-07-05", + * "userReleaseDate": "5/Jul/2010", + * "project": "PXA" + * } + */ + this.updateVersion = function (version, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/version/' + version.id), + method: 'PUT', + followAllRedirects: true, + json: true, + body: version }; - - // ## Search user on Jira ## - // ### Takes ### - // - // username: A query string used to search username, name or e-mail address - // startAt: The index of the first user to return (0-based) - // maxResults: The maximum number of users to return (defaults to 50). - // includeActive: If true, then active users are included in the results (default true) - // includeInactive: If true, then inactive users are included in the results (default false) - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string if there's an error - // * users: array of users for the user - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#d2e3756) - // - this.searchUsers = function(username, startAt, maxResults, includeActive, includeInactive, callback) { - startAt = (startAt !== undefined) ? startAt : 0; - maxResults = (maxResults !== undefined) ? maxResults : 50; - includeActive = (includeActive !== undefined) ? includeActive : true; - includeInactive = (includeInactive !== undefined) ? includeInactive : false; - - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri( - '/user/search?username=' + username + - '&startAt=' + startAt + - '&maxResults=' + maxResults + - '&includeActive=' + includeActive + - '&includeInactive=' + includeInactive), - method: 'GET', - json: true, - followAllRedirects: true - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 400) { - callback('Unable to search'); - return; - } - - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during search.'); - return; - } - - callback(null, body); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Version does not exist or the currently authenticated user does not have permission to view it'); + return; + } + if (response.statusCode === 403) { + callback('The currently authenticated user does not have permission to edit the version'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during updateVersion.'); + return; + } + callback(null, body); + }); + }; + // ## Pass a search query to Jira ## + // ### Takes ### + // + // * searchString: jira query string + // * optional: object containing any of the following properties + // * startAt: optional index number (default 0) + // * maxResults: optional max results number (default 50) + // * fields: optional array of desired fields, defaults when null: + // * "summary" + // * "status" + // * "assignee" + // * "description" + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string if there's an error + // * issues: array of issues for the user + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id333082) + this.searchJira = function (searchString, optional, callback) { + // backwards compatibility + optional = optional || {}; + if (Array.isArray(optional)) { + optional = { + fields: optional + }; + } + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/search'), + method: 'POST', + json: true, + followAllRedirects: true, + body: { + jql: searchString, + startAt: optional.startAt || 0, + maxResults: optional.maxResults || 50, + fields: optional.fields || ["summary", "status", "assignee", "description"] + } }; - - // ## Get issues related to a user ## - // ### Takes ### - // - // * user: username of user to search for - // * open: `boolean` determines if only open issues should be returned - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string if there's an error - // * issues: array of issues for the user - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id296043) - // - this.getUsersIssues = function(username, open, callback) { - if (username.indexOf("@") > -1) { - username = username.replace("@", '\\u0040'); - } - var jql = "assignee = " + username; - var openText = ' AND status in (Open, "In Progress", Reopened)'; - if (open) { jql += openText; } - this.searchJira(jql, {}, callback); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 400) { + callback('Problem with the JQL query'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during search.'); + return; + } + callback(null, body); + }); + }; + // ## Search user on Jira ## + // ### Takes ### + // + // username: A query string used to search username, name or e-mail address + // startAt: The index of the first user to return (0-based) + // maxResults: The maximum number of users to return (defaults to 50). + // includeActive: If true, then active users are included in the results (default true) + // includeInactive: If true, then inactive users are included in the results (default false) + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string if there's an error + // * users: array of users for the user + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#d2e3756) + // + this.searchUsers = function (username, startAt, maxResults, includeActive, includeInactive, callback) { + startAt = (startAt !== undefined) ? startAt : 0; + maxResults = (maxResults !== undefined) ? maxResults : 50; + includeActive = (includeActive !== undefined) ? includeActive : true; + includeInactive = (includeInactive !== undefined) ? includeInactive : false; + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/user/search?username=' + username + '&startAt=' + startAt + '&maxResults=' + maxResults + '&includeActive=' + includeActive + '&includeInactive=' + includeInactive), + method: 'GET', + json: true, + followAllRedirects: true }; - - // ## Add issue to Jira ## - // ### Takes ### - // - // * issue: Properly Formatted Issue - // * callback: for when it's done - // - // ### Returns ### - // * error object (check out the Jira Doc) - // * success object - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290028) - this.addNewIssue = function(issue, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue'), - method: 'POST', - followAllRedirects: true, - json: true, - body: issue - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 400) { - callback(body); - return; - } - - if ((response.statusCode !== 200) && (response.statusCode !== 201)) { - callback(response.statusCode + ': Unable to connect to JIRA during search.'); - return; - } - - callback(null, body); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 400) { + callback('Unable to search'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during search.'); + return; + } + callback(null, body); + }); + }; + // ## Get issues related to a user ## + // ### Takes ### + // + // * user: username of user to search for + // * open: `boolean` determines if only open issues should be returned + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string if there's an error + // * issues: array of issues for the user + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id296043) + // + this.getUsersIssues = function (username, open, callback) { + if (username.indexOf("@") > -1) { + username = username.replace("@", '\\u0040'); + } + var jql = "assignee = " + username, + openText = ' AND status in (Open, "In Progress", Reopened)'; + if (open) { + jql += openText; + } + this.searchJira(jql, {}, callback); + }; + // ## Add issue to Jira ## + // ### Takes ### + // + // * issue: Properly Formatted Issue + // * callback: for when it's done + // + // ### Returns ### + // * error object (check out the Jira Doc) + // * success object + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290028) + this.addNewIssue = function (issue, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue'), + method: 'POST', + followAllRedirects: true, + json: true, + body: issue }; - - // ## Delete issue to Jira ## - // ### Takes ### - // - // * issueId: the Id of the issue to delete - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success object - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290791) - this.deleteIssue = function(issueNum, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNum), - method: 'DELETE', - followAllRedirects: true, - json: true - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 204) { - callback(null, "Success"); - return; - } - - callback(response.statusCode + ': Error while deleting'); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 400) { + callback(body); + return; + } + if ((response.statusCode !== 200) && (response.statusCode !== 201)) { + callback(response.statusCode + ': Unable to connect to JIRA during search.'); + return; + } + callback(null, body); + }); + }; + // ## Delete issue to Jira ## + // ### Takes ### + // + // * issueId: the Id of the issue to delete + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success object + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290791) + this.deleteIssue = function (issueNum, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNum), + method: 'DELETE', + followAllRedirects: true, + json: true }; - - // ## Update issue in Jira ## - // ### Takes ### - // - // * issueId: the Id of the issue to delete - // * issueUpdate: update Object - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success string - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290878) - this.updateIssue = function(issueNum, issueUpdate, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNum), - body: issueUpdate, - method: 'PUT', - followAllRedirects: true, - json: true - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 200 || response.statusCode === 204) { - callback(null, "Success"); - return; - } - - callback(response.statusCode + ': Error while updating'); - - }); + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 204) { + callback(null, "Success"); + return; + } + callback(response.statusCode + ': Error while deleting'); + }); + }; + // ## Update issue in Jira ## + // ### Takes ### + // + // * issueId: the Id of the issue to delete + // * issueUpdate: update Object + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success string + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290878) + this.updateIssue = function (issueNum, issueUpdate, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNum), + body: issueUpdate, + method: 'PUT', + followAllRedirects: true, + json: true }; - - // ## List Components ## - // ### Takes ### - // - // * project: key for the project - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of components - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) - /* - * [{ - * "self": "http://localhostname:8090/jira/rest/api/2.0/component/1234", - * "id": "1234", - * "name": "name", - * "description": "Description.", - * "assigneeType": "PROJECT_DEFAULT", - * "assignee": { - * "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com", - * "name": "user@domain.com", - * "displayName": "SE Support", - * "active": true - * }, - * "realAssigneeType": "PROJECT_DEFAULT", - * "realAssignee": { - * "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com", - * "name": "user@domain.com", - * "displayName": "User name", - * "active": true - * }, - * "isAssigneeTypeValid": true - * }] - */ - this.listComponents = function(project, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/project/' + project + '/components'), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 200) { - callback(null, body); - return; - } - if (response.statusCode === 404) { - callback("Project not found"); - return; - } - - callback(response.statusCode + ': Error while updating'); - - }); + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200 || response.statusCode === 204) { + callback(null, "Success"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List Components ## + // ### Takes ### + // + // * project: key for the project + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of components + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) + /* + * [{ + * "self": "http://localhostname:8090/jira/rest/api/2.0/component/1234", + * "id": "1234", + * "name": "name", + * "description": "Description.", + * "assigneeType": "PROJECT_DEFAULT", + * "assignee": { + * "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com", + * "name": "user@domain.com", + * "displayName": "SE Support", + * "active": true + * }, + * "realAssigneeType": "PROJECT_DEFAULT", + * "realAssignee": { + * "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com", + * "name": "user@domain.com", + * "displayName": "User name", + * "active": true + * }, + * "isAssigneeTypeValid": true + * }] + */ + this.listComponents = function (project, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/project/' + project + '/components'), + method: 'GET', + json: true }; - - // ## List listFields ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of priorities - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) - /* - * [{ - * "id": "field", - * "name": "Field", - * "custom": false, - * "orderable": true, - * "navigable": true, - * "searchable": true, - * "schema": { - * "type": "string", - * "system": "field" - * } - * }] - */ - this.listFields = function(callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/field'), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 200) { - callback(null, body); - return; - } - if (response.statusCode === 404) { - callback("Not found"); - return; - } - - callback(response.statusCode + ': Error while updating'); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + if (response.statusCode === 404) { + callback("Project not found"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List listFields ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of priorities + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) + /* + * [{ + * "id": "field", + * "name": "Field", + * "custom": false, + * "orderable": true, + * "navigable": true, + * "searchable": true, + * "schema": { + * "type": "string", + * "system": "field" + * } + * }] + */ + this.listFields = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/field'), + method: 'GET', + json: true }; - - // ## List listPriorities ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of priorities - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) - /* - * [{ - * "self": "http://localhostname:8090/jira/rest/api/2.0/priority/1", - * "statusColor": "#ff3300", - * "description": "Crashes, loss of data, severe memory leak.", - * "name": "Major", - * "id": "2" - * }] - */ - this.listPriorities = function(callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/priority'), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 200) { - callback(null, body); - return; - } - if (response.statusCode === 404) { - callback("Not found"); - return; - } - - callback(response.statusCode + ': Error while updating'); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + if (response.statusCode === 404) { + callback("Not found"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List listPriorities ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of priorities + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) + /* + * [{ + * "self": "http://localhostname:8090/jira/rest/api/2.0/priority/1", + * "statusColor": "#ff3300", + * "description": "Crashes, loss of data, severe memory leak.", + * "name": "Major", + * "id": "2" + * }] + */ + this.listPriorities = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/priority'), + method: 'GET', + json: true }; - - // ## List Transitions ## - // ### Takes ### - // - // * issueId: get transitions available for the issue - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of transitions - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) - /* - * { - * "expand": "transitions", - * "transitions": [ - * { - * "id": "2", - * "name": "Close Issue", - * "to": { - * "self": "http://localhostname:8090/jira/rest/api/2.0/status/10000", - * "description": "The issue is currently being worked on.", - * "iconUrl": "http://localhostname:8090/jira/images/icons/progress.gif", - * "name": "In Progress", - * "id": "10000" - * }, - * "fields": { - * "summary": { - * "required": false, - * "schema": { - * "type": "array", - * "items": "option", - * "custom": "com.atlassian.jira.plugin.system.customfieldtypes:multiselect", - * "customId": 10001 - * }, - * "name": "My Multi Select", - * "operations": [ - * "set", - * "add" - * ], - * "allowedValues": [ - * "red", - * "blue" - * ] - * } - * } - * } - * ]} - */ - this.listTransitions = function(issueId, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueId + '/transitions?expand=transitions.fields'), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 200) { - callback(null, body); - return; - } - if (response.statusCode === 404) { - callback("Issue not found"); - return; - } - - callback(response.statusCode + ': Error while updating'); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + if (response.statusCode === 404) { + callback("Not found"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List Transitions ## + // ### Takes ### + // + // * issueId: get transitions available for the issue + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of transitions + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) + /* + * { + * "expand": "transitions", + * "transitions": [ + * { + * "id": "2", + * "name": "Close Issue", + * "to": { + * "self": "http://localhostname:8090/jira/rest/api/2.0/status/10000", + * "description": "The issue is currently being worked on.", + * "iconUrl": "http://localhostname:8090/jira/images/icons/progress.gif", + * "name": "In Progress", + * "id": "10000" + * }, + * "fields": { + * "summary": { + * "required": false, + * "schema": { + * "type": "array", + * "items": "option", + * "custom": "com.atlassian.jira.plugin.system.customfieldtypes:multiselect", + * "customId": 10001 + * }, + * "name": "My Multi Select", + * "operations": [ + * "set", + * "add" + * ], + * "allowedValues": [ + * "red", + * "blue" + * ] + * } + * } + * } + * ]} + */ + this.listTransitions = function (issueId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueId + '/transitions?expand=transitions.fields'), + method: 'GET', + json: true }; - - // ## Transition issue in Jira ## - // ### Takes ### - // - // * issueId: the Id of the issue to delete - // * issueTransition: transition Object - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success string - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) - this.transitionIssue = function(issueNum, issueTransition, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNum + '/transitions'), - body: issueTransition, - method: 'POST', - followAllRedirects: true, - json: true - }; - - this.doRequest(options, function(error, response) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 204) { - callback(null, "Success"); - return; - } - - callback(response.statusCode + ': Error while updating'); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + if (response.statusCode === 404) { + callback("Issue not found"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## Transition issue in Jira ## + // ### Takes ### + // + // * issueId: the Id of the issue to delete + // * issueTransition: transition Object + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success string + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) + this.transitionIssue = function (issueNum, issueTransition, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNum + '/transitions'), + body: issueTransition, + method: 'POST', + followAllRedirects: true, + json: true }; - - // ## List all Viewable Projects ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of projects - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289193) - /* - * Result items are in the format: - * { - * "self": "http://www.example.com/jira/rest/api/2/project/ABC", - * "id": "10001", - * "key": "ABC", - * "name": "Alphabetical", - * "avatarUrls": { - * "16x16": "http://www.example.com/jira/secure/projectavatar?size=small&pid=10001", - * "48x48": "http://www.example.com/jira/secure/projectavatar?size=large&pid=10001" - * } - * } - */ - this.listProjects = function(callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/project'), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 200) { - callback(null, body); - return; - } - if (response.statusCode === 500) { - callback(response.statusCode + ': Error while retrieving list.'); - return; - } - - callback(response.statusCode + ': Error while updating'); - - }); + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 204) { + callback(null, "Success"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List all Viewable Projects ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of projects + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289193) + /* + * Result items are in the format: + * { + * "self": "http://www.example.com/jira/rest/api/2/project/ABC", + * "id": "10001", + * "key": "ABC", + * "name": "Alphabetical", + * "avatarUrls": { + * "16x16": "http://www.example.com/jira/secure/projectavatar?size=small&pid=10001", + * "48x48": "http://www.example.com/jira/secure/projectavatar?size=large&pid=10001" + * } + * } + */ + this.listProjects = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/project'), + method: 'GET', + json: true }; - - // ## Add a comment to an issue ## - // ### Takes ### - // * issueId: Issue to add a comment to - // * comment: string containing comment - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success string - // - // [Jira Doc](https://docs.atlassian.com/jira/REST/latest/#id108798) - this.addComment = function(issueId, comment, callback){ - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueId + '/comment'), - body: { - "body": comment - }, - method: 'POST', - followAllRedirects: true, - json: true - }; - - this.doRequest(options, function(error, response, body) { - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 201) { - callback(null, "Success"); - return; - } - - if (response.statusCode === 400) { - callback("Invalid Fields: " + JSON.stringify(body)); - return; - }; - - callback(response.statusCode + ': Error while adding comment'); - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + if (response.statusCode === 500) { + callback(response.statusCode + ': Error while retrieving list.'); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## Add a comment to an issue ## + // ### Takes ### + // * issueId: Issue to add a comment to + // * comment: string containing comment + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success string + // + // [Jira Doc](https://docs.atlassian.com/jira/REST/latest/#id108798) + this.addComment = function (issueId, comment, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueId + '/comment'), + body: { + "body": comment + }, + method: 'POST', + followAllRedirects: true, + json: true }; - - // ## Add a worklog to a project ## - // ### Takes ### - // * issueId: Issue to add a worklog to - // * worklog: worklog object - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success string - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id291617) - /* - * Worklog item is in the format: - * { - * "self": "http://www.example.com/jira/rest/api/2.0/issue/10010/worklog/10000", - * "author": { - * "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred", - * "name": "fred", - * "displayName": "Fred F. User", - * "active": false - * }, - * "updateAuthor": { - * "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred", - * "name": "fred", - * "displayName": "Fred F. User", - * "active": false - * }, - * "comment": "I did some work here.", - * "visibility": { - * "type": "group", - * "value": "jira-developers" - * }, - * "started": "2012-11-22T04:19:46.736-0600", - * "timeSpent": "3h 20m", - * "timeSpentSeconds": 12000, - * "id": "100028" - * } - */ - this.addWorklog = function(issueId, worklog, newEstimate, callback) { - if(typeof callback == 'undefined') { - callback = newEstimate; - newEstimate = false; - } - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueId + '/worklog' + (newEstimate ? "?adjustEstimate=new&newEstimate=" + newEstimate : "")), - body: worklog, - method: 'POST', - followAllRedirects: true, - json: true - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 201) { - callback(null, "Success"); - return; - } - if (response.statusCode === 400) { - callback("Invalid Fields: " + JSON.stringify(body)); - return; - } - if (response.statusCode === 403) { - callback("Insufficient Permissions"); - return; - } - - callback(response.statusCode + ': Error while updating'); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 201) { + callback(null, "Success"); + return; + } + if (response.statusCode === 400) { + callback("Invalid Fields: " + JSON.stringify(body)); + return; + } + callback(response.statusCode + ': Error while adding comment'); + }); + }; + // ## Add a worklog to a project ## + // ### Takes ### + // * issueId: Issue to add a worklog to + // * worklog: worklog object + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success string + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id291617) + /* + * Worklog item is in the format: + * { + * "self": "http://www.example.com/jira/rest/api/2.0/issue/10010/worklog/10000", + * "author": { + * "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred", + * "name": "fred", + * "displayName": "Fred F. User", + * "active": false + * }, + * "updateAuthor": { + * "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred", + * "name": "fred", + * "displayName": "Fred F. User", + * "active": false + * }, + * "comment": "I did some work here.", + * "visibility": { + * "type": "group", + * "value": "jira-developers" + * }, + * "started": "2012-11-22T04:19:46.736-0600", + * "timeSpent": "3h 20m", + * "timeSpentSeconds": 12000, + * "id": "100028" + * } + */ + this.addWorklog = function (issueId, worklog, newEstimate, callback) { + if (callback === undefined) { + callback = newEstimate; + newEstimate = false; + } + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueId + '/worklog' + (newEstimate ? "?adjustEstimate=new&newEstimate=" + newEstimate : "")), + body: worklog, + method: 'POST', + followAllRedirects: true, + json: true }; - - // ## List all Issue Types ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of types - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id295946) - /* - * Result items are in the format: - * { - * "self": "http://localhostname:8090/jira/rest/api/2.0/issueType/3", - * "id": "3", - * "description": "A task that needs to be done.", - * "iconUrl": "http://localhostname:8090/jira/images/icons/task.gif", - * "name": "Task", - * "subtask": false - * } - */ - this.listIssueTypes = function(callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issuetype'), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 200) { - callback(null, body); - return; - } - - callback(response.statusCode + ': Error while retrieving issue types'); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 201) { + callback(null, "Success"); + return; + } + if (response.statusCode === 400) { + callback("Invalid Fields: " + JSON.stringify(body)); + return; + } + if (response.statusCode === 403) { + callback("Insufficient Permissions"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List all Issue Types ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of types + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id295946) + /* + * Result items are in the format: + * { + * "self": "http://localhostname:8090/jira/rest/api/2.0/issueType/3", + * "id": "3", + * "description": "A task that needs to be done.", + * "iconUrl": "http://localhostname:8090/jira/images/icons/task.gif", + * "name": "Task", + * "subtask": false + * } + */ + this.listIssueTypes = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issuetype'), + method: 'GET', + json: true }; - - // ## Register a webhook ## - // ### Takes ### - // - // * webhook: properly formatted webhook - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success object - // - // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) - /* - * Success object in the format: - * { - * name: 'my first webhook via rest', - * events: [], - * url: 'http://www.example.com/webhooks', - * filter: '', - * excludeIssueDetails: false, - * enabled: true, - * self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5', - * lastUpdatedUser: 'user', - * lastUpdatedDisplayName: 'User Name', - * lastUpdated: 1383247225784 - * } - */ - this.registerWebhook = function(webhook, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'), - method: 'POST', - json: true, - body: webhook - }; - - this.request(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 201) { - callback(null, body); - return; - } - - callback(response.statusCode + ': Error while registering new webhook'); - - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + callback(response.statusCode + ': Error while retrieving issue types'); + }); + }; + // ## Register a webhook ## + // ### Takes ### + // + // * webhook: properly formatted webhook + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success object + // + // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) + /* + * Success object in the format: + * { + * name: 'my first webhook via rest', + * events: [], + * url: 'http://www.example.com/webhooks', + * filter: '', + * excludeIssueDetails: false, + * enabled: true, + * self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5', + * lastUpdatedUser: 'user', + * lastUpdatedDisplayName: 'User Name', + * lastUpdated: 1383247225784 + * } + */ + this.registerWebhook = function (webhook, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'), + method: 'POST', + json: true, + body: webhook }; - - // ## List all registered webhooks ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of webhook objects - // - // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) - /* - * Webhook object in the format: - * { - * name: 'my first webhook via rest', - * events: [], - * url: 'http://www.example.com/webhooks', - * filter: '', - * excludeIssueDetails: false, - * enabled: true, - * self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5', - * lastUpdatedUser: 'user', - * lastUpdatedDisplayName: 'User Name', - * lastUpdated: 1383247225784 - * } - */ - this.listWebhooks = function(callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'), - method: 'GET', - json: true - }; - - this.request(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 200) { - callback(null, body); - return; - } - - callback(response.statusCode + ': Error while listing webhooks'); - - }); + this.request(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 201) { + callback(null, body); + return; + } + callback(response.statusCode + ': Error while registering new webhook'); + }); + }; + // ## List all registered webhooks ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of webhook objects + // + // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) + /* + * Webhook object in the format: + * { + * name: 'my first webhook via rest', + * events: [], + * url: 'http://www.example.com/webhooks', + * filter: '', + * excludeIssueDetails: false, + * enabled: true, + * self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5', + * lastUpdatedUser: 'user', + * lastUpdatedDisplayName: 'User Name', + * lastUpdated: 1383247225784 + * } + */ + this.listWebhooks = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'), + method: 'GET', + json: true }; - - // ## Get a webhook by its ID ## - // ### Takes ### - // - // * webhookID: id of webhook to get - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * webhook object - // - // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) - this.getWebhook = function(webhookID, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'), - method: 'GET', - json: true - }; - - this.request(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 200) { - callback(null, body); - return; - } - - callback(response.statusCode + ': Error while getting webhook'); - - }); + this.request(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + callback(response.statusCode + ': Error while listing webhooks'); + }); + }; + // ## Get a webhook by its ID ## + // ### Takes ### + // + // * webhookID: id of webhook to get + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * webhook object + // + // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) + this.getWebhook = function (webhookID, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'), + method: 'GET', + json: true }; - - // ## Delete a registered webhook ## - // ### Takes ### - // - // * webhookID: id of the webhook to delete - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success string - // - // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) - this.deleteWebhook = function(webhookID, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'), - method: 'DELETE', - json: true - }; - - this.request(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 204) { - callback(null, "Success"); - return; - } - - callback(response.statusCode + ': Error while deleting webhook'); - - }); + this.request(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + callback(response.statusCode + ': Error while getting webhook'); + }); + }; + // ## Delete a registered webhook ## + // ### Takes ### + // + // * webhookID: id of the webhook to delete + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success string + // + // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) + this.deleteWebhook = function (webhookID, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'), + method: 'DELETE', + json: true }; - - // ## Describe the currently authenticated user ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * user object - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id2e865) - /* - * User object in the format: - * { - * self: 'http://localhost:8090/rest/api/latest/user?username=user', - * name: 'user', - * loginInfo: - * { - * failedLoginCount: 2, - * loginCount: 114, - * lastFailedLoginTime: '2013-10-29T13:33:26.702+0000', - * previousLoginTime: '2013-10-31T20:30:51.924+0000' - * } - * } - */ - this.getCurrentUser = function(callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/session', 'rest/auth/', '1'), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response, body) { - - if (error) { - callback(error, null); - return; - } - - if (response.statusCode === 200) { - callback(null, body); - return; - } - - callback(response.statusCode + ': Error while getting current user'); - - }); + this.request(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 204) { + callback(null, "Success"); + return; + } + callback(response.statusCode + ': Error while deleting webhook'); + }); + }; + // ## Describe the currently authenticated user ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * user object + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id2e865) + /* + * User object in the format: + * { + * self: 'http://localhost:8090/rest/api/latest/user?username=user', + * name: 'user', + * loginInfo: + * { + * failedLoginCount: 2, + * loginCount: 114, + * lastFailedLoginTime: '2013-10-29T13:33:26.702+0000', + * previousLoginTime: '2013-10-31T20:30:51.924+0000' + * } + * } + */ + this.getCurrentUser = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/session', 'rest/auth/', '1'), + method: 'GET', + json: true }; - - // ## Retrieve the backlog of a certain Rapid View ## - // ### Takes ### - // * rapidViewId: rapid view id - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * backlog object - /* - * Backlog item is in the format: - * { - * "sprintMarkersMigrated": true, - * "issues": [ - * { - * "id": 67890, - * "key": "KEY-1234", - * "summary": "Issue Summary", - * ... - * } - * ], - * "rankCustomFieldId": 12345, - * "sprints": [ - * { - * "id": 123, - * "name": "Sprint Name", - * "state": "FUTURE", - * ... - * } - * ], - * "supportsPages": true, - * "projects": [ - * { - * "id": 567, - * "key": "KEY", - * "name": "Project Name" - * } - * ], - * "epicData": { - * "epics": [ - * { - * "id": 9876, - * "key": "KEY-4554", - * "typeName": "Epic", - * ... - * } - * ], - * "canEditEpics": true, - * "supportsPages": true - * }, - * "canManageSprints": true, - * "maxIssuesExceeded": false, - * "queryResultLimit": 2147483647, - * "versionData": { - * "versionsPerProject": { - * "567": [ - * { - * "id": 8282, - * "name": "Version Name", - * ... - * } - * ] - * }, - * "canCreateVersion": true - * } - * } - */ - this.getBacklogForRapidView = function(rapidViewId, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/xboard/plan/backlog/data?rapidViewId=' + rapidViewId, 'rest/greenhopper/'), - method: 'GET', - json: true - }; - - this.doRequest(options, function(error, response) { - if (error) { - callback(error, null); - - return; - } - - if (response.statusCode === 200) { - callback(null, response.body); - - return; - } - - callback(response.statusCode + ': Error while retrieving backlog'); - }); + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + callback(response.statusCode + ': Error while getting current user'); + }); + }; + // ## Retrieve the backlog of a certain Rapid View ## + // ### Takes ### + // * rapidViewId: rapid view id + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * backlog object + /* + * Backlog item is in the format: + * { + * "sprintMarkersMigrated": true, + * "issues": [ + * { + * "id": 67890, + * "key": "KEY-1234", + * "summary": "Issue Summary", + * ... + * } + * ], + * "rankCustomFieldId": 12345, + * "sprints": [ + * { + * "id": 123, + * "name": "Sprint Name", + * "state": "FUTURE", + * ... + * } + * ], + * "supportsPages": true, + * "projects": [ + * { + * "id": 567, + * "key": "KEY", + * "name": "Project Name" + * } + * ], + * "epicData": { + * "epics": [ + * { + * "id": 9876, + * "key": "KEY-4554", + * "typeName": "Epic", + * ... + * } + * ], + * "canEditEpics": true, + * "supportsPages": true + * }, + * "canManageSprints": true, + * "maxIssuesExceeded": false, + * "queryResultLimit": 2147483647, + * "versionData": { + * "versionsPerProject": { + * "567": [ + * { + * "id": 8282, + * "name": "Version Name", + * ... + * } + * ] + * }, + * "canCreateVersion": true + * } + * } + */ + this.getBacklogForRapidView = function (rapidViewId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/xboard/plan/backlog/data?rapidViewId=' + rapidViewId, 'rest/greenhopper/'), + method: 'GET', + json: true }; - -}).call(JiraApi.prototype); \ No newline at end of file + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, response.body); + return; + } + callback(response.statusCode + ': Error while retrieving backlog'); + }); + }; +}(JiraApi.prototype)); \ No newline at end of file From e46289ca4d826dc2e8f3ffc7b44b580f4ed46940 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sat, 18 Oct 2014 21:37:55 -0400 Subject: [PATCH 06/21] ignore .cache files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 242f619f..3f4d123c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ npm-debug.log .idea *.iml +*.cache \ No newline at end of file From 7613a0c584abf8c37533222d528ccbffced91165 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sat, 18 Oct 2014 21:43:20 -0400 Subject: [PATCH 07/21] Add getAllRapidViews method --- lib/jira.js | 33 + lib/newjira.js | 1850 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1883 insertions(+) create mode 100644 lib/newjira.js diff --git a/lib/jira.js b/lib/jira.js index efa0b667..a933950d 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -290,6 +290,39 @@ JiraApi = exports.JiraApi = function (protocol, host, port, username, password, callback(null, body); }); }; + // ## Find all Rapid Views ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error: string of the error + // * rapidViews: array of views + this.getAllRapidViews = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/rapidview', 'rest/greenhopper/', '1.0'), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); + return; + } + if (response.body !== null) { + callback(null, response.body.views); + } + }); + }; // ## Find the Rapid View for a specified project ## // ### Takes ### // diff --git a/lib/newjira.js b/lib/newjira.js new file mode 100644 index 00000000..ec6aac58 --- /dev/null +++ b/lib/newjira.js @@ -0,0 +1,1850 @@ +// # JavaScript JIRA API for node.js # +// +// [![Build Status](https://travis-ci.org/steves/node-jira.png?branch=master)](https://travis-ci.org/steves/node-jira) +// +// A node.js module, which provides an object oriented wrapper for the JIRA REST API. +// +// This library is built to support version `2.0.alpha1` of the JIRA REST API. +// This library is also tested with version `2` of the JIRA REST API +// It has been noted that with Jira OnDemand, `2.0.alpha1` does not work, devs +// should revert to `2`. If this changes, please notify us. +// +// JIRA REST API documentation can be found [here](http://docs.atlassian.com/jira/REST/latest/) +// +// ## Installation ## +// +// Install with the node package manager [npm](http://npmjs.org): +// +// $ npm install jira +// +// or +// +// Install via git clone: +// +// $ git clone git://github.com/steves/node-jira.git +// $ cd node-jira +// $ npm install +// +// ## Example ## +// +// Find the status of an issue. +// +// JiraApi = require('jira').JiraApi; +// +// var jira = new JiraApi('https', config.host, config.port, config.user, config.password, '2.0.alpha1'); +// jira.findIssue(issueNumber, function(error, issue) { +// console.log('Status: ' + issue.fields.status.name); +// }); +// +// Currently there is no explicit login call necessary as each API call uses Basic Authentication to authenticate. +// +// ## Options ## +// +// JiraApi options: +// * `protocol`: Typically 'http:' or 'https:' +// * `host`: The hostname for your jira server +// * `port`: The port your jira server is listening on (probably `80` or `443`) +// * `user`: The username to log in with +// * `password`: Keep it secret, keep it safe +// * `Jira API Version`: Known to work with `2` and `2.0.alpha1` +// * `verbose`: Log some info to the console, usually for debugging +// * `strictSSL`: Set to false if you have self-signed certs or something non-trustworthy +// * `oauth`: A disctionary of `consumer_key`, `consumer_secret`, `access_token` and `access_token_secret` to be used for OAuth authentication. +// +// ## Implemented APIs ## +// +// * Authentication +// * HTTP +// * OAuth +// * Projects +// * Pulling a project +// * List all projects viewable to the user +// * List Components +// * List Fields +// * List Priorities +// * Versions +// * Pulling versions +// * Adding a new version +// * Updating a version +// * Pulling unresolved issues count for a specific version +// * Rapid Views +// * Find based on project name +// * Get the latest Green Hopper sprint +// * Gets attached issues +// * Issues +// * Add a new issue +// * Update an issue +// * Transition an issue +// * Pulling an issue +// * Issue linking +// * Add an issue to a sprint +// * Get a users issues (open or all) +// * List issue types +// * Search using jql +// * Set Max Results +// * Set Start-At parameter for results +// * Add a worklog +// * Add new estimate for worklog +// * Add a comment +// * Transitions +// * List +// * Users +// * Search +// +// ## TODO ## +// +// * Refactor currently implemented APIs to be more Object Oriented +// * Refactor to make use of built-in node.js events and classes +// +// ## Changelog ## +// +// +// * _0.10.0 Add Update Version (thanks to [floralvikings](https://github.com/floralvikings))_ +// * _0.9.0 Add OAuth Support and New Estimates on addWorklog (thanks to +// [nagyv](https://github.com/nagyv))_ +// * _0.8.2 Fix URL Format Issues (thanks to +// [eduardolundgren](https://github.com/eduardolundgren))_ +// * _0.8.1 Expanding the transitions options (thanks to +// [eduardolundgren](https://github.com/eduardolundgren))_ +// * _0.8.0 Ability to search users (thanks to +// [eduardolundgren](https://github.com/eduardolundgren))_ +// * _0.7.2 Allows HTTP Code 204 on issue update edit (thanks to +// [eduardolundgren](https://github.com/eduardolundgren))_ +// * _0.7.1 Check if body variable is undef (thanks to +// [AlexCline](https://github.com/AlexCline))_ +// * _0.7.0 Adds list priorities, list fields, and project components (thanks to +// [eduardolundgren](https://github.com/eduardolundgren))_ +// * _0.6.0 Comment API implemented (thanks to [StevenMcD](https://github.com/StevenMcD))_ +// * _0.5.0 Last param is now for strict SSL checking, defaults to true_ +// * _0.4.1 Now handing errors in the request callback (thanks [mrbrookman](https://github.com/mrbrookman))_ +// * _0.4.0 Now auto-redirecting between http and https (for both GET and POST)_ +// * _0.3.1 [Request](https://github.com/mikeal/request) is broken, setting max request package at 2.15.0_ +// * _0.3.0 Now Gets Issues for a Rapidview/Sprint (thanks [donbonifacio](https://github.com/donbonifacio))_ +// * _0.2.0 Now allowing startAt and MaxResults to be passed to searchJira, +// switching to semantic versioning._ +// * _0.1.0 Using Basic Auth instead of cookies, all calls unit tested, URI +// creation refactored_ +// * _0.0.6 Now linting, preparing to refactor_ +// * _0.0.5 JQL search now takes a list of fields_ +// * _0.0.4 Added jql search_ +// * _0.0.3 Added APIs and Docco documentation_ +// * _0.0.2 Initial version_ +var url = require('url'), + logger = console, + OAuth = require("oauth"); +var JiraApi = exports.JiraApi = function (protocol, host, port, username, password, apiVersion, agileApiVersion, verbose, strictSSL, oauth) { + this.protocol = protocol; + this.host = host; + this.port = port; + this.username = username; + this.password = password; + this.apiVersion = apiVersion; + this.agileApiVersion = agileApiVersion; + // Default strictSSL to true (previous behavior) but now allow it to be + // modified + if (strictSSL == null) { + strictSSL = true; + } + this.strictSSL = strictSSL; + // This is so we can fake during unit tests + this.request = require('request'); + if (verbose !== true) { + logger = { + log: function () {} + }; + } + // This is the same almost every time, refactored to make changing it + // later, easier + this.makeUri = function (pathname, altBase, altApiVersion) { + var basePath = 'rest/api/'; + if (altBase != null) { + basePath = altBase; + } + var apiVersion = this.apiVersion; + if (altApiVersion != null) { + apiVersion = altApiVersion; + } + var uri = url.format({ + protocol: this.protocol, + hostname: this.host, + port: this.port, + pathname: basePath + apiVersion + pathname + }); + return decodeURIComponent(uri); + }; + this.doRequest = function (options, callback) { + if (oauth && oauth.consumer_key && oauth.consumer_secret) { + options.oauth = { + consumer_key: oauth.consumer_key, + consumer_secret: oauth.consumer_secret, + token: oauth.access_token, + token_secret: oauth.access_token_secret + }; + } else { + options.auth = { + 'user': this.username, + 'pass': this.password + }; + } + this.request(options, callback); + }; +}; +(function () { + // ## Find an issue in jira ## + // ### Takes ### + // + // * issueNumber: the issueNumber to find + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string of the error + // * issue: an object of the issue + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290709) + this.findIssue = function (issueNumber, params, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNumber), + method: 'GET' + }; + if (params) { + options.uri += ('?fields=' + params); + } + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid issue number.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.'); + return; + } + if (body === undefined) { + callback('Response body was undefined.'); + return; + } + callback(null, JSON.parse(body)); + }); + }; + // ## Get the unresolved issue count ## + // ### Takes ### + // + // * version: version of your product that you want issues against + // * callback: function for when it's done + // + // ### Returns ### + // * error: string with the error code + // * count: count of unresolved issues for requested version + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id288524) + this.getUnresolvedIssueCount = function (version, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/version/' + version + '/unresolvedIssueCount'), + method: 'GET' + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid version.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.'); + return; + } + body = JSON.parse(body); + callback(null, body.issuesUnresolvedCount); + }); + }; + // ## Get the Project by project key ## + // ### Takes ### + // + // * project: key for the project + // * callback: for when it's done + // + // ### Returns ### + // * error: string of the error + // * project: the json object representing the entire project + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289232) + this.getProject = function (project, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/project/' + project), + method: 'GET' + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid project.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during getProject.'); + return; + } + body = JSON.parse(body); + callback(null, body); + }); + }; + // ## Find all Rapid Views ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error: string of the error + // * rapidViews: array of views + this.getAllRapidViews = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/rapidview', 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); + return; + } + if (response.body !== null) { + var rapidViews = response.body.views; + callback(null, rapidViews); + } + }); + }; + // ## Find the Rapid View for a specified project ## + // ### Takes ### + // + // * projectName: name for the project + // * callback: for when it's done + // + // ### Returns ### + // * error: string of the error + // * rapidView: rapid view matching the projectName + /** + * Finds the Rapid View that belongs to a specified project. + * + * @param projectName + * @param callback + */ + this.findRapidView = function (projectName, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/rapidviews/list', 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); + return; + } + if (response.body !== null) { + var rapidViews = response.body.views; + var matchedViews = []; + for (var i = 0; i < rapidViews.length; i++) { + if (rapidViews[i].name.toLowerCase() === projectName.toLowerCase()) { + matchedViews.push(rapidViews[i]); + } + } + callback(null, matchedViews[0]); + } + }); + }; + /** + * Gets the Rapid View configuration. + * + * @param rapidViewId + * @param callback + */ + this.getRapidViewConfig = function (rapidViewId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/rapidviewconfig/editmodel?rapidViewId=' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); + return; + } + if (response.body) { + callback(null, response.body); + } + }); + }; + // ## Get a list of Active Sprints belonging to a Rapid View ## + // ### Takes ### + // + // * rapidViewId: the id for the rapid view + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string with the error + // * sprints: the ?array? of sprints + /** + * Returns a list of active sprints belonging to a Rapid View. + * + * @param rapidView ID + * @param callback + */ + this.getActiveSprintsForRapidView = function (rapidViewId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during sprints search.'); + return; + } + if (response.body) { + var sprints = response.body.sprints; + var activeSprints = []; + for (var i = 0; i < sprints.length; i++) { + if (sprints[i].state && sprints[i].state === 'ACTIVE') { + activeSprints.push(sprints[i]); + } + } + callback(null, activeSprints); + return; + } + }); + }; + // ## Get the last Sprint belonging to a Rapid View ## + // ### Takes ### + // + // * rapidViewId: the id for the rapid view + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string with the error + // * sprints: the ?array? of sprints + /** + * Returns last sprint belonging to a Rapid View. + * + * @param rapidView ID + * @param callback + */ + this.getLastSprintForRapidView = function (rapidViewId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during sprints search.'); + return; + } + if (response.body !== null) { + var sprints = response.body.sprints; + callback(null, sprints.pop()); + return; + } + }); + }; + // ## Get the issues for a rapidView / sprint## + // ### Takes ### + // + // * rapidViewId: the id for the rapid view + // * sprintId: the id for the sprint + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string with the error + // * results: the object with the issues and additional sprint information + /** + * Returns sprint and issues information + * + * @param rapidView ID + * @param sprint ID + * @param callback + */ + this.getSprintIssues = function (rapidViewId, sprintId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/rapid/charts/sprintreport?rapidViewId=' + rapidViewId + '&sprintId=' + sprintId, 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during sprints search'); + return; + } + if (response.body !== null) { + callback(null, response.body); + } else { + callback('No body'); + } + }); + }; + // ## Get all data for a rapidView + // ### Takes ### + // + // * rapidViewId: the id for the rapid view + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string with the error + // * results: the object with the issues and additional sprint information + /** + * Returns all data + * + * @param rapidView ID + * @param callback + */ + this.getAllRapidViewData = function (rapidViewId, sprintIds, callback) { + var sprints = ''; + if (sprintIds.length) { + sprintIds.forEach(function (id) { + sprints += ('&activeSprints=' + id); + }); + } + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/xboard/work/allData/?rapidViewId=' + rapidViewId + sprints, 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during sprints search'); + return; + } + if (response.body !== null) { + callback(null, response.body); + } else { + callback('No body'); + } + }); + }; + // ## Add an issue to the project's current sprint ## + // ### Takes ### + // + // * issueId: the id of the existing issue + // * sprintId: the id of the sprint to add it to + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string of the error + // + // + // **does this callback if there's success?** + /** + * Adds a given issue to a project's current sprint + * + * @param issueId + * @param sprintId + * @param callback + */ + this.addIssueToSprint = function (issueId, sprintId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/sprint/' + sprintId + '/issues/add', 'rest/greenhopper/', this.agileApiVersion), + method: 'PUT', + followAllRedirects: true, + json: true, + body: { + issueKeys: [issueId] + } + }; + logger.log(options.uri); + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 204) { + callback(response.statusCode + ': Unable to connect to JIRA to add to sprint.'); + return; + } + }); + }; + // ## Create an issue link between two issues ## + // ### Takes ### + // + // * link: a link object + // * callback: for when it's done + // + // ### Returns ### + // * error: string if there was an issue, null if success + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id296682) + /** + * Creates an issue link between two issues. Link should follow the below format: + * + * { + * 'linkType': 'Duplicate', + * 'fromIssueKey': 'HSP-1', + * 'toIssueKey': 'MKY-1', + * 'comment': { + * 'body': 'Linked related issue!', + * 'visibility': { + * 'type': 'GROUP', + * 'value': 'jira-users' + * } + * } + * } + * + * @param link + * @param errorCallback + * @param successCallback + */ + this.issueLink = function (link, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issueLink'), + method: 'POST', + followAllRedirects: true, + json: true, + body: link + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid project.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during issueLink.'); + return; + } + callback(null); + }); + }; + /** + * Retrieves the remote links associated with the given issue. + * + * @param issueNumber - The internal id or key of the issue + * @param callback + */ + this.getRemoteLinks = function getRemoteLinks(issueNumber, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNumber + '/remotelink'), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid issue number.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during request.'); + return; + } + callback(null, response.body); + }); + }; + /** + * Retrieves the remote links associated with the given issue. + * + * @param issueNumber - The internal id (not the issue key) of the issue + * @param callback + */ + this.createRemoteLink = function createRemoteLink(issueNumber, remoteLink, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNumber + '/remotelink'), + method: 'POST', + json: true, + body: remoteLink + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Cannot create remote link. Invalid issue.'); + return; + } + if (response.statusCode === 400) { + callback('Cannot create remote link. ' + response.body.errors.title); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during request.'); + return; + } + callback(null, response.body); + }); + }; + // ## Get Versions for a project ## + // ### Takes ### + // * project: A project key + // * callback: for when it's done + // + // ### Returns ### + // * error: a string with the error + // * versions: array of the versions for a product + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289653) + this.getVersions = function (project, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/project/' + project + '/versions'), + method: 'GET' + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid project.'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during getVersions.'); + return; + } + body = JSON.parse(body); + callback(null, body); + }); + }; + // ## Create a version ## + // ### Takes ### + // + // * version: an object of the new version + // * callback: for when it's done + // + // ### Returns ### + // + // * error: error text + // * version: should be the same version you passed up + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id288232) + // + /* { + * "description": "An excellent version", + * "name": "New Version 1", + * "archived": false, + * "released": true, + * "releaseDate": "2010-07-05", + * "userReleaseDate": "5/Jul/2010", + * "project": "PXA" + * } + */ + this.createVersion = function (version, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/version'), + method: 'POST', + followAllRedirects: true, + json: true, + body: version + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Version does not exist or the currently authenticated user does not have permission to view it'); + return; + } + if (response.statusCode === 403) { + callback('The currently authenticated user does not have permission to edit the version'); + return; + } + if (response.statusCode !== 201) { + callback(response.statusCode + ': Unable to connect to JIRA during createVersion.'); + return; + } + callback(null, body); + }); + }; + // ## Update a version ## + // ### Takes ### + // + // * version: an object of the new version + // * callback: for when it's done + // + // ### Returns ### + // + // * error: error text + // * version: should be the same version you passed up + // + // [Jira Doc](https://docs.atlassian.com/jira/REST/latest/#d2e510) + // + /* { + * "id": The ID of the version being updated. Required. + * "description": "An excellent version", + * "name": "New Version 1", + * "archived": false, + * "released": true, + * "releaseDate": "2010-07-05", + * "userReleaseDate": "5/Jul/2010", + * "project": "PXA" + * } + */ + this.updateVersion = function (version, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/version/' + version.id), + method: 'PUT', + followAllRedirects: true, + json: true, + body: version + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Version does not exist or the currently authenticated user does not have permission to view it'); + return; + } + if (response.statusCode === 403) { + callback('The currently authenticated user does not have permission to edit the version'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during updateVersion.'); + return; + } + callback(null, body); + }); + }; + // ## Pass a search query to Jira ## + // ### Takes ### + // + // * searchString: jira query string + // * optional: object containing any of the following properties + // * startAt: optional index number (default 0) + // * maxResults: optional max results number (default 50) + // * fields: optional array of desired fields, defaults when null: + // * "summary" + // * "status" + // * "assignee" + // * "description" + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string if there's an error + // * issues: array of issues for the user + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id333082) + // + this.searchJira = function (searchString, optional, callback) { + // backwards compatibility + optional = optional || {}; + if (Array.isArray(optional)) { + optional = { + fields: optional + }; + } + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/search'), + method: 'POST', + json: true, + followAllRedirects: true, + body: { + jql: searchString, + startAt: optional.startAt || 0, + maxResults: optional.maxResults || 50, + fields: optional.fields || ["summary", "status", "assignee", "description"] + } + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 400) { + callback('Problem with the JQL query'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during search.'); + return; + } + callback(null, body); + }); + }; + // ## Search user on Jira ## + // ### Takes ### + // + // username: A query string used to search username, name or e-mail address + // startAt: The index of the first user to return (0-based) + // maxResults: The maximum number of users to return (defaults to 50). + // includeActive: If true, then active users are included in the results (default true) + // includeInactive: If true, then inactive users are included in the results (default false) + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string if there's an error + // * users: array of users for the user + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#d2e3756) + // + this.searchUsers = function (username, startAt, maxResults, includeActive, includeInactive, callback) { + startAt = (startAt !== undefined) ? startAt : 0; + maxResults = (maxResults !== undefined) ? maxResults : 50; + includeActive = (includeActive !== undefined) ? includeActive : true; + includeInactive = (includeInactive !== undefined) ? includeInactive : false; + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/user/search?username=' + username + '&startAt=' + startAt + '&maxResults=' + maxResults + '&includeActive=' + includeActive + '&includeInactive=' + includeInactive), + method: 'GET', + json: true, + followAllRedirects: true + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 400) { + callback('Unable to search'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during search.'); + return; + } + callback(null, body); + }); + }; + // ## Get issues related to a user ## + // ### Takes ### + // + // * user: username of user to search for + // * open: `boolean` determines if only open issues should be returned + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string if there's an error + // * issues: array of issues for the user + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id296043) + // + this.getUsersIssues = function (username, open, callback) { + var jql = "assignee = " + username; + var openText = ' AND status in (Open, "In Progress", Reopened)'; + if (open) { + jql += openText; + } + this.searchJira(jql, {}, callback); + }; + // ## Add issue to Jira ## + // ### Takes ### + // + // * issue: Properly Formatted Issue + // * callback: for when it's done + // + // ### Returns ### + // * error object (check out the Jira Doc) + // * success object + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290028) + this.addNewIssue = function (issue, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue'), + method: 'POST', + followAllRedirects: true, + json: true, + body: issue + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 400) { + callback(body); + return; + } + if ((response.statusCode !== 200) && (response.statusCode !== 201)) { + callback(response.statusCode + ': Unable to connect to JIRA during search.'); + return; + } + callback(null, body); + }); + }; + // ## Delete issue to Jira ## + // ### Takes ### + // + // * issueId: the Id of the issue to delete + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success object + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290791) + this.deleteIssue = function (issueNum, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNum), + method: 'DELETE', + followAllRedirects: true, + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 204) { + callback(null, "Success"); + return; + } + callback(response.statusCode + ': Error while deleting'); + }); + }; + // ## Update issue in Jira ## + // ### Takes ### + // + // * issueId: the Id of the issue to delete + // * issueUpdate: update Object + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success string + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290878) + this.updateIssue = function (issueNum, issueUpdate, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNum), + body: issueUpdate, + method: 'PUT', + followAllRedirects: true, + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200 || response.statusCode === 204) { + callback(null, "Success"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List Components ## + // ### Takes ### + // + // * project: key for the project + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of components + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) + /* + * [{ + * "self": "http://localhostname:8090/jira/rest/api/2.0/component/1234", + * "id": "1234", + * "name": "name", + * "description": "Description.", + * "assigneeType": "PROJECT_DEFAULT", + * "assignee": { + * "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com", + * "name": "user@domain.com", + * "displayName": "SE Support", + * "active": true + * }, + * "realAssigneeType": "PROJECT_DEFAULT", + * "realAssignee": { + * "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com", + * "name": "user@domain.com", + * "displayName": "User name", + * "active": true + * }, + * "isAssigneeTypeValid": true + * }] + */ + this.listComponents = function (project, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/project/' + project + '/components'), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + if (response.statusCode === 404) { + callback("Project not found"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List listFields ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of priorities + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) + /* + * [{ + * "id": "field", + * "name": "Field", + * "custom": false, + * "orderable": true, + * "navigable": true, + * "searchable": true, + * "schema": { + * "type": "string", + * "system": "field" + * } + * }] + */ + this.listFields = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/field'), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + if (response.statusCode === 404) { + callback("Not found"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List listPriorities ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of priorities + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) + /* + * [{ + * "self": "http://localhostname:8090/jira/rest/api/2.0/priority/1", + * "statusColor": "#ff3300", + * "description": "Crashes, loss of data, severe memory leak.", + * "name": "Major", + * "id": "2" + * }] + */ + this.listPriorities = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/priority'), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + if (response.statusCode === 404) { + callback("Not found"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List Transitions ## + // ### Takes ### + // + // * issueId: get transitions available for the issue + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of transitions + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) + /* + * { + * "expand": "transitions", + * "transitions": [ + * { + * "id": "2", + * "name": "Close Issue", + * "to": { + * "self": "http://localhostname:8090/jira/rest/api/2.0/status/10000", + * "description": "The issue is currently being worked on.", + * "iconUrl": "http://localhostname:8090/jira/images/icons/progress.gif", + * "name": "In Progress", + * "id": "10000" + * }, + * "fields": { + * "summary": { + * "required": false, + * "schema": { + * "type": "array", + * "items": "option", + * "custom": "com.atlassian.jira.plugin.system.customfieldtypes:multiselect", + * "customId": 10001 + * }, + * "name": "My Multi Select", + * "operations": [ + * "set", + * "add" + * ], + * "allowedValues": [ + * "red", + * "blue" + * ] + * } + * } + * } + * ]} + */ + this.listTransitions = function (issueId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueId + '/transitions?expand=transitions.fields'), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body.transitions); + return; + } + if (response.statusCode === 404) { + callback("Issue not found"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## Transition issue in Jira ## + // ### Takes ### + // + // * issueId: the Id of the issue to delete + // * issueTransition: transition Object + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success string + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) + this.transitionIssue = function (issueNum, issueTransition, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueNum + '/transitions'), + body: issueTransition, + method: 'POST', + followAllRedirects: true, + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 204) { + callback(null, "Success"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List all Viewable Projects ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of projects + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289193) + /* + * Result items are in the format: + * { + * "self": "http://www.example.com/jira/rest/api/2/project/ABC", + * "id": "10001", + * "key": "ABC", + * "name": "Alphabetical", + * "avatarUrls": { + * "16x16": "http://www.example.com/jira/secure/projectavatar?size=small&pid=10001", + * "48x48": "http://www.example.com/jira/secure/projectavatar?size=large&pid=10001" + * } + * } + */ + this.listProjects = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/project'), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + if (response.statusCode === 500) { + callback(response.statusCode + ': Error while retrieving list.'); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## Add a comment to an issue ## + // ### Takes ### + // * issueId: Issue to add a comment to + // * comment: string containing comment + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success string + // + // [Jira Doc](https://docs.atlassian.com/jira/REST/latest/#id108798) + this.addComment = function (issueId, comment, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueId + '/comment'), + body: { + "body": comment + }, + method: 'POST', + followAllRedirects: true, + json: true + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 201) { + callback(null, "Success"); + return; + } + if (response.statusCode === 400) { + callback("Invalid Fields: " + JSON.stringify(body)); + return; + } + }); + }; + // ## Add a worklog to a project ## + // ### Takes ### + // * issueId: Issue to add a worklog to + // * worklog: worklog object + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success string + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id291617) + /* + * Worklog item is in the format: + * { + * "self": "http://www.example.com/jira/rest/api/2.0/issue/10010/worklog/10000", + * "author": { + * "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred", + * "name": "fred", + * "displayName": "Fred F. User", + * "active": false + * }, + * "updateAuthor": { + * "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred", + * "name": "fred", + * "displayName": "Fred F. User", + * "active": false + * }, + * "comment": "I did some work here.", + * "visibility": { + * "type": "group", + * "value": "jira-developers" + * }, + * "started": "2012-11-22T04:19:46.736-0600", + * "timeSpent": "3h 20m", + * "timeSpentSeconds": 12000, + * "id": "100028" + * } + */ + this.addWorklog = function (issueId, worklog, newEstimate, callback) { + if (typeof callback == 'undefined') { + callback = newEstimate + newEstimate = false + } + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issue/' + issueId + '/worklog' + (newEstimate ? "?adjustEstimate=new&newEstimate=" + newEstimate : "")), + body: worklog, + method: 'POST', + followAllRedirects: true, + json: true + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 201) { + callback(null, "Success"); + return; + } + if (response.statusCode === 400) { + callback("Invalid Fields: " + JSON.stringify(body)); + return; + } + if (response.statusCode === 403) { + callback("Insufficient Permissions"); + return; + } + callback(response.statusCode + ': Error while updating'); + }); + }; + // ## List all Issue Types ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of types + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id295946) + /* + * Result items are in the format: + * { + * "self": "http://localhostname:8090/jira/rest/api/2.0/issueType/3", + * "id": "3", + * "description": "A task that needs to be done.", + * "iconUrl": "http://localhostname:8090/jira/images/icons/task.gif", + * "name": "Task", + * "subtask": false + * } + */ + this.listIssueTypes = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/issuetype'), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + callback(response.statusCode + ': Error while retrieving issue types'); + }); + }; + // ## Register a webhook ## + // ### Takes ### + // + // * webhook: properly formatted webhook + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success object + // + // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) + /* + * Success object in the format: + * { + * name: 'my first webhook via rest', + * events: [], + * url: 'http://www.example.com/webhooks', + * filter: '', + * excludeIssueDetails: false, + * enabled: true, + * self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5', + * lastUpdatedUser: 'user', + * lastUpdatedDisplayName: 'User Name', + * lastUpdated: 1383247225784 + * } + */ + this.registerWebhook = function (webhook, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'), + method: 'POST', + json: true, + body: webhook + }; + this.request(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 201) { + callback(null, body); + return; + } + callback(response.statusCode + ': Error while registering new webhook'); + }); + }; + // ## List all registered webhooks ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * array of webhook objects + // + // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) + /* + * Webhook object in the format: + * { + * name: 'my first webhook via rest', + * events: [], + * url: 'http://www.example.com/webhooks', + * filter: '', + * excludeIssueDetails: false, + * enabled: true, + * self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5', + * lastUpdatedUser: 'user', + * lastUpdatedDisplayName: 'User Name', + * lastUpdated: 1383247225784 + * } + */ + this.listWebhooks = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'), + method: 'GET', + json: true + }; + this.request(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + callback(response.statusCode + ': Error while listing webhooks'); + }); + }; + // ## Get a webhook by its ID ## + // ### Takes ### + // + // * webhookID: id of webhook to get + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * webhook object + // + // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) + this.getWebhook = function (webhookID, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'), + method: 'GET', + json: true + }; + this.request(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + callback(response.statusCode + ': Error while getting webhook'); + }); + }; + // ## Delete a registered webhook ## + // ### Takes ### + // + // * webhookID: id of the webhook to delete + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * success string + // + // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) + this.deleteWebhook = function (webhookID, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'), + method: 'DELETE', + json: true + }; + this.request(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 204) { + callback(null, "Success"); + return; + } + callback(response.statusCode + ': Error while deleting webhook'); + }); + }; + // ## Describe the currently authenticated user ## + // ### Takes ### + // + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * user object + // + // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id2e865) + /* + * User object in the format: + * { + * self: 'http://localhost:8090/rest/api/latest/user?username=user', + * name: 'user', + * loginInfo: + * { + * failedLoginCount: 2, + * loginCount: 114, + * lastFailedLoginTime: '2013-10-29T13:33:26.702+0000', + * previousLoginTime: '2013-10-31T20:30:51.924+0000' + * } + * } + */ + this.getCurrentUser = function (callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/session', 'rest/auth/', '1'), + method: 'GET', + json: true + }; + this.request(options, function (error, response, body) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, body); + return; + } + callback(response.statusCode + ': Error while getting current user'); + }); + }; + // ## Retrieve the backlog of a certain Rapid View ## + // ### Takes ### + // * rapidViewId: rapid view id + // * callback: for when it's done + // + // ### Returns ### + // * error string + // * backlog object + /* + * Backlog item is in the format: + * { + * "sprintMarkersMigrated": true, + * "issues": [ + * { + * "id": 67890, + * "key": "KEY-1234", + * "summary": "Issue Summary", + * ... + * } + * ], + * "rankCustomFieldId": 12345, + * "sprints": [ + * { + * "id": 123, + * "name": "Sprint Name", + * "state": "FUTURE", + * ... + * } + * ], + * "supportsPages": true, + * "projects": [ + * { + * "id": 567, + * "key": "KEY", + * "name": "Project Name" + * } + * ], + * "epicData": { + * "epics": [ + * { + * "id": 9876, + * "key": "KEY-4554", + * "typeName": "Epic", + * ... + * } + * ], + * "canEditEpics": true, + * "supportsPages": true + * }, + * "canManageSprints": true, + * "maxIssuesExceeded": false, + * "queryResultLimit": 2147483647, + * "versionData": { + * "versionsPerProject": { + * "567": [ + * { + * "id": 8282, + * "name": "Version Name", + * ... + * } + * ] + * }, + * "canCreateVersion": true + * } + * } + */ + this.getBacklogForRapidView = function (rapidViewId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/xboard/plan/backlog/data?rapidViewId=' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 200) { + callback(null, response.body); + return; + } + callback(response.statusCode + ': Error while retrieving backlog'); + }); + }; +}).call(JiraApi.prototype); \ No newline at end of file From fe2de1225e6e50192d48d609a414133ac6d29eb6 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sat, 18 Oct 2014 22:19:01 -0400 Subject: [PATCH 08/21] add back call(JiraApi.prototype) so tests will pass; but now lint fails --- lib/jira.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jira.js b/lib/jira.js index a933950d..cea9f846 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -1711,4 +1711,4 @@ JiraApi = exports.JiraApi = function (protocol, host, port, username, password, callback(response.statusCode + ': Error while retrieving backlog'); }); }; -}(JiraApi.prototype)); \ No newline at end of file +}).call(JiraApi.prototype); \ No newline at end of file From 231a530ad6a5dcae5623a3c719bec1a6d2f75fab Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sat, 18 Oct 2014 22:21:57 -0400 Subject: [PATCH 09/21] updates to documentation --- lib/jira.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/jira.js b/lib/jira.js index cea9f846..9a98acd7 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -290,14 +290,14 @@ JiraApi = exports.JiraApi = function (protocol, host, port, username, password, callback(null, body); }); }; - // ## Find all Rapid Views ## + // ## List all Rapid Views ## // ### Takes ### // // * callback: for when it's done // // ### Returns ### // * error: string of the error - // * rapidViews: array of views + // * array of rapidViews this.getAllRapidViews = function (callback) { var options = { rejectUnauthorized: this.strictSSL, From a603c5418f9cf6573ddeb0a95c7c74ca3ac2f023 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sun, 19 Oct 2014 19:27:46 -0400 Subject: [PATCH 10/21] formatting docs --- lib/jira.js | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/jira.js b/lib/jira.js index 9a98acd7..fe0d4806 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -67,6 +67,7 @@ // * Adding a new version // * Pulling unresolved issues count for a specific version // * Rapid Views +// * Get all Rapid Views // * Find based on project name // * Get the latest Green Hopper sprint // * Gets attached issues @@ -98,20 +99,14 @@ // ## Changelog ## // // -// * _0.9.0 Add OAuth Support and New Estimates on addWorklog (thanks to -// [nagyv](https://github.com/nagyv))_ -// * _0.8.2 Fix URL Format Issues (thanks to -// [eduardolundgren](https://github.com/eduardolundgren))_ -// * _0.8.1 Expanding the transitions options (thanks to -// [eduardolundgren](https://github.com/eduardolundgren))_ -// * _0.8.0 Ability to search users (thanks to -// [eduardolundgren](https://github.com/eduardolundgren))_ -// * _0.7.2 Allows HTTP Code 204 on issue update edit (thanks to -// [eduardolundgren](https://github.com/eduardolundgren))_ -// * _0.7.1 Check if body variable is undef (thanks to -// [AlexCline](https://github.com/AlexCline))_ -// * _0.7.0 Adds list priorities, list fields, and project components (thanks to -// [eduardolundgren](https://github.com/eduardolundgren))_ +// * _0.10.0 Additional methods for Rapid Views (thanks to [adamgruber](http://github.com/adamgruber))_ +// * _0.9.0 Add OAuth Support and New Estimates on addWorklog (thanks to [nagyv](https://github.com/nagyv))_ +// * _0.8.2 Fix URL Format Issues (thanks to [eduardolundgren](https://github.com/eduardolundgren))_ +// * _0.8.1 Expanding the transitions options (thanks to [eduardolundgren](https://github.com/eduardolundgren))_ +// * _0.8.0 Ability to search users (thanks to [eduardolundgren](https://github.com/eduardolundgren))_ +// * _0.7.2 Allows HTTP Code 204 on issue update edit (thanks to [eduardolundgren](https://github.com/eduardolundgren))_ +// * _0.7.1 Check if body variable is undef (thanks to [AlexCline](https://github.com/AlexCline))_ +// * _0.7.0 Adds list priorities, list fields, and project components (thanks to [eduardolundgren](https://github.com/eduardolundgren))_ // * _0.6.0 Comment API implemented (thanks to [StevenMcD](https://github.com/StevenMcD))_ // * _0.5.0 Last param is now for strict SSL checking, defaults to true_ // * _0.4.1 Now handing errors in the request callback (thanks [mrbrookman](https://github.com/mrbrookman))_ From b8e605cc69ca085903ba235604ef2f5dc2b53942 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sun, 19 Oct 2014 19:28:34 -0400 Subject: [PATCH 11/21] Add getRapidViewConfig method Returns configuration object for specified rapid view --- lib/jira.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/lib/jira.js b/lib/jira.js index fe0d4806..d0b4d9ee 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -69,6 +69,7 @@ // * Rapid Views // * Get all Rapid Views // * Find based on project name +// * Get the configuration for a Rapid View // * Get the latest Green Hopper sprint // * Gets attached issues // * Issues @@ -366,6 +367,46 @@ JiraApi = exports.JiraApi = function (protocol, host, port, username, password, } }); }; + // ## Get the Configuration for a specified Rapid View ## + // ### Takes ### + // + // * rapidViewId: id of the rapid view + // * callback: for when it's done + // + // ### Returns ### + // * error: string of the error + // * config: rapid view configuration object + /** + * Finds the Rapid View that belongs to a specified project. + * + * @param projectName + * @param callback + */ + this.getRapidViewConfig = function (rapidViewId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/rapidviewconfig/editmodel?rapidViewId=' + rapidViewId, 'rest/greenhopper/', '1.0'), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); + return; + } + if (response.body) { + callback(null, response.body); + } + }); + }; // ## Get a list of Sprints belonging to a Rapid View ## // ### Takes ### // From 3b1463f6cf9f107543f93d488320dbb7187881bb Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sun, 19 Oct 2014 19:52:26 -0400 Subject: [PATCH 12/21] test for getRapidViewConfig --- spec/jira.spec.coffee | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/spec/jira.spec.coffee b/spec/jira.spec.coffee index 716d0c9d..af6c5a25 100644 --- a/spec/jira.spec.coffee +++ b/spec/jira.spec.coffee @@ -4,9 +4,10 @@ rewire = require 'rewire' nodeJira = rewire '../lib/jira' describe "Node Jira Tests", -> - makeUrl = (path, altBase) -> + makeUrl = (path, altBase, altGreenhopper) -> base = 'rest/api/2/' base = 'rest/greenhopper/2/' if altBase? + base = 'rest/greenhopper/1.0/' if altGreenhopper? decodeURIComponent( url.format protocol: 'http:' @@ -167,6 +168,36 @@ describe "Node Jira Tests", -> expect(@cb).toHaveBeenCalledWith null, name: 'ABC' + it "Gets the configuration for a Rapid View", -> + options = + rejectUnauthorized: true + uri: makeUrl("rapidviewconfig/editmodel?rapidViewId=1", null, true) + method: 'GET' + json: true + auth: + user: 'test' + pass: 'test' + + @jira.getRapidViewConfig '1', @cb + expect(@jira.request) + .toHaveBeenCalledWith options, jasmine.any(Function) + + # Invalid URL + @jira.request.mostRecentCall.args[1] null, statusCode:404, null + expect(@cb).toHaveBeenCalledWith 'Invalid URL' + + @jira.request.mostRecentCall.args[1] null, statusCode:401, null + expect(@cb).toHaveBeenCalledWith( + '401: Unable to connect to JIRA during rapidView search.') + + # Successful Request + @jira.request.mostRecentCall.args[1] null, + statusCode:200, + body: + id: 1 + + expect(@cb).toHaveBeenCalledWith null, id: 1 + it "Gets the last sprint for a Rapid View", -> options = rejectUnauthorized: true From 11edba525718ee9b1c4d4738af93e1f2cf992494 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sun, 19 Oct 2014 19:56:09 -0400 Subject: [PATCH 13/21] return after callback for consistency --- Gruntfile.js | 4 +- docs/jira.html | 245 ++++++++++++++++++++++++++++++++++--------------- lib/jira.js | 2 + 3 files changed, 176 insertions(+), 75 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index d0e72fb2..233454f1 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -9,7 +9,7 @@ module.exports = function (grunt) { forceExit: false, extensions: 'coffee', jUnit: { - report: false, + report: true, savePath : "./build/reports/jasmine/", useDotNotation: true, consolidate: true @@ -27,7 +27,7 @@ module.exports = function (grunt) { }, jslint: { client: { - src: ['./Gruntfile.js', 'lib/**/*.js'], + src: ['./Gruntfile.js', 'lib/**/jira.js'], directives: { indent: 2, curly: true, diff --git a/docs/jira.html b/docs/jira.html index c73489d5..c274acbc 100644 --- a/docs/jira.html +++ b/docs/jira.html @@ -102,7 +102,9 @@

    Implemented APIs

  • Rapid Views
      +
    • Get all Rapid Views
    • Find based on project name
    • +
    • Get the configuration for a Rapid View
    • Get the latest Green Hopper sprint
    • Gets attached issues
    @@ -142,20 +144,14 @@

    TO-DO

    Changelog

      -
    • 0.9.0 Add OAuth Support and New Estimates on addWorklog (thanks to -nagyv)
    • -
    • _0.8.2 Fix URL Format Issues (thanks to
       [eduardolundgren](https://github.com/eduardolundgren))_
      -
    • -
    • _0.8.1 Expanding the transitions options (thanks to
       [eduardolundgren](https://github.com/eduardolundgren))_
      -
    • -
    • _0.8.0 Ability to search users (thanks to
       [eduardolundgren](https://github.com/eduardolundgren))_
      -
    • -
    • _0.7.2 Allows HTTP Code 204 on issue update edit (thanks to
       [eduardolundgren](https://github.com/eduardolundgren))_
      -
    • -
    • _0.7.1 Check if body variable is undef (thanks to
       [AlexCline](https://github.com/AlexCline))_
      -
    • -
    • _0.7.0 Adds list priorities, list fields, and project components (thanks to
       [eduardolundgren](https://github.com/eduardolundgren))_
      -
    • +
    • 0.10.0 Additional methods for Rapid Views (thanks to adamgruber)
    • +
    • 0.9.0 Add OAuth Support and New Estimates on addWorklog (thanks to nagyv)
    • +
    • 0.8.2 Fix URL Format Issues (thanks to eduardolundgren)
    • +
    • 0.8.1 Expanding the transitions options (thanks to eduardolundgren)
    • +
    • 0.8.0 Ability to search users (thanks to eduardolundgren)
    • +
    • 0.7.2 Allows HTTP Code 204 on issue update edit (thanks to eduardolundgren)
    • +
    • 0.7.1 Check if body variable is undef (thanks to AlexCline)
    • +
    • 0.7.0 Adds list priorities, list fields, and project components (thanks to eduardolundgren)
    • 0.6.0 Comment API implemented (thanks to StevenMcD)
    • 0.5.0 Last param is now for strict SSL checking, defaults to true
    • 0.4.1 Now handing errors in the request callback (thanks mrbrookman)
    • @@ -428,6 +424,54 @@

      Returns

      +

      List all Rapid Views

      +

      Takes

      +
        +
      • callback: for when it’s done
      • +
      +

      Returns

      +
        +
      • error: string of the error
      • +
      • array of rapidViews
      • +
      + + + +
        this.getAllRapidViews = function (callback) {
      +    var options = {
      +      rejectUnauthorized: this.strictSSL,
      +      uri: this.makeUri('/rapidview', 'rest/greenhopper/', '1.0'),
      +      method: 'GET',
      +      json: true
      +    };
      +    this.doRequest(options, function (error, response) {
      +      if (error) {
      +        callback(error, null);
      +        return;
      +      }
      +      if (response.statusCode === 404) {
      +        callback('Invalid URL');
      +        return;
      +      }
      +      if (response.statusCode !== 200) {
      +        callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.');
      +        return;
      +      }
      +      if (response.body !== null) {
      +        callback(null, response.body.views);
      +      }
      +    });
      +  };
      + + + + +
    • +
      + +
      + +

      Find the Rapid View for a specified project

      Takes

        @@ -485,11 +529,66 @@

        Returns

        -
      • +
      • - + +
        +

        Get the Configuration for a specified Rapid View

        +

        Takes

        +
          +
        • rapidViewId: id of the rapid view
        • +
        • callback: for when it’s done
        • +
        +

        Returns

        +
          +
        • error: string of the error
        • +
        • config: rapid view configuration object
        • +
        + +
        + +
          /**
        +   * Finds the Rapid View that belongs to a specified project.
        +   *
        +   * @param projectName
        +   * @param callback
        +   */
        +  this.getRapidViewConfig = function (rapidViewId, callback) {
        +    var options = {
        +      rejectUnauthorized: this.strictSSL,
        +      uri: this.makeUri('/rapidviewconfig/editmodel?rapidViewId=' + rapidViewId, 'rest/greenhopper/', '1.0'),
        +      method: 'GET',
        +      json: true
        +    };
        +    this.doRequest(options, function (error, response) {
        +      if (error) {
        +        callback(error, null);
        +        return;
        +      }
        +      if (response.statusCode === 404) {
        +        callback('Invalid URL');
        +        return;
        +      }
        +      if (response.statusCode !== 200) {
        +        callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.');
        +        return;
        +      }
        +      if (response.body) {
        +        callback(null, response.body);
        +      }
        +    });
        +  };
        + +
      • + + +
      • +
        + +
        +

        Get a list of Sprints belonging to a Rapid View

        Takes

        @@ -542,11 +641,11 @@

        Returns

      • -
      • +
      • - +

        Get the issues for a rapidView / sprint

        Takes

        @@ -601,11 +700,11 @@

        Returns

      • -
      • +
      • - +

        Add an issue to the project’s current sprint

        Takes

        @@ -660,11 +759,11 @@

        Returns

      • -
      • +
      • - +

        Takes

        @@ -791,11 +890,11 @@

        Returns

      • -
      • +
      • - +

        Get Versions for a project

        Takes

        @@ -839,11 +938,11 @@

        Returns

      • -
      • +
      • - +

        Create a version

        Takes

        @@ -903,11 +1002,11 @@

        Returns

      • -
      • +
      • - +

        Update a version

        Takes

        @@ -968,11 +1067,11 @@

        Returns

      • -
      • +
      • - +

        Pass a search query to Jira

        Takes

        @@ -1006,11 +1105,11 @@

        Returns

      • -
      • +
      • - +

        backwards compatibility

        @@ -1055,11 +1154,11 @@

        Returns

      • -
      • +
      • - +

        Search user on Jira

        Takes

        @@ -1112,11 +1211,11 @@

        Returns

      • -
      • +
      • - +

        Takes

        @@ -1149,11 +1248,11 @@

        Returns

      • -
      • +
      • - +

        Add issue to Jira

        Takes

        @@ -1199,11 +1298,11 @@

        Returns

      • -
      • +
      • - +

        Delete issue to Jira

        Takes

        @@ -1244,11 +1343,11 @@

        Returns

      • -
      • +
      • - +

        Update issue in Jira

        Takes

        @@ -1291,11 +1390,11 @@

        Returns

      • -
      • +
      • - +

        List Components

        Takes

        @@ -1362,11 +1461,11 @@

        Returns

      • -
      • +
      • - +

        List listFields

        Takes

        @@ -1423,11 +1522,11 @@

        Returns

      • -
      • +
      • - +

        List listPriorities

        Takes

        @@ -1479,11 +1578,11 @@

        Returns

      • -
      • +
      • - +

        List Transitions

        Takes

        @@ -1564,11 +1663,11 @@

        Returns

      • -
      • +
      • - +

        Transition issue in Jira

        Takes

        @@ -1611,11 +1710,11 @@

        Returns

      • -
      • +
      • - +

        List all Viewable Projects

        Takes

        @@ -1671,11 +1770,11 @@

        Returns

      • -
      • +
      • - +

        Add a comment to an issue

        Takes

        @@ -1724,11 +1823,11 @@

        Returns

      • -
      • +
      • - +

        Add a worklog to a project

        Takes

        @@ -1810,11 +1909,11 @@

        Returns

      • -
      • +
      • - +

        List all Issue Types

        Takes

        @@ -1864,11 +1963,11 @@

        Returns

      • -
      • +
      • - +

        Register a webhook

        Takes

        @@ -1924,11 +2023,11 @@

        Returns

      • -
      • +
      • - +

        List all registered webhooks

        Takes

        @@ -1982,11 +2081,11 @@

        Returns

      • -
      • +
      • - +

        Get a webhook by its ID

        Takes

        @@ -2026,11 +2125,11 @@

        Returns

      • -
      • +
      • - +

        Delete a registered webhook

        Takes

        @@ -2070,11 +2169,11 @@

        Returns

      • -
      • +
      • - +

        Describe the currently authenticated user

        Takes

        @@ -2127,11 +2226,11 @@

        Returns

      • -
      • +
      • - +

        Retrieve the backlog of a certain Rapid View

        Takes

        @@ -2224,7 +2323,7 @@

        Returns

        callback(response.statusCode + ': Error while retrieving backlog'); }); }; -}(JiraApi.prototype));
      +}).call(JiraApi.prototype);
    • diff --git a/lib/jira.js b/lib/jira.js index d0b4d9ee..c56188bd 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -316,6 +316,7 @@ JiraApi = exports.JiraApi = function (protocol, host, port, username, password, } if (response.body !== null) { callback(null, response.body.views); + return; } }); }; @@ -404,6 +405,7 @@ JiraApi = exports.JiraApi = function (protocol, host, port, username, password, } if (response.body) { callback(null, response.body); + return; } }); }; From 5595da3dcf1e2f56f0167bc64b2e348edf594ad6 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Sun, 19 Oct 2014 20:48:28 -0400 Subject: [PATCH 14/21] Add getActiveSprintsForRapidView method --- lib/jira.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/jira.js b/lib/jira.js index c56188bd..d0cf84ab 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -70,6 +70,7 @@ // * Get all Rapid Views // * Find based on project name // * Get the configuration for a Rapid View +// * Get all active Green Hopper sprints // * Get the latest Green Hopper sprint // * Gets attached issues // * Issues @@ -409,6 +410,56 @@ JiraApi = exports.JiraApi = function (protocol, host, port, username, password, } }); }; + // ## Get a list of Active Sprints belonging to a Rapid View ## + // ### Takes ### + // + // * rapidViewId: the id for the rapid view + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string with the error + // * sprints: the ?array? of sprints + /** + * Returns a list of active sprints belonging to a Rapid View. + * + * @param rapidView ID + * @param callback + */ + this.getActiveSprintsForRapidView = function (rapidViewId, callback) { + var options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/', '1.0'), + method: 'GET', + json: true + }; + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during sprints search.'); + return; + } + if (response.body) { + var sprints = response.body.sprints, + activeSprints = [], + i; + for (i = 0; i < sprints.length; i++) { + if (sprints[i].state && sprints[i].state === 'ACTIVE') { + activeSprints.push(sprints[i]); + } + } + callback(null, activeSprints); + return; + } + }); + }; // ## Get a list of Sprints belonging to a Rapid View ## // ### Takes ### // From 046c64f5786d4fb8fd92e641e519d58dd3ac1e98 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Mon, 20 Oct 2014 12:09:06 -0400 Subject: [PATCH 15/21] add test for getActiveSprintsForRapidView method --- spec/jira.spec.coffee | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/spec/jira.spec.coffee b/spec/jira.spec.coffee index af6c5a25..a1647c72 100644 --- a/spec/jira.spec.coffee +++ b/spec/jira.spec.coffee @@ -198,6 +198,35 @@ describe "Node Jira Tests", -> expect(@cb).toHaveBeenCalledWith null, id: 1 + it "Gets all active sprints for a Rapid View", -> + options = + rejectUnauthorized: true + uri: makeUrl("sprintquery/1", null, true) + method: 'GET' + json: true + auth: + user: 'test' + pass: 'test' + + @jira.getActiveSprintsForRapidView 1, @cb + expect(@jira.request).toHaveBeenCalledWith options, jasmine.any(Function) + + # Invalid URL + @jira.request.mostRecentCall.args[1] null, statusCode:404, null + expect(@cb).toHaveBeenCalledWith 'Invalid URL' + + @jira.request.mostRecentCall.args[1] null, statusCode:401, null + expect(@cb).toHaveBeenCalledWith( + '401: Unable to connect to JIRA during sprints search.') + + # Successful Request + @jira.request.mostRecentCall.args[1] null, + statusCode:200, + body: + sprints: [state: 'ACTIVE'] + + expect(@cb).toHaveBeenCalledWith null, [state: 'ACTIVE'] + it "Gets the last sprint for a Rapid View", -> options = rejectUnauthorized: true From 4e9eac4b7083e94fe98b0cdbcf7a15766450ad3c Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Mon, 20 Oct 2014 12:48:32 -0400 Subject: [PATCH 16/21] add getAllRapidViewData method --- lib/jira.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/lib/jira.js b/lib/jira.js index d0cf84ab..9b7a1f37 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -410,6 +410,61 @@ JiraApi = exports.JiraApi = function (protocol, host, port, username, password, } }); }; + // ## Get all data for a rapidView, optionally filtered by specific sprints + // ### Takes ### + // + // * rapidViewId: the id for the rapid view + // * sprintIds: array of active sprints to filter by + // * callback: for when it's done + // + // ### Returns ### + // + // * error: string with the error + // * results: the object with the issues and additional sprint information + /** + * Returns all data + * + * @param rapidView ID + * @param sprint IDs + * @param callback + */ + this.getAllRapidViewData = function (rapidViewId, sprintIds, callback) { + var sprints = '', + options; + if (Array.isArray(sprintIds) && sprintIds.length) { + sprintIds.forEach(function (id) { + sprints += ('&activeSprints=' + id); + }); + } else if (typeof sprintIds === 'function') { + callback = sprintIds; + } + + options = { + rejectUnauthorized: this.strictSSL, + uri: this.makeUri('/xboard/work/allData/?rapidViewId=' + rapidViewId + sprints, 'rest/greenhopper/', '1.0'), + method: 'GET', + json: true + }; + + this.doRequest(options, function (error, response) { + if (error) { + callback(error, null); + return; + } + if (response.statusCode === 404) { + callback('Invalid URL'); + return; + } + if (response.statusCode !== 200) { + callback(response.statusCode + ': Unable to connect to JIRA during sprints search.'); + return; + } + if (response.body !== null) { + callback(null, response.body); + return; + } + }); + }; // ## Get a list of Active Sprints belonging to a Rapid View ## // ### Takes ### // From 43e05bca23f2a3008554d6fb5bdc2c34f5e913f9 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Mon, 20 Oct 2014 12:48:51 -0400 Subject: [PATCH 17/21] tests for getAllRapidViewData method --- spec/jira.spec.coffee | 58 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/spec/jira.spec.coffee b/spec/jira.spec.coffee index a1647c72..c60e895f 100644 --- a/spec/jira.spec.coffee +++ b/spec/jira.spec.coffee @@ -198,6 +198,64 @@ describe "Node Jira Tests", -> expect(@cb).toHaveBeenCalledWith null, id: 1 + it "Gets all Rapid View data", -> + options = + rejectUnauthorized: true + uri: makeUrl("xboard/work/allData/?rapidViewId=1", null, true) + method: 'GET' + json: true + auth: + user: 'test' + pass: 'test' + + @jira.getAllRapidViewData 1, @cb + expect(@jira.request).toHaveBeenCalledWith options, jasmine.any(Function) + + # Invalid URL + @jira.request.mostRecentCall.args[1] null, statusCode:404, null + expect(@cb).toHaveBeenCalledWith('Invalid URL') + + @jira.request.mostRecentCall.args[1] null, statusCode:401, null + expect(@cb).toHaveBeenCalledWith( + '401: Unable to connect to JIRA during sprints search.') + + # Successful Request + @jira.request.mostRecentCall.args[1] null, + statusCode:200, + body: + rapidViewId: 1 + + expect(@cb).toHaveBeenCalledWith null, rapidViewId: 1 + + it "Gets all Rapid View data, limited to specified sprint", -> + options = + rejectUnauthorized: true + uri: makeUrl("xboard/work/allData/?rapidViewId=1&activeSprints=2", null, true) + method: 'GET' + json: true + auth: + user: 'test' + pass: 'test' + + @jira.getAllRapidViewData 1, [2], @cb + expect(@jira.request).toHaveBeenCalledWith options, jasmine.any(Function) + + # Invalid URL + @jira.request.mostRecentCall.args[1] null, statusCode:404, null + expect(@cb).toHaveBeenCalledWith('Invalid URL') + + @jira.request.mostRecentCall.args[1] null, statusCode:401, null + expect(@cb).toHaveBeenCalledWith( + '401: Unable to connect to JIRA during sprints search.') + + # Successful Request + @jira.request.mostRecentCall.args[1] null, + statusCode:200, + body: + rapidViewId: 1 + + expect(@cb).toHaveBeenCalledWith null, rapidViewId: 1 + it "Gets all active sprints for a Rapid View", -> options = rejectUnauthorized: true From f894c03061c45922265c6285859a82af8f7ee6d5 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Mon, 20 Oct 2014 12:50:53 -0400 Subject: [PATCH 18/21] updated docs --- lib/jira.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/jira.js b/lib/jira.js index 9b7a1f37..ded1eccf 100644 --- a/lib/jira.js +++ b/lib/jira.js @@ -70,6 +70,7 @@ // * Get all Rapid Views // * Find based on project name // * Get the configuration for a Rapid View +// * Get all data for a Rapid View // * Get all active Green Hopper sprints // * Get the latest Green Hopper sprint // * Gets attached issues @@ -414,7 +415,7 @@ JiraApi = exports.JiraApi = function (protocol, host, port, username, password, // ### Takes ### // // * rapidViewId: the id for the rapid view - // * sprintIds: array of active sprints to filter by + // * sprintIds: array of active sprints to filter by // * callback: for when it's done // // ### Returns ### From e9873dff655daffe91d49d6ac057dc55e0f5bbb6 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Mon, 20 Oct 2014 13:10:10 -0400 Subject: [PATCH 19/21] remove unused file --- lib/newjira.js | 1850 ------------------------------------------------ 1 file changed, 1850 deletions(-) delete mode 100644 lib/newjira.js diff --git a/lib/newjira.js b/lib/newjira.js deleted file mode 100644 index ec6aac58..00000000 --- a/lib/newjira.js +++ /dev/null @@ -1,1850 +0,0 @@ -// # JavaScript JIRA API for node.js # -// -// [![Build Status](https://travis-ci.org/steves/node-jira.png?branch=master)](https://travis-ci.org/steves/node-jira) -// -// A node.js module, which provides an object oriented wrapper for the JIRA REST API. -// -// This library is built to support version `2.0.alpha1` of the JIRA REST API. -// This library is also tested with version `2` of the JIRA REST API -// It has been noted that with Jira OnDemand, `2.0.alpha1` does not work, devs -// should revert to `2`. If this changes, please notify us. -// -// JIRA REST API documentation can be found [here](http://docs.atlassian.com/jira/REST/latest/) -// -// ## Installation ## -// -// Install with the node package manager [npm](http://npmjs.org): -// -// $ npm install jira -// -// or -// -// Install via git clone: -// -// $ git clone git://github.com/steves/node-jira.git -// $ cd node-jira -// $ npm install -// -// ## Example ## -// -// Find the status of an issue. -// -// JiraApi = require('jira').JiraApi; -// -// var jira = new JiraApi('https', config.host, config.port, config.user, config.password, '2.0.alpha1'); -// jira.findIssue(issueNumber, function(error, issue) { -// console.log('Status: ' + issue.fields.status.name); -// }); -// -// Currently there is no explicit login call necessary as each API call uses Basic Authentication to authenticate. -// -// ## Options ## -// -// JiraApi options: -// * `protocol`: Typically 'http:' or 'https:' -// * `host`: The hostname for your jira server -// * `port`: The port your jira server is listening on (probably `80` or `443`) -// * `user`: The username to log in with -// * `password`: Keep it secret, keep it safe -// * `Jira API Version`: Known to work with `2` and `2.0.alpha1` -// * `verbose`: Log some info to the console, usually for debugging -// * `strictSSL`: Set to false if you have self-signed certs or something non-trustworthy -// * `oauth`: A disctionary of `consumer_key`, `consumer_secret`, `access_token` and `access_token_secret` to be used for OAuth authentication. -// -// ## Implemented APIs ## -// -// * Authentication -// * HTTP -// * OAuth -// * Projects -// * Pulling a project -// * List all projects viewable to the user -// * List Components -// * List Fields -// * List Priorities -// * Versions -// * Pulling versions -// * Adding a new version -// * Updating a version -// * Pulling unresolved issues count for a specific version -// * Rapid Views -// * Find based on project name -// * Get the latest Green Hopper sprint -// * Gets attached issues -// * Issues -// * Add a new issue -// * Update an issue -// * Transition an issue -// * Pulling an issue -// * Issue linking -// * Add an issue to a sprint -// * Get a users issues (open or all) -// * List issue types -// * Search using jql -// * Set Max Results -// * Set Start-At parameter for results -// * Add a worklog -// * Add new estimate for worklog -// * Add a comment -// * Transitions -// * List -// * Users -// * Search -// -// ## TODO ## -// -// * Refactor currently implemented APIs to be more Object Oriented -// * Refactor to make use of built-in node.js events and classes -// -// ## Changelog ## -// -// -// * _0.10.0 Add Update Version (thanks to [floralvikings](https://github.com/floralvikings))_ -// * _0.9.0 Add OAuth Support and New Estimates on addWorklog (thanks to -// [nagyv](https://github.com/nagyv))_ -// * _0.8.2 Fix URL Format Issues (thanks to -// [eduardolundgren](https://github.com/eduardolundgren))_ -// * _0.8.1 Expanding the transitions options (thanks to -// [eduardolundgren](https://github.com/eduardolundgren))_ -// * _0.8.0 Ability to search users (thanks to -// [eduardolundgren](https://github.com/eduardolundgren))_ -// * _0.7.2 Allows HTTP Code 204 on issue update edit (thanks to -// [eduardolundgren](https://github.com/eduardolundgren))_ -// * _0.7.1 Check if body variable is undef (thanks to -// [AlexCline](https://github.com/AlexCline))_ -// * _0.7.0 Adds list priorities, list fields, and project components (thanks to -// [eduardolundgren](https://github.com/eduardolundgren))_ -// * _0.6.0 Comment API implemented (thanks to [StevenMcD](https://github.com/StevenMcD))_ -// * _0.5.0 Last param is now for strict SSL checking, defaults to true_ -// * _0.4.1 Now handing errors in the request callback (thanks [mrbrookman](https://github.com/mrbrookman))_ -// * _0.4.0 Now auto-redirecting between http and https (for both GET and POST)_ -// * _0.3.1 [Request](https://github.com/mikeal/request) is broken, setting max request package at 2.15.0_ -// * _0.3.0 Now Gets Issues for a Rapidview/Sprint (thanks [donbonifacio](https://github.com/donbonifacio))_ -// * _0.2.0 Now allowing startAt and MaxResults to be passed to searchJira, -// switching to semantic versioning._ -// * _0.1.0 Using Basic Auth instead of cookies, all calls unit tested, URI -// creation refactored_ -// * _0.0.6 Now linting, preparing to refactor_ -// * _0.0.5 JQL search now takes a list of fields_ -// * _0.0.4 Added jql search_ -// * _0.0.3 Added APIs and Docco documentation_ -// * _0.0.2 Initial version_ -var url = require('url'), - logger = console, - OAuth = require("oauth"); -var JiraApi = exports.JiraApi = function (protocol, host, port, username, password, apiVersion, agileApiVersion, verbose, strictSSL, oauth) { - this.protocol = protocol; - this.host = host; - this.port = port; - this.username = username; - this.password = password; - this.apiVersion = apiVersion; - this.agileApiVersion = agileApiVersion; - // Default strictSSL to true (previous behavior) but now allow it to be - // modified - if (strictSSL == null) { - strictSSL = true; - } - this.strictSSL = strictSSL; - // This is so we can fake during unit tests - this.request = require('request'); - if (verbose !== true) { - logger = { - log: function () {} - }; - } - // This is the same almost every time, refactored to make changing it - // later, easier - this.makeUri = function (pathname, altBase, altApiVersion) { - var basePath = 'rest/api/'; - if (altBase != null) { - basePath = altBase; - } - var apiVersion = this.apiVersion; - if (altApiVersion != null) { - apiVersion = altApiVersion; - } - var uri = url.format({ - protocol: this.protocol, - hostname: this.host, - port: this.port, - pathname: basePath + apiVersion + pathname - }); - return decodeURIComponent(uri); - }; - this.doRequest = function (options, callback) { - if (oauth && oauth.consumer_key && oauth.consumer_secret) { - options.oauth = { - consumer_key: oauth.consumer_key, - consumer_secret: oauth.consumer_secret, - token: oauth.access_token, - token_secret: oauth.access_token_secret - }; - } else { - options.auth = { - 'user': this.username, - 'pass': this.password - }; - } - this.request(options, callback); - }; -}; -(function () { - // ## Find an issue in jira ## - // ### Takes ### - // - // * issueNumber: the issueNumber to find - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string of the error - // * issue: an object of the issue - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290709) - this.findIssue = function (issueNumber, params, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNumber), - method: 'GET' - }; - if (params) { - options.uri += ('?fields=' + params); - } - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid issue number.'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.'); - return; - } - if (body === undefined) { - callback('Response body was undefined.'); - return; - } - callback(null, JSON.parse(body)); - }); - }; - // ## Get the unresolved issue count ## - // ### Takes ### - // - // * version: version of your product that you want issues against - // * callback: function for when it's done - // - // ### Returns ### - // * error: string with the error code - // * count: count of unresolved issues for requested version - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id288524) - this.getUnresolvedIssueCount = function (version, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/version/' + version + '/unresolvedIssueCount'), - method: 'GET' - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid version.'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during findIssueStatus.'); - return; - } - body = JSON.parse(body); - callback(null, body.issuesUnresolvedCount); - }); - }; - // ## Get the Project by project key ## - // ### Takes ### - // - // * project: key for the project - // * callback: for when it's done - // - // ### Returns ### - // * error: string of the error - // * project: the json object representing the entire project - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289232) - this.getProject = function (project, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/project/' + project), - method: 'GET' - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid project.'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during getProject.'); - return; - } - body = JSON.parse(body); - callback(null, body); - }); - }; - // ## Find all Rapid Views ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error: string of the error - // * rapidViews: array of views - this.getAllRapidViews = function (callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapidview', 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); - return; - } - if (response.body !== null) { - var rapidViews = response.body.views; - callback(null, rapidViews); - } - }); - }; - // ## Find the Rapid View for a specified project ## - // ### Takes ### - // - // * projectName: name for the project - // * callback: for when it's done - // - // ### Returns ### - // * error: string of the error - // * rapidView: rapid view matching the projectName - /** - * Finds the Rapid View that belongs to a specified project. - * - * @param projectName - * @param callback - */ - this.findRapidView = function (projectName, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapidviews/list', 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); - return; - } - if (response.body !== null) { - var rapidViews = response.body.views; - var matchedViews = []; - for (var i = 0; i < rapidViews.length; i++) { - if (rapidViews[i].name.toLowerCase() === projectName.toLowerCase()) { - matchedViews.push(rapidViews[i]); - } - } - callback(null, matchedViews[0]); - } - }); - }; - /** - * Gets the Rapid View configuration. - * - * @param rapidViewId - * @param callback - */ - this.getRapidViewConfig = function (rapidViewId, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapidviewconfig/editmodel?rapidViewId=' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during rapidView search.'); - return; - } - if (response.body) { - callback(null, response.body); - } - }); - }; - // ## Get a list of Active Sprints belonging to a Rapid View ## - // ### Takes ### - // - // * rapidViewId: the id for the rapid view - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string with the error - // * sprints: the ?array? of sprints - /** - * Returns a list of active sprints belonging to a Rapid View. - * - * @param rapidView ID - * @param callback - */ - this.getActiveSprintsForRapidView = function (rapidViewId, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during sprints search.'); - return; - } - if (response.body) { - var sprints = response.body.sprints; - var activeSprints = []; - for (var i = 0; i < sprints.length; i++) { - if (sprints[i].state && sprints[i].state === 'ACTIVE') { - activeSprints.push(sprints[i]); - } - } - callback(null, activeSprints); - return; - } - }); - }; - // ## Get the last Sprint belonging to a Rapid View ## - // ### Takes ### - // - // * rapidViewId: the id for the rapid view - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string with the error - // * sprints: the ?array? of sprints - /** - * Returns last sprint belonging to a Rapid View. - * - * @param rapidView ID - * @param callback - */ - this.getLastSprintForRapidView = function (rapidViewId, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during sprints search.'); - return; - } - if (response.body !== null) { - var sprints = response.body.sprints; - callback(null, sprints.pop()); - return; - } - }); - }; - // ## Get the issues for a rapidView / sprint## - // ### Takes ### - // - // * rapidViewId: the id for the rapid view - // * sprintId: the id for the sprint - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string with the error - // * results: the object with the issues and additional sprint information - /** - * Returns sprint and issues information - * - * @param rapidView ID - * @param sprint ID - * @param callback - */ - this.getSprintIssues = function (rapidViewId, sprintId, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/rapid/charts/sprintreport?rapidViewId=' + rapidViewId + '&sprintId=' + sprintId, 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during sprints search'); - return; - } - if (response.body !== null) { - callback(null, response.body); - } else { - callback('No body'); - } - }); - }; - // ## Get all data for a rapidView - // ### Takes ### - // - // * rapidViewId: the id for the rapid view - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string with the error - // * results: the object with the issues and additional sprint information - /** - * Returns all data - * - * @param rapidView ID - * @param callback - */ - this.getAllRapidViewData = function (rapidViewId, sprintIds, callback) { - var sprints = ''; - if (sprintIds.length) { - sprintIds.forEach(function (id) { - sprints += ('&activeSprints=' + id); - }); - } - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/xboard/work/allData/?rapidViewId=' + rapidViewId + sprints, 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during sprints search'); - return; - } - if (response.body !== null) { - callback(null, response.body); - } else { - callback('No body'); - } - }); - }; - // ## Add an issue to the project's current sprint ## - // ### Takes ### - // - // * issueId: the id of the existing issue - // * sprintId: the id of the sprint to add it to - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string of the error - // - // - // **does this callback if there's success?** - /** - * Adds a given issue to a project's current sprint - * - * @param issueId - * @param sprintId - * @param callback - */ - this.addIssueToSprint = function (issueId, sprintId, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/sprint/' + sprintId + '/issues/add', 'rest/greenhopper/', this.agileApiVersion), - method: 'PUT', - followAllRedirects: true, - json: true, - body: { - issueKeys: [issueId] - } - }; - logger.log(options.uri); - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid URL'); - return; - } - if (response.statusCode !== 204) { - callback(response.statusCode + ': Unable to connect to JIRA to add to sprint.'); - return; - } - }); - }; - // ## Create an issue link between two issues ## - // ### Takes ### - // - // * link: a link object - // * callback: for when it's done - // - // ### Returns ### - // * error: string if there was an issue, null if success - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id296682) - /** - * Creates an issue link between two issues. Link should follow the below format: - * - * { - * 'linkType': 'Duplicate', - * 'fromIssueKey': 'HSP-1', - * 'toIssueKey': 'MKY-1', - * 'comment': { - * 'body': 'Linked related issue!', - * 'visibility': { - * 'type': 'GROUP', - * 'value': 'jira-users' - * } - * } - * } - * - * @param link - * @param errorCallback - * @param successCallback - */ - this.issueLink = function (link, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issueLink'), - method: 'POST', - followAllRedirects: true, - json: true, - body: link - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid project.'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during issueLink.'); - return; - } - callback(null); - }); - }; - /** - * Retrieves the remote links associated with the given issue. - * - * @param issueNumber - The internal id or key of the issue - * @param callback - */ - this.getRemoteLinks = function getRemoteLinks(issueNumber, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNumber + '/remotelink'), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid issue number.'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during request.'); - return; - } - callback(null, response.body); - }); - }; - /** - * Retrieves the remote links associated with the given issue. - * - * @param issueNumber - The internal id (not the issue key) of the issue - * @param callback - */ - this.createRemoteLink = function createRemoteLink(issueNumber, remoteLink, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNumber + '/remotelink'), - method: 'POST', - json: true, - body: remoteLink - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Cannot create remote link. Invalid issue.'); - return; - } - if (response.statusCode === 400) { - callback('Cannot create remote link. ' + response.body.errors.title); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during request.'); - return; - } - callback(null, response.body); - }); - }; - // ## Get Versions for a project ## - // ### Takes ### - // * project: A project key - // * callback: for when it's done - // - // ### Returns ### - // * error: a string with the error - // * versions: array of the versions for a product - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289653) - this.getVersions = function (project, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/project/' + project + '/versions'), - method: 'GET' - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Invalid project.'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during getVersions.'); - return; - } - body = JSON.parse(body); - callback(null, body); - }); - }; - // ## Create a version ## - // ### Takes ### - // - // * version: an object of the new version - // * callback: for when it's done - // - // ### Returns ### - // - // * error: error text - // * version: should be the same version you passed up - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id288232) - // - /* { - * "description": "An excellent version", - * "name": "New Version 1", - * "archived": false, - * "released": true, - * "releaseDate": "2010-07-05", - * "userReleaseDate": "5/Jul/2010", - * "project": "PXA" - * } - */ - this.createVersion = function (version, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/version'), - method: 'POST', - followAllRedirects: true, - json: true, - body: version - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Version does not exist or the currently authenticated user does not have permission to view it'); - return; - } - if (response.statusCode === 403) { - callback('The currently authenticated user does not have permission to edit the version'); - return; - } - if (response.statusCode !== 201) { - callback(response.statusCode + ': Unable to connect to JIRA during createVersion.'); - return; - } - callback(null, body); - }); - }; - // ## Update a version ## - // ### Takes ### - // - // * version: an object of the new version - // * callback: for when it's done - // - // ### Returns ### - // - // * error: error text - // * version: should be the same version you passed up - // - // [Jira Doc](https://docs.atlassian.com/jira/REST/latest/#d2e510) - // - /* { - * "id": The ID of the version being updated. Required. - * "description": "An excellent version", - * "name": "New Version 1", - * "archived": false, - * "released": true, - * "releaseDate": "2010-07-05", - * "userReleaseDate": "5/Jul/2010", - * "project": "PXA" - * } - */ - this.updateVersion = function (version, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/version/' + version.id), - method: 'PUT', - followAllRedirects: true, - json: true, - body: version - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 404) { - callback('Version does not exist or the currently authenticated user does not have permission to view it'); - return; - } - if (response.statusCode === 403) { - callback('The currently authenticated user does not have permission to edit the version'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during updateVersion.'); - return; - } - callback(null, body); - }); - }; - // ## Pass a search query to Jira ## - // ### Takes ### - // - // * searchString: jira query string - // * optional: object containing any of the following properties - // * startAt: optional index number (default 0) - // * maxResults: optional max results number (default 50) - // * fields: optional array of desired fields, defaults when null: - // * "summary" - // * "status" - // * "assignee" - // * "description" - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string if there's an error - // * issues: array of issues for the user - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id333082) - // - this.searchJira = function (searchString, optional, callback) { - // backwards compatibility - optional = optional || {}; - if (Array.isArray(optional)) { - optional = { - fields: optional - }; - } - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/search'), - method: 'POST', - json: true, - followAllRedirects: true, - body: { - jql: searchString, - startAt: optional.startAt || 0, - maxResults: optional.maxResults || 50, - fields: optional.fields || ["summary", "status", "assignee", "description"] - } - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 400) { - callback('Problem with the JQL query'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during search.'); - return; - } - callback(null, body); - }); - }; - // ## Search user on Jira ## - // ### Takes ### - // - // username: A query string used to search username, name or e-mail address - // startAt: The index of the first user to return (0-based) - // maxResults: The maximum number of users to return (defaults to 50). - // includeActive: If true, then active users are included in the results (default true) - // includeInactive: If true, then inactive users are included in the results (default false) - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string if there's an error - // * users: array of users for the user - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#d2e3756) - // - this.searchUsers = function (username, startAt, maxResults, includeActive, includeInactive, callback) { - startAt = (startAt !== undefined) ? startAt : 0; - maxResults = (maxResults !== undefined) ? maxResults : 50; - includeActive = (includeActive !== undefined) ? includeActive : true; - includeInactive = (includeInactive !== undefined) ? includeInactive : false; - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/user/search?username=' + username + '&startAt=' + startAt + '&maxResults=' + maxResults + '&includeActive=' + includeActive + '&includeInactive=' + includeInactive), - method: 'GET', - json: true, - followAllRedirects: true - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 400) { - callback('Unable to search'); - return; - } - if (response.statusCode !== 200) { - callback(response.statusCode + ': Unable to connect to JIRA during search.'); - return; - } - callback(null, body); - }); - }; - // ## Get issues related to a user ## - // ### Takes ### - // - // * user: username of user to search for - // * open: `boolean` determines if only open issues should be returned - // * callback: for when it's done - // - // ### Returns ### - // - // * error: string if there's an error - // * issues: array of issues for the user - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id296043) - // - this.getUsersIssues = function (username, open, callback) { - var jql = "assignee = " + username; - var openText = ' AND status in (Open, "In Progress", Reopened)'; - if (open) { - jql += openText; - } - this.searchJira(jql, {}, callback); - }; - // ## Add issue to Jira ## - // ### Takes ### - // - // * issue: Properly Formatted Issue - // * callback: for when it's done - // - // ### Returns ### - // * error object (check out the Jira Doc) - // * success object - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290028) - this.addNewIssue = function (issue, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue'), - method: 'POST', - followAllRedirects: true, - json: true, - body: issue - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 400) { - callback(body); - return; - } - if ((response.statusCode !== 200) && (response.statusCode !== 201)) { - callback(response.statusCode + ': Unable to connect to JIRA during search.'); - return; - } - callback(null, body); - }); - }; - // ## Delete issue to Jira ## - // ### Takes ### - // - // * issueId: the Id of the issue to delete - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success object - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290791) - this.deleteIssue = function (issueNum, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNum), - method: 'DELETE', - followAllRedirects: true, - json: true - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 204) { - callback(null, "Success"); - return; - } - callback(response.statusCode + ': Error while deleting'); - }); - }; - // ## Update issue in Jira ## - // ### Takes ### - // - // * issueId: the Id of the issue to delete - // * issueUpdate: update Object - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success string - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290878) - this.updateIssue = function (issueNum, issueUpdate, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNum), - body: issueUpdate, - method: 'PUT', - followAllRedirects: true, - json: true - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 200 || response.statusCode === 204) { - callback(null, "Success"); - return; - } - callback(response.statusCode + ': Error while updating'); - }); - }; - // ## List Components ## - // ### Takes ### - // - // * project: key for the project - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of components - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) - /* - * [{ - * "self": "http://localhostname:8090/jira/rest/api/2.0/component/1234", - * "id": "1234", - * "name": "name", - * "description": "Description.", - * "assigneeType": "PROJECT_DEFAULT", - * "assignee": { - * "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com", - * "name": "user@domain.com", - * "displayName": "SE Support", - * "active": true - * }, - * "realAssigneeType": "PROJECT_DEFAULT", - * "realAssignee": { - * "self": "http://localhostname:8090/jira/rest/api/2.0/user?username=user@domain.com", - * "name": "user@domain.com", - * "displayName": "User name", - * "active": true - * }, - * "isAssigneeTypeValid": true - * }] - */ - this.listComponents = function (project, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/project/' + project + '/components'), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 200) { - callback(null, body); - return; - } - if (response.statusCode === 404) { - callback("Project not found"); - return; - } - callback(response.statusCode + ': Error while updating'); - }); - }; - // ## List listFields ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of priorities - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) - /* - * [{ - * "id": "field", - * "name": "Field", - * "custom": false, - * "orderable": true, - * "navigable": true, - * "searchable": true, - * "schema": { - * "type": "string", - * "system": "field" - * } - * }] - */ - this.listFields = function (callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/field'), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 200) { - callback(null, body); - return; - } - if (response.statusCode === 404) { - callback("Not found"); - return; - } - callback(response.statusCode + ': Error while updating'); - }); - }; - // ## List listPriorities ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of priorities - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) - /* - * [{ - * "self": "http://localhostname:8090/jira/rest/api/2.0/priority/1", - * "statusColor": "#ff3300", - * "description": "Crashes, loss of data, severe memory leak.", - * "name": "Major", - * "id": "2" - * }] - */ - this.listPriorities = function (callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/priority'), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 200) { - callback(null, body); - return; - } - if (response.statusCode === 404) { - callback("Not found"); - return; - } - callback(response.statusCode + ': Error while updating'); - }); - }; - // ## List Transitions ## - // ### Takes ### - // - // * issueId: get transitions available for the issue - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of transitions - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) - /* - * { - * "expand": "transitions", - * "transitions": [ - * { - * "id": "2", - * "name": "Close Issue", - * "to": { - * "self": "http://localhostname:8090/jira/rest/api/2.0/status/10000", - * "description": "The issue is currently being worked on.", - * "iconUrl": "http://localhostname:8090/jira/images/icons/progress.gif", - * "name": "In Progress", - * "id": "10000" - * }, - * "fields": { - * "summary": { - * "required": false, - * "schema": { - * "type": "array", - * "items": "option", - * "custom": "com.atlassian.jira.plugin.system.customfieldtypes:multiselect", - * "customId": 10001 - * }, - * "name": "My Multi Select", - * "operations": [ - * "set", - * "add" - * ], - * "allowedValues": [ - * "red", - * "blue" - * ] - * } - * } - * } - * ]} - */ - this.listTransitions = function (issueId, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueId + '/transitions?expand=transitions.fields'), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 200) { - callback(null, body.transitions); - return; - } - if (response.statusCode === 404) { - callback("Issue not found"); - return; - } - callback(response.statusCode + ': Error while updating'); - }); - }; - // ## Transition issue in Jira ## - // ### Takes ### - // - // * issueId: the Id of the issue to delete - // * issueTransition: transition Object - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success string - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id290489) - this.transitionIssue = function (issueNum, issueTransition, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueNum + '/transitions'), - body: issueTransition, - method: 'POST', - followAllRedirects: true, - json: true - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 204) { - callback(null, "Success"); - return; - } - callback(response.statusCode + ': Error while updating'); - }); - }; - // ## List all Viewable Projects ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of projects - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id289193) - /* - * Result items are in the format: - * { - * "self": "http://www.example.com/jira/rest/api/2/project/ABC", - * "id": "10001", - * "key": "ABC", - * "name": "Alphabetical", - * "avatarUrls": { - * "16x16": "http://www.example.com/jira/secure/projectavatar?size=small&pid=10001", - * "48x48": "http://www.example.com/jira/secure/projectavatar?size=large&pid=10001" - * } - * } - */ - this.listProjects = function (callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/project'), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 200) { - callback(null, body); - return; - } - if (response.statusCode === 500) { - callback(response.statusCode + ': Error while retrieving list.'); - return; - } - callback(response.statusCode + ': Error while updating'); - }); - }; - // ## Add a comment to an issue ## - // ### Takes ### - // * issueId: Issue to add a comment to - // * comment: string containing comment - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success string - // - // [Jira Doc](https://docs.atlassian.com/jira/REST/latest/#id108798) - this.addComment = function (issueId, comment, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueId + '/comment'), - body: { - "body": comment - }, - method: 'POST', - followAllRedirects: true, - json: true - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 201) { - callback(null, "Success"); - return; - } - if (response.statusCode === 400) { - callback("Invalid Fields: " + JSON.stringify(body)); - return; - } - }); - }; - // ## Add a worklog to a project ## - // ### Takes ### - // * issueId: Issue to add a worklog to - // * worklog: worklog object - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success string - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id291617) - /* - * Worklog item is in the format: - * { - * "self": "http://www.example.com/jira/rest/api/2.0/issue/10010/worklog/10000", - * "author": { - * "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred", - * "name": "fred", - * "displayName": "Fred F. User", - * "active": false - * }, - * "updateAuthor": { - * "self": "http://www.example.com/jira/rest/api/2.0/user?username=fred", - * "name": "fred", - * "displayName": "Fred F. User", - * "active": false - * }, - * "comment": "I did some work here.", - * "visibility": { - * "type": "group", - * "value": "jira-developers" - * }, - * "started": "2012-11-22T04:19:46.736-0600", - * "timeSpent": "3h 20m", - * "timeSpentSeconds": 12000, - * "id": "100028" - * } - */ - this.addWorklog = function (issueId, worklog, newEstimate, callback) { - if (typeof callback == 'undefined') { - callback = newEstimate - newEstimate = false - } - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issue/' + issueId + '/worklog' + (newEstimate ? "?adjustEstimate=new&newEstimate=" + newEstimate : "")), - body: worklog, - method: 'POST', - followAllRedirects: true, - json: true - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 201) { - callback(null, "Success"); - return; - } - if (response.statusCode === 400) { - callback("Invalid Fields: " + JSON.stringify(body)); - return; - } - if (response.statusCode === 403) { - callback("Insufficient Permissions"); - return; - } - callback(response.statusCode + ': Error while updating'); - }); - }; - // ## List all Issue Types ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of types - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id295946) - /* - * Result items are in the format: - * { - * "self": "http://localhostname:8090/jira/rest/api/2.0/issueType/3", - * "id": "3", - * "description": "A task that needs to be done.", - * "iconUrl": "http://localhostname:8090/jira/images/icons/task.gif", - * "name": "Task", - * "subtask": false - * } - */ - this.listIssueTypes = function (callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/issuetype'), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 200) { - callback(null, body); - return; - } - callback(response.statusCode + ': Error while retrieving issue types'); - }); - }; - // ## Register a webhook ## - // ### Takes ### - // - // * webhook: properly formatted webhook - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success object - // - // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) - /* - * Success object in the format: - * { - * name: 'my first webhook via rest', - * events: [], - * url: 'http://www.example.com/webhooks', - * filter: '', - * excludeIssueDetails: false, - * enabled: true, - * self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5', - * lastUpdatedUser: 'user', - * lastUpdatedDisplayName: 'User Name', - * lastUpdated: 1383247225784 - * } - */ - this.registerWebhook = function (webhook, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'), - method: 'POST', - json: true, - body: webhook - }; - this.request(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 201) { - callback(null, body); - return; - } - callback(response.statusCode + ': Error while registering new webhook'); - }); - }; - // ## List all registered webhooks ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * array of webhook objects - // - // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) - /* - * Webhook object in the format: - * { - * name: 'my first webhook via rest', - * events: [], - * url: 'http://www.example.com/webhooks', - * filter: '', - * excludeIssueDetails: false, - * enabled: true, - * self: 'http://localhost:8090/rest/webhooks/1.0/webhook/5', - * lastUpdatedUser: 'user', - * lastUpdatedDisplayName: 'User Name', - * lastUpdated: 1383247225784 - * } - */ - this.listWebhooks = function (callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/webhook', 'rest/webhooks/', '1.0'), - method: 'GET', - json: true - }; - this.request(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 200) { - callback(null, body); - return; - } - callback(response.statusCode + ': Error while listing webhooks'); - }); - }; - // ## Get a webhook by its ID ## - // ### Takes ### - // - // * webhookID: id of webhook to get - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * webhook object - // - // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) - this.getWebhook = function (webhookID, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'), - method: 'GET', - json: true - }; - this.request(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 200) { - callback(null, body); - return; - } - callback(response.statusCode + ': Error while getting webhook'); - }); - }; - // ## Delete a registered webhook ## - // ### Takes ### - // - // * webhookID: id of the webhook to delete - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * success string - // - // [Jira Doc](https://developer.atlassian.com/display/JIRADEV/JIRA+Webhooks+Overview) - this.deleteWebhook = function (webhookID, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/webhook/' + webhookID, 'rest/webhooks/', '1.0'), - method: 'DELETE', - json: true - }; - this.request(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 204) { - callback(null, "Success"); - return; - } - callback(response.statusCode + ': Error while deleting webhook'); - }); - }; - // ## Describe the currently authenticated user ## - // ### Takes ### - // - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * user object - // - // [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#id2e865) - /* - * User object in the format: - * { - * self: 'http://localhost:8090/rest/api/latest/user?username=user', - * name: 'user', - * loginInfo: - * { - * failedLoginCount: 2, - * loginCount: 114, - * lastFailedLoginTime: '2013-10-29T13:33:26.702+0000', - * previousLoginTime: '2013-10-31T20:30:51.924+0000' - * } - * } - */ - this.getCurrentUser = function (callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/session', 'rest/auth/', '1'), - method: 'GET', - json: true - }; - this.request(options, function (error, response, body) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 200) { - callback(null, body); - return; - } - callback(response.statusCode + ': Error while getting current user'); - }); - }; - // ## Retrieve the backlog of a certain Rapid View ## - // ### Takes ### - // * rapidViewId: rapid view id - // * callback: for when it's done - // - // ### Returns ### - // * error string - // * backlog object - /* - * Backlog item is in the format: - * { - * "sprintMarkersMigrated": true, - * "issues": [ - * { - * "id": 67890, - * "key": "KEY-1234", - * "summary": "Issue Summary", - * ... - * } - * ], - * "rankCustomFieldId": 12345, - * "sprints": [ - * { - * "id": 123, - * "name": "Sprint Name", - * "state": "FUTURE", - * ... - * } - * ], - * "supportsPages": true, - * "projects": [ - * { - * "id": 567, - * "key": "KEY", - * "name": "Project Name" - * } - * ], - * "epicData": { - * "epics": [ - * { - * "id": 9876, - * "key": "KEY-4554", - * "typeName": "Epic", - * ... - * } - * ], - * "canEditEpics": true, - * "supportsPages": true - * }, - * "canManageSprints": true, - * "maxIssuesExceeded": false, - * "queryResultLimit": 2147483647, - * "versionData": { - * "versionsPerProject": { - * "567": [ - * { - * "id": 8282, - * "name": "Version Name", - * ... - * } - * ] - * }, - * "canCreateVersion": true - * } - * } - */ - this.getBacklogForRapidView = function (rapidViewId, callback) { - var options = { - rejectUnauthorized: this.strictSSL, - uri: this.makeUri('/xboard/plan/backlog/data?rapidViewId=' + rapidViewId, 'rest/greenhopper/', this.agileApiVersion), - method: 'GET', - json: true - }; - this.doRequest(options, function (error, response) { - if (error) { - callback(error, null); - return; - } - if (response.statusCode === 200) { - callback(null, response.body); - return; - } - callback(response.statusCode + ': Error while retrieving backlog'); - }); - }; -}).call(JiraApi.prototype); \ No newline at end of file From 41d0351c6e304f730fc60dee07ed4f8cb1fd4ad7 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Mon, 20 Oct 2014 13:15:59 -0400 Subject: [PATCH 20/21] update documentation --- README.md | 93 +++++------------ docs/jira.html | 270 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 216 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index 951c87c3..3335fd34 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A node.js module, which provides an object oriented wrapper for the JIRA REST API. This library is built to support version `2.0.alpha1` of the JIRA REST API. -This library is also tested with version `2` of the JIRA REST API. +This library is also tested with version `2` of the JIRA REST API It has been noted that with Jira OnDemand, `2.0.alpha1` does not work, devs should revert to `2`. If this changes, please notify us. @@ -25,53 +25,18 @@ or $ cd node-jira $ npm install -## Examples ## +## Example ## -### Create the JIRA client ### +Find the status of an issue. JiraApi = require('jira').JiraApi; - - var jira = new JiraApi('https', config.host, config.port, config.user, config.password, '2.0.alpha1'); - -### Find the status of an issue ### + var jira = new JiraApi('https', config.host, config.port, config.user, config.password, '2.0.alpha1'); jira.findIssue(issueNumber, function(error, issue) { console.log('Status: ' + issue.fields.status.name); }); - -Currently there is no explicit login call necessary as each API call uses Basic Authentication to authenticate. - -### Get an issue remote links ### - -Returns an array of remote links data. - - jira.getRemoteLinks(issueKey, function(err, links) { - if (!err) { - console.log(issueKey + ' has ' + links.length + ' web links'); - } - }); - -### Create a remote link on an issue ### - -Returns an array of remote links data. - - // create a web link to a GitHub issue - var linkData = { - "object": { - "url" : "https://github.com/steves/node-jira/issues/1", - "title": "Add getVersions and createVersion functions", - "icon" : { - "url16x16": "https://github.com/favicon.ico" - } - } - }; - - jira.createRemoteLink(issueKey, linkData, function (err, link) { - - }); - -More information can be found by checking [JIRA Developer documentation](https://developer.atlassian.com/display/JIRADEV/JIRA+REST+API+for+Remote+Issue+Links#JIRARESTAPIforRemoteIssueLinks-CreatingLinks). +Currently there is no explicit login call necessary as each API call uses Basic Authentication to authenticate. ## Options ## @@ -84,12 +49,13 @@ JiraApi options: * `Jira API Version`: Known to work with `2` and `2.0.alpha1` * `verbose`: Log some info to the console, usually for debugging * `strictSSL`: Set to false if you have self-signed certs or something non-trustworthy +* `oauth`: A dictionary of `consumer_key`, `consumer_secret`, `access_token` and `access_token_secret` to be used for OAuth authentication. ## Implemented APIs ## * Authentication - * HTTP - * OAuth + * HTTP + * OAuth * Projects * Pulling a project * List all projects viewable to the user @@ -101,7 +67,11 @@ JiraApi options: * Adding a new version * Pulling unresolved issues count for a specific version * Rapid Views + * Get all Rapid Views * Find based on project name + * Get the configuration for a Rapid View + * Get all data for a Rapid View + * Get all active Green Hopper sprints * Get the latest Green Hopper sprint * Gets attached issues * Issues @@ -119,51 +89,34 @@ JiraApi options: * Add a worklog * Add new estimate for worklog * Add a comment - * Remote links (aka Web Links) - * Create a remote link on an issue - * Get all remote links of an issue * Transitions * List * Users * Search -## TODO ## - +## TO-DO ## * Refactor currently implemented APIs to be more Object Oriented * Refactor to make use of built-in node.js events and classes ## Changelog ## - - -* _0.9.2 Smaller fixes and features added_ - * proper doRequest usage (thanks to [ndamnjanovic](https://github.com/ndamnjanovic)) - * Support for @ in usernames (thanks to [ryanplasma](https://github.com/ryanplasma)) - * Handling empty responses in getIssue +* _0.10.0 Additional methods for Rapid Views (thanks to [adamgruber](http://github.com/adamgruber))_ * _0.9.0 Add OAuth Support and New Estimates on addWorklog (thanks to [nagyv](https://github.com/nagyv))_ -* _0.8.2 Fix URL Format Issues (thanks to - [eduardolundgren](https://github.com/eduardolundgren))_ -* _0.8.1 Expanding the transitions options (thanks to - [eduardolundgren](https://github.com/eduardolundgren))_ -* _0.8.0 Ability to search users (thanks to - [eduardolundgren](https://github.com/eduardolundgren))_ -* _0.7.2 Allows HTTP Code 204 on issue update edit (thanks to - [eduardolundgren](https://github.com/eduardolundgren))_ -* _0.7.1 Check if body variable is undef (thanks to - [AlexCline](https://github.com/AlexCline))_ -* _0.7.0 Adds list priorities, list fields, and project components (thanks to - [eduardolundgren](https://github.com/eduardolundgren))_ +* _0.8.2 Fix URL Format Issues (thanks to [eduardolundgren](https://github.com/eduardolundgren))_ +* _0.8.1 Expanding the transitions options (thanks to [eduardolundgren](https://github.com/eduardolundgren))_ +* _0.8.0 Ability to search users (thanks to [eduardolundgren](https://github.com/eduardolundgren))_ +* _0.7.2 Allows HTTP Code 204 on issue update edit (thanks to [eduardolundgren](https://github.com/eduardolundgren))_ +* _0.7.1 Check if body variable is undef (thanks to [AlexCline](https://github.com/AlexCline))_ +* _0.7.0 Adds list priorities, list fields, and project components (thanks to [eduardolundgren](https://github.com/eduardolundgren))_ * _0.6.0 Comment API implemented (thanks to [StevenMcD](https://github.com/StevenMcD))_ * _0.5.0 Last param is now for strict SSL checking, defaults to true_ * _0.4.1 Now handing errors in the request callback (thanks [mrbrookman](https://github.com/mrbrookman))_ * _0.4.0 Now auto-redirecting between http and https (for both GET and POST)_ * _0.3.1 [Request](https://github.com/mikeal/request) is broken, setting max request package at 2.15.0_ * _0.3.0 Now Gets Issues for a Rapidview/Sprint (thanks [donbonifacio](https://github.com/donbonifacio))_ -* _0.2.0 Now allowing startAt and MaxResults to be passed to searchJira, - switching to semantic versioning._ -* _0.1.0 Using Basic Auth instead of cookies, all calls unit tested, URI - creation refactored_ +* _0.2.0 Now allowing startAt and MaxResults to be passed to searchJira, switching to semantic versioning._ +* _0.1.0 Using Basic Auth instead of cookies, all calls unit tested, URI creation refactored_ * _0.0.6 Now linting, preparing to refactor_ * _0.0.5 JQL search now takes a list of fields_ * _0.0.4 Added jql search_ * _0.0.3 Added APIs and Docco documentation_ -* _0.0.2 Initial version_ +* _0.0.2 Initial version_ \ No newline at end of file diff --git a/docs/jira.html b/docs/jira.html index c274acbc..0554cdeb 100644 --- a/docs/jira.html +++ b/docs/jira.html @@ -11,27 +11,6 @@
      - -
        @@ -105,6 +84,8 @@

        Implemented APIs

      • Get all Rapid Views
      • Find based on project name
      • Get the configuration for a Rapid View
      • +
      • Get all data for a Rapid View
      • +
      • Get all active Green Hopper sprints
      • Get the latest Green Hopper sprint
      • Gets attached issues
      @@ -459,6 +440,7 @@

      Returns

      } if (response.body !== null) { callback(null, response.body.views); + return; } }); };
      @@ -577,6 +559,7 @@

      Returns

      } if (response.body) { callback(null, response.body); + return; } }); }; @@ -590,6 +573,139 @@

      Returns

      +

      Get all data for a rapidView, optionally filtered by specific sprints

      +

      Takes

      +
        +
      • rapidViewId: the id for the rapid view
      • +
      • sprintIds: array of active sprints to filter by
      • +
      • callback: for when it’s done
      • +
      +

      Returns

      +
        +
      • error: string with the error
      • +
      • results: the object with the issues and additional sprint information
      • +
      + + + +
        /**
      +   * Returns all data
      +   *
      +   * @param rapidView ID
      +   * @param sprint IDs
      +   * @param callback
      +   */
      +  this.getAllRapidViewData = function (rapidViewId, sprintIds, callback) {
      +    var sprints = '',
      +      options;
      +    if (Array.isArray(sprintIds) && sprintIds.length) {
      +      sprintIds.forEach(function (id) {
      +        sprints += ('&activeSprints=' + id);
      +      });
      +    } else if (typeof sprintIds === 'function') {
      +      callback = sprintIds;
      +    }
      +
      +    options = {
      +      rejectUnauthorized: this.strictSSL,
      +      uri: this.makeUri('/xboard/work/allData/?rapidViewId=' + rapidViewId + sprints, 'rest/greenhopper/', '1.0'),
      +      method: 'GET',
      +      json: true
      +    };
      +
      +    this.doRequest(options, function (error, response) {
      +      if (error) {
      +        callback(error, null);
      +        return;
      +      }
      +      if (response.statusCode === 404) {
      +        callback('Invalid URL');
      +        return;
      +      }
      +      if (response.statusCode !== 200) {
      +        callback(response.statusCode + ': Unable to connect to JIRA during sprints search.');
      +        return;
      +      }
      +      if (response.body !== null) {
      +        callback(null, response.body);
      +        return;
      +      }
      +    });
      +  };
      + + + + +
    • +
      + +
      + +
      +

      Get a list of Active Sprints belonging to a Rapid View

      +

      Takes

      +
        +
      • rapidViewId: the id for the rapid view
      • +
      • callback: for when it’s done
      • +
      +

      Returns

      +
        +
      • error: string with the error
      • +
      • sprints: the ?array? of sprints
      • +
      + +
      + +
        /**
      +   * Returns a list of active sprints belonging to a Rapid View.
      +   *
      +   * @param rapidView ID
      +   * @param callback
      +   */
      +  this.getActiveSprintsForRapidView = function (rapidViewId, callback) {
      +    var options = {
      +      rejectUnauthorized: this.strictSSL,
      +      uri: this.makeUri('/sprintquery/' + rapidViewId, 'rest/greenhopper/', '1.0'),
      +      method: 'GET',
      +      json: true
      +    };
      +    this.doRequest(options, function (error, response) {
      +      if (error) {
      +        callback(error, null);
      +        return;
      +      }
      +      if (response.statusCode === 404) {
      +        callback('Invalid URL');
      +        return;
      +      }
      +      if (response.statusCode !== 200) {
      +        callback(response.statusCode + ': Unable to connect to JIRA during sprints search.');
      +        return;
      +      }
      +      if (response.body) {
      +        var sprints = response.body.sprints,
      +          activeSprints = [],
      +          i;
      +        for (i = 0; i < sprints.length; i++) {
      +          if (sprints[i].state && sprints[i].state === 'ACTIVE') {
      +            activeSprints.push(sprints[i]);
      +          }
      +        }
      +        callback(null, activeSprints);
      +        return;
      +      }
      +    });
      +  };
      + +
    • + + +
    • +
      + +
      + +

      Get a list of Sprints belonging to a Rapid View

      Takes

        @@ -641,11 +757,11 @@

        Returns

        -
      • +
      • - +

        Get the issues for a rapidView / sprint

        Takes

        @@ -700,11 +816,11 @@

        Returns

      • -
      • +
      • - +

        Add an issue to the project’s current sprint

        Takes

        @@ -759,11 +875,11 @@

        Returns

      • -
      • +
      • - +

        Takes

        @@ -890,11 +1006,11 @@

        Returns

      • -
      • +
      • - +

        Get Versions for a project

        Takes

        @@ -938,11 +1054,11 @@

        Returns

      • -
      • +
      • - +

        Create a version

        Takes

        @@ -1002,11 +1118,11 @@

        Returns

      • -
      • +
      • - +

        Update a version

        Takes

        @@ -1067,11 +1183,11 @@

        Returns

      • -
      • +
      • - +

        Pass a search query to Jira

        Takes

        @@ -1105,11 +1221,11 @@

        Returns

      • -
      • +
      • - +

        backwards compatibility

        @@ -1154,11 +1270,11 @@

        Returns

      • -
      • +
      • - +

        Search user on Jira

        Takes

        @@ -1211,11 +1327,11 @@

        Returns

      • -
      • +
      • - +

        Takes

        @@ -1248,11 +1364,11 @@

        Returns

      • -
      • +
      • - +

        Add issue to Jira

        Takes

        @@ -1298,11 +1414,11 @@

        Returns

      • -
      • +
      • - +

        Delete issue to Jira

        Takes

        @@ -1343,11 +1459,11 @@

        Returns

      • -
      • +
      • - +

        Update issue in Jira

        Takes

        @@ -1390,11 +1506,11 @@

        Returns

      • -
      • +
      • - +

        List Components

        Takes

        @@ -1461,11 +1577,11 @@

        Returns

      • -
      • +
      • - +

        List listFields

        Takes

        @@ -1522,11 +1638,11 @@

        Returns

      • -
      • +
      • - +

        List listPriorities

        Takes

        @@ -1578,11 +1694,11 @@

        Returns

      • -
      • +
      • - +

        List Transitions

        Takes

        @@ -1663,11 +1779,11 @@

        Returns

      • -
      • +
      • - +

        Transition issue in Jira

        Takes

        @@ -1710,11 +1826,11 @@

        Returns

      • -
      • +
      • - +

        List all Viewable Projects

        Takes

        @@ -1770,11 +1886,11 @@

        Returns

      • -
      • +
      • - +

        Add a comment to an issue

        Takes

        @@ -1823,11 +1939,11 @@

        Returns

      • -
      • +
      • - +

        Add a worklog to a project

        Takes

        @@ -1909,11 +2025,11 @@

        Returns

      • -
      • +
      • - +

        List all Issue Types

        Takes

        @@ -1963,11 +2079,11 @@

        Returns

      • -
      • +
      • - +

        Register a webhook

        Takes

        @@ -2023,11 +2139,11 @@

        Returns

      • -
      • +
      • - +

        List all registered webhooks

        Takes

        @@ -2081,11 +2197,11 @@

        Returns

      • -
      • +
      • - +

        Get a webhook by its ID

        Takes

        @@ -2125,11 +2241,11 @@

        Returns

      • -
      • +
      • - +

        Delete a registered webhook

        Takes

        @@ -2169,11 +2285,11 @@

        Returns

      • -
      • +
      • - +

        Describe the currently authenticated user

        Takes

        @@ -2226,11 +2342,11 @@

        Returns

      • -
      • +
      • - +

        Retrieve the backlog of a certain Rapid View

        Takes

        From f7871ee8cd8dacf3ca424dc8e27aeec9f7980055 Mon Sep 17 00:00:00 2001 From: Adam Gruber Date: Mon, 20 Oct 2014 13:22:28 -0400 Subject: [PATCH 21/21] revert changes made for testing --- Gruntfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 233454f1..d0e72fb2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -9,7 +9,7 @@ module.exports = function (grunt) { forceExit: false, extensions: 'coffee', jUnit: { - report: true, + report: false, savePath : "./build/reports/jasmine/", useDotNotation: true, consolidate: true @@ -27,7 +27,7 @@ module.exports = function (grunt) { }, jslint: { client: { - src: ['./Gruntfile.js', 'lib/**/jira.js'], + src: ['./Gruntfile.js', 'lib/**/*.js'], directives: { indent: 2, curly: true,