From bba1644eb560ae1ae7dcbd5bd0b1b13bd4253ef4 Mon Sep 17 00:00:00 2001 From: Jeff Baldwin Date: Mon, 22 May 2017 11:26:57 -0600 Subject: [PATCH] JSDoc, Formatting, and Spelling Fixes I made some changes to support JSDoc to bring it up to date with JSDocs newest standards. I fixed a few spelling errors. Also removed unnecessary declaration in createProject. After all of these updates my jira.js no longer has any reported issues with ESLint and my JSDocs look a lot cleaner. I have been using this in a production environment for about a week now with no issues. --- src/jira.js | 158 +++++++++++++++++++++------------------------ test/jira-tests.js | 37 ++++++----- 2 files changed, 94 insertions(+), 101 deletions(-) diff --git a/src/jira.js b/src/jira.js index c118ac57..a115a88d 100644 --- a/src/jira.js +++ b/src/jira.js @@ -21,7 +21,7 @@ export default class JiraApi { this.base = options.base || ''; this.intermediatePath = options.intermediatePath; this.strictSSL = options.hasOwnProperty('strictSSL') ? options.strictSSL : true; - // This is so we can fake during unit tests + // This is so we can fake during unit tests this.request = options.request || request; this.webhookVersion = options.webHookVersion || '1.0'; this.greenhopperVersion = options.greenhopperVersion || '1.0'; @@ -55,8 +55,7 @@ export default class JiraApi { } /** - * @typedef JiraApiOptions - * @type {object} + * @typedef {object} JiraApiOptions * @property {string} [protocol=http] - What protocol to use to connect to * jira? Ex: http|https * @property {string} host - What host is this tool connecting to for the jira @@ -82,31 +81,51 @@ export default class JiraApi { * that if the underlying TCP connection cannot be established, the OS-wide TCP connection timeout * will overrule the timeout option ([the default in Linux can be anywhere from 20-120 * * seconds](http://www.sekuda.com/overriding_the_default_linux_kernel_20_second_tcp_socket_connect_timeout)). - * @property {string} [webhookVersion=1.0] - What webhook version does this api wrapper need to + * @property {string} [webHookVersion=1.0] - What webhook version does this api wrapper need to * hit? * @property {string} [greenhopperVersion=1.0] - What webhook version does this api wrapper need * to hit? - * @property {OAuth} - Specify an oauth object for this tool to authenticate all requests using - * OAuth. + * @property {OAuth} oauth - Specify an oauth object for this tool to authenticate all requests + * using OAuth. + * @property {string} bearer - Specify an Authorization Bearer to be used instead of user/pass */ /** - * @typedef OAuth - * @type {object} + * @typedef {object} OAuth * @property {string} consumer_key - The consumer entered in Jira Preferences. * @property {string} consumer_secret - The private RSA file. * @property {string} access_token - The generated access token. * @property {string} access_token_secret - The generated access toke secret. - * @property {string} signature_method [signature_method=RSA-SHA1] - OAuth signurate methode + * @property {string} signature_method [signature_method=RSA-SHA1] - OAuth signature method * Possible values RSA-SHA1, HMAC-SHA1, PLAINTEXT. Jira Cloud supports only RSA-SHA1. */ + /** + * @typedef {object} UriOptions + * @property {string} pathname - The url after the specific functions path + * @property {object} [query] - An object of all query parameters + * @property {string} [intermediatePath] - Overwrites with specified path + */ + + /** + * @typedef {object} WebhookUriOptions + * @property {string} pathname - The url after the specific functions path + * @property {string} [intermediatePath] - Overwrites with specified path + */ + + /** + * @typedef {object} makeRequestHeaderOptions + * @property {string} [method] - HTTP Request Method. ie GET, POST, PUT, DELETE + * @property {boolean} [followAllRedirects] + */ + /** * @name makeRequestHeader * @function * Creates a requestOptions object based on the default template for one * @param {string} uri - * @param {object} [options] - an object containing fields and formatting how the + * @param {makeRequestHeaderOptions} [options] - an object containing fields to + * add to the header request object */ makeRequestHeader(uri, options = {}) { return { @@ -118,120 +137,92 @@ export default class JiraApi { }; } - /** - * @typedef makeRequestHeaderOptions - * @type {object} - * @property {string} [method] - HTTP Request Method. ie GET, POST, PUT, DELETE - */ - /** * @name makeUri * @function * Creates a URI object for a given pathname - * @param {object} [options] - an object containing path information + * @param {UriOptions} object - an object containing path information */ - makeUri({ pathname, query, intermediatePath }) { - const intermediateToUse = this.intermediatePath || intermediatePath; + makeUri(object) { + const intermediateToUse = this.intermediatePath || object.intermediatePath; const tempPath = intermediateToUse || `/rest/api/${this.apiVersion}`; const uri = url.format({ protocol: this.protocol, hostname: this.host, port: this.port, - pathname: `${this.base}${tempPath}${pathname}`, - query, + pathname: `${this.base}${tempPath}${object.pathname}`, + query: object.query, }); return decodeURIComponent(uri); } - /** - * @typedef makeUriOptions - * @type {object} - * @property {string} pathname - The url after the /rest/api/version - * @property {object} query - a query object - * @property {string} intermediatePath - If specified will overwrite the /rest/api/version section - */ - /** * @name makeWebhookUri * @function * Creates a URI object for a given pathName - * @param {object} [options] - An options object specifying uri information + * @param {WebhookUriOptions} object - an object containing */ - makeWebhookUri({ pathname, intermediatePath }) { - const intermediateToUse = this.intermediatePath || intermediatePath; + makeWebhookUri(object) { + const intermediateToUse = this.intermediatePath || object.intermediatePath; const tempPath = intermediateToUse || `/rest/webhooks/${this.webhookVersion}`; const uri = url.format({ protocol: this.protocol, hostname: this.host, port: this.port, - pathname: `${this.base}${tempPath}${pathname}`, + pathname: `${this.base}${tempPath}${object.pathname}`, }); return decodeURIComponent(uri); } - /** - * @typedef makeWebhookUriOptions - * @type {object} - * @property {string} pathname - The url after the /rest/webhooks - * @property {string} intermediatePath - If specified will overwrite the /rest/webhooks section - */ - /** * @name makeSprintQueryUri * @function * Creates a URI object for a given pathName - * @param {object} [options] - The url after the /rest/ + * @param {UriOptions} object - an object containing path information */ - makeSprintQueryUri({ pathname, query, intermediatePath }) { - const intermediateToUse = this.intermediatePath || intermediatePath; + makeSprintQueryUri(object) { + const intermediateToUse = this.intermediatePath || object.intermediatePath; const tempPath = intermediateToUse || `/rest/greenhopper/${this.greenhopperVersion}`; const uri = url.format({ protocol: this.protocol, hostname: this.host, port: this.port, - pathname: `${this.base}${tempPath}${pathname}`, - query, + pathname: `${this.base}${tempPath}${object.pathname}`, + query: object.query, }); return decodeURIComponent(uri); } /** - * @typedef makeSprintQueryUriOptions - * @type {object} - * @property {string} pathname - The url after the /rest/api/version - * @property {object} query - a query object - * @property {string} intermediatePath - will overwrite the /rest/greenhopper/version section - */ - - /** - * @typedef makeDevStatusUri + * @name makeDevStatusUri * @function * Creates a URI object for a given pathname - * @arg {pathname, query, intermediatePath} obj1 - * @param {string} pathname obj1.pathname - The url after the /rest/api/version - * @param {object} query obj1.query - a query object - * @param {string} intermediatePath obj1.intermediatePath - If specified will overwrite the - * /rest/dev-status/latest/issue/detail section - */ - makeDevStatusUri({ pathname, query, intermediatePath }) { - const intermediateToUse = this.intermediatePath || intermediatePath; + * @param {UriOptions} object + */ + makeDevStatusUri(object) { + const intermediateToUse = this.intermediatePath || object.intermediatePath; const tempPath = intermediateToUse || '/rest/dev-status/latest/issue'; const uri = url.format({ protocol: this.protocol, hostname: this.host, port: this.port, - pathname: `${this.base}${tempPath}${pathname}`, - query, + pathname: `${this.base}${tempPath}${object.pathname}`, + query: object.query, }); return decodeURIComponent(uri); } + /** @typedef {object} doRequest + * @property {object} views + */ + /** * @name doRequest * @function * Does a request based on the requestOptions object * @param {object} requestOptions - fields on this object get posted as a request header for * requests to jira + * @returns {doRequest} */ async doRequest(requestOptions) { const options = { @@ -304,7 +295,7 @@ export default class JiraApi { }))); } -/** + /** * @name createProject * @function * Create a new Project @@ -332,10 +323,8 @@ export default class JiraApi { if (typeof projectName === 'undefined' || projectName === null) return response.views; - const rapidViewResult = response.views - .find(x => x.name.toLowerCase() === projectName.toLowerCase()); - - return rapidViewResult; + return response.views + .find(x => x.name.toLowerCase() === projectName.toLowerCase()); } /** Get the most recent sprint for a given rapidViewId @@ -471,7 +460,7 @@ export default class JiraApi { * [Jira Doc](https://docs.atlassian.com/jira/REST/latest/#d2e510) * @name updateVersion * @function - * @param {string} version - an new object of the version to update + * @param {object} version - an new object of the version to update */ updateVersion(version) { return this.doRequest(this.makeRequestHeader(this.makeUri({ @@ -514,8 +503,8 @@ export default class JiraApi { * @function * @param {string} searchString - jira query string in JQL * @param {object} optional - object containing any of the following properties - * @param {integer} [optional.startAt=0]: optional starting index number - * @param {integer} [optional.maxResults=50]: optional ending index number + * @param {number} [optional.startAt=0]: optional starting index number + * @param {number} [optional.maxResults=50]: optional ending index number * @param {array} [optional.fields]: optional array of string names of desired fields */ searchJira(searchString, optional = {}) { @@ -553,15 +542,15 @@ export default class JiraApi { * @function * @param {SearchUserOptions} options */ - searchUsers({ username, startAt, maxResults, includeActive, includeInactive }) { + searchUsers(options) { return this.doRequest(this.makeRequestHeader(this.makeUri({ pathname: '/user/search', query: { - username, - startAt: startAt || 0, - maxResults: maxResults || 50, - includeActive: includeActive || true, - includeInactive: includeInactive || false, + username: options.username, + startAt: options.startAt || 0, + maxResults: options.maxResults || 50, + includeActive: options.includeActive || true, + includeInactive: options.includeInactive || false, }, }), { followAllRedirects: true, @@ -569,11 +558,10 @@ export default class JiraApi { } /** - * @typedef SearchUserOptions - * @type {object} + * @typedef {object} SearchUserOptions * @property {string} username - A query string used to search username, name or e-mail address - * @property {integer} [startAt=0] - The index of the first user to return (0-based) - * @property {integer} [maxResults=50] - The maximum number of users to return + * @property {number} [startAt=0] - The index of the first user to return (0-based) + * @property {number} [maxResults=50] - The maximum number of users to return * @property {boolean} [includeActive=true] - If true, then active users are included * in the results * @property {boolean} [includeInactive=false] - If true, then inactive users @@ -584,8 +572,8 @@ export default class JiraApi { * @name getUsersInGroup * @function * @param {string} groupname - A query string used to search users in group - * @param {integer} [startAt=0] - The index of the first user to return (0-based) - * @param {integer} [maxResults=50] - The maximum number of users to return (defaults to 50). + * @param {number} [startAt=0] - The index of the first user to return (0-based) + * @param {number} [maxResults=50] - The maximum number of users to return (defaults to 50). */ getUsersInGroup(groupname, startAt = 0, maxResults = 50) { return this.doRequest( @@ -748,7 +736,7 @@ export default class JiraApi { }))); } - /** Add an option for a select list issue field. + /** Add an option for a select list issue field. * [Jira Doc](http://docs.atlassian.com/jira/REST/latest/#api/2/field/{fieldKey}/option-createOption) * @name createFieldOption * @function diff --git a/test/jira-tests.js b/test/jira-tests.js index 2adcd91c..a1145310 100644 --- a/test/jira-tests.js +++ b/test/jira-tests.js @@ -131,14 +131,14 @@ describe('Jira API Tests', () => { const jira = new JiraApi(getOptions()); expect(jira.makeUri({ pathname: '/somePathName' })) - .to.eql('http://jira.somehost.com:8080/rest/api/2.0/somePathName'); + .to.eql('http://jira.somehost.com:8080/rest/api/2.0/somePathName'); }); it('builds url with intermediatePath', () => { const jira = new JiraApi(getOptions()); expect(jira.makeUri({ pathname: '/somePathName', intermediatePath: 'intermediatePath' })) - .to.eql('http://jira.somehost.com:8080/intermediatePath/somePathName'); + .to.eql('http://jira.somehost.com:8080/intermediatePath/somePathName'); }); it('builds url with globally specified intermediatePath', () => { @@ -147,7 +147,7 @@ describe('Jira API Tests', () => { })); expect(jira.makeUri({ pathname: '/somePathName' })) - .to.eql('http://jira.somehost.com:8080/intermediatePath/somePathName'); + .to.eql('http://jira.somehost.com:8080/intermediatePath/somePathName'); }); it('builds url with query string parameters', () => { @@ -175,7 +175,7 @@ describe('Jira API Tests', () => { expect(jira.makeWebhookUri({ pathname: '/somePathName', })) - .to.eql('http://jira.somehost.com:8080/rest/webhooks/1.0/somePathName'); + .to.eql('http://jira.somehost.com:8080/rest/webhooks/1.0/somePathName'); }); it('makeWebhookUri functions with intermediate path', () => { @@ -185,7 +185,7 @@ describe('Jira API Tests', () => { pathname: '/somePathName', intermediatePath: '/someIntermediatePath', })) - .to.eql('http://jira.somehost.com:8080/someIntermediatePath/somePathName'); + .to.eql('http://jira.somehost.com:8080/someIntermediatePath/somePathName'); }); it('makeSprintQueryUri functions properly in the average case', () => { @@ -194,7 +194,7 @@ describe('Jira API Tests', () => { expect(jira.makeSprintQueryUri({ pathname: '/somePathName', })) - .to.eql('http://jira.somehost.com:8080/rest/greenhopper/1.0/somePathName'); + .to.eql('http://jira.somehost.com:8080/rest/greenhopper/1.0/somePathName'); }); it('makeSprintQueryUri functions properly in the average case', () => { @@ -204,7 +204,7 @@ describe('Jira API Tests', () => { pathname: '/somePathName', intermediatePath: '/someIntermediatePath', })) - .to.eql('http://jira.somehost.com:8080/someIntermediatePath/somePathName'); + .to.eql('http://jira.somehost.com:8080/someIntermediatePath/somePathName'); }); it('makeUri functions properly no port http', () => { @@ -217,7 +217,7 @@ describe('Jira API Tests', () => { expect(jira.makeUri({ pathname: '/somePathName', })) - .to.eql('http://jira.somehost.com/rest/api/2.0/somePathName'); + .to.eql('http://jira.somehost.com/rest/api/2.0/somePathName'); }); it('makeUri functions properly no port https', () => { @@ -230,7 +230,7 @@ describe('Jira API Tests', () => { expect(jira.makeUri({ pathname: '/somePathName', })) - .to.eql('https://jira.somehost.com/rest/api/2.0/somePathName'); + .to.eql('https://jira.somehost.com/rest/api/2.0/somePathName'); }); }); @@ -300,7 +300,7 @@ describe('Jira API Tests', () => { ); await jira.doRequest({}) - .should.eventually.be.rejectedWith('some error to throw'); + .should.eventually.be.rejectedWith('some error to throw'); }); it('doRequest throws a list of errors properly', async () => { @@ -321,7 +321,7 @@ describe('Jira API Tests', () => { ); await jira.doRequest({}) - .should.eventually.be.rejectedWith('some error to throw, another error'); + .should.eventually.be.rejectedWith('some error to throw, another error'); }); it('doRequest does not throw an error on empty response', (done) => { @@ -335,8 +335,8 @@ describe('Jira API Tests', () => { ); jira.doRequest({}) - .should.eventually.be.fulfilled - .and.notify(done); + .should.eventually.be.fulfilled + .and.notify(done); }); }); @@ -398,7 +398,7 @@ describe('Jira API Tests', () => { const result = await dummyURLCall('getUnresolvedIssueCount', ['someVersion'], dummyRequest); result.should - .eql('http://jira.somehost.com:8080/rest/api/2.0/version/someVersion/unresolvedIssueCount'); + .eql('http://jira.somehost.com:8080/rest/api/2.0/version/someVersion/unresolvedIssueCount'); }); it('getProject hits proper url', async () => { @@ -445,7 +445,7 @@ describe('Jira API Tests', () => { it('getSprintIssues hits proper url', async () => { const result = await dummyURLCall('getSprintIssues', ['someRapidView', 'someSprintId']); result.should - .eql('http://jira.somehost.com:8080/rest/greenhopper/1.0/rapid/charts/sprintreport?rapidViewId=someRapidView&sprintId=someSprintId'); + .eql('http://jira.somehost.com:8080/rest/greenhopper/1.0/rapid/charts/sprintreport?rapidViewId=someRapidView&sprintId=someSprintId'); }); it('listSprints hits proper url', async () => { @@ -708,7 +708,12 @@ describe('Jira API Tests', () => { result.should.eql('http://jira.somehost.com:8080/rest/api/2.0/status'); }); - // Field Option APIs Suite Tests + it('issueNotify hits proper url', async () => { + const result = await dummyURLCall('issueNotify', ['someIssueId', {}]); + result.should.eql('http://jira.somehost.com:8080/rest/api/2.0/issue/someIssueId/notify'); + }); + + // Dev-Status APIs Suite Tests describe('Dev-Status APIs Suite Tests', () => { it('getDevStatusSummary hits proper url', async () => { const result = await dummyURLCall('getDevStatusSummary', ['someIssueId']);