Skip to content

Commit 7b4ff7d

Browse files
committed
Merge branch 'release/v0.0.2'
2 parents cf77cd7 + 431f29e commit 7b4ff7d

File tree

7 files changed

+351
-73
lines changed

7 files changed

+351
-73
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ npm-debug.log
66
coverage/lcov-report/
77

88
coverage/
9+
10+
all_issues\.csv

LICENSE.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2017 REMOTE ORIGIN s.r.o.
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

+13-5
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,24 @@ Command line application allowing you to download all issues in the CSV format f
1111
- [Node.js](https://nodejs.org) (`v6.10.3 LTS`)
1212
- [Yarn](https://yarnpkg.com) (for development)
1313

14-
## Usage
15-
16-
### Install app globally
14+
## Installation
1715

1816
npm install -g git-issues-downloader
1917

20-
### Running the app
18+
## Ussage
2119

2220
git-issues-downloader <repository URL>
2321

22+
### Examples
23+
24+
Command prompt will ask for username and password credentials for GitHub
25+
26+
git-issues-downloader https://github.com/remoteorigin/git-issues-downloader
27+
28+
Example with username and password
29+
30+
git-issues-downloader -u <username> -p <username> https://github.com/remoteorigin/git-issues-downloader
31+
2432
## Development
2533

2634
### Project Setup
@@ -39,7 +47,7 @@ All tests are are written in [Mocha](https://mochajs.org/) and stored in the `te
3947

4048
yarn run test
4149

42-
### Lintining
50+
### Linting
4351

4452
Using [Standard](https://github.com/feross/standard) JavaScript linter & automatic code fixer.
4553

app.js

100644100755
+39-43
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ const _ = require('lodash')
66
const moment = require('moment')
77
const read = require('read')
88
const chalk = require('chalk')
9-
const sinon = require('sinon')
109
const argv = require('yargs')
1110
.usage('Usage: git-issues-downloader [options] URL \nType git-issues-downloader --help to see a list of all options.')
1211
.help('h')
1312

14-
.version(function() {
13+
.version(function () {
1514
return `Version: ${require('./package.json').version}`
1615
})
1716

@@ -39,7 +38,7 @@ getAuth = function (auth, silent, callback) {
3938

4039
// callback function for getting requested options
4140

42-
exports.getRequestedOptions = function (username, password, url, callback) {
41+
const getRequestedOptions = exports.getRequestedOptions = function (username, password, url, callback) {
4342
const requestOptions = {
4443
headers: {
4544
'User-Agent': 'request'
@@ -89,35 +88,39 @@ exports.getRequestedOptions = function (username, password, url, callback) {
8988

9089
// main function for running program
9190

92-
exports.main = function (data, requestedOptions) {
93-
console.log('Requesting API...')
94-
this.requestBody(requestedOptions, (error, response, body) => {
95-
const linkObject = this.responseToObject(response.headers)
91+
const main = exports.main = function (data, requestedOptions) {
92+
logExceptOnTest('Requesting API...')
93+
requestBody(requestedOptions, (error, response, body) => {
94+
linkObject = responseToObject(response.headers)
9695

9796
// take body, parse it and add it to data
9897

9998
data = _.concat(data, body)
10099

101100
if (linkObject.nextPage) {
102-
console.log(chalk.green(`Successfully requested ${linkObject.nextPage.number - 1}. page of ${linkObject.lastPage.number}`))
103-
101+
logExceptOnTest(chalk.green(`Successfully requested ${linkObject.nextPage.number - 1}. page of ${linkObject.lastPage.number}`))
104102
requestedOptions.url = linkObject.nextPage.url
105-
this.main(data, requestedOptions)
103+
main(data, requestedOptions)
106104
} else {
107-
console.log(chalk.green('Successfully requested last page'))
105+
logExceptOnTest(chalk.green('Successfully requested last page'))
106+
107+
logExceptOnTest('\nConverting issues...')
108+
const csvData = convertJSonToCsv(data)
109+
logExceptOnTest(chalk.green(`\nSuccessfully converted ${data.length} issues!`))
108110

109-
console.log('\nConverting issues...')
110-
const csvData = this.convertJSonToCsv(data)
111-
console.log(chalk.green(`\nSuccessfully converted ${data.length} issues!`))
111+
logExceptOnTest('\nWriting data to csv file')
112+
fs.writeFile(outputFileName, csvData, (err) => {
113+
if (err) throw err
112114

113-
this.writeData(csvData, outputFileName)
115+
logExceptOnTest(chalk.yellow(`\nIssues was downloaded, converted and saved to ${outputFileName}`))
116+
})
114117
}
115118
})
116119
}
117120

118121
// get page url and page number from link
119122

120-
exports.getUrlAndNumber = function (link) {
123+
const getUrlAndNumber = exports.getUrlAndNumber = function (link) {
121124
return {
122125
url: link.slice(link.indexOf('<') + 1, link.indexOf('>')),
123126
number: link.slice(link.indexOf('page', link.indexOf('state')) + 5, link.indexOf('>'))
@@ -126,25 +129,25 @@ exports.getUrlAndNumber = function (link) {
126129

127130
// create and return links info (page url and page number for all 4 possible links in response.headers.link) from whole response.hearders
128131

129-
exports.responseToObject = function (response) {
132+
const responseToObject = exports.responseToObject = function (response) {
130133
const rawLink = response.link
131134

132135
if (rawLink && rawLink.includes('next')) {
133136
const links = rawLink.split(',')
134137

135138
return {
136-
nextPage: (links[0]) ? this.getUrlAndNumber(links[0]) : false,
137-
lastPage: (links[1]) ? this.getUrlAndNumber(links[1]) : false,
138-
firstPage: (links[2]) ? this.getUrlAndNumber(links[2]) : false,
139-
prevPage: (links[3]) ? this.getUrlAndNumber(links[3]) : false
139+
nextPage: (links[0]) ? getUrlAndNumber(links[0]) : false,
140+
lastPage: (links[1]) ? getUrlAndNumber(links[1]) : false,
141+
firstPage: (links[2]) ? getUrlAndNumber(links[2]) : false,
142+
prevPage: (links[3]) ? getUrlAndNumber(links[3]) : false
140143
}
141144
}
142145
return false
143146
}
144147

145148
// use url and request api
146149

147-
exports.requestBody = function (requestedOptions, callback) {
150+
const requestBody = exports.requestBody = function (requestedOptions, callback) {
148151
request.get(requestedOptions, function (err, response, body) {
149152
const JSObject = JSON.parse(body)
150153

@@ -153,13 +156,13 @@ exports.requestBody = function (requestedOptions, callback) {
153156

154157
switch (JSObject.message) {
155158
case 'Not Found':
156-
console.log(chalk.red('\nWe didn\'t find any repository on this URL, please check it'))
159+
logExceptOnTest(chalk.red('\nWe didn\'t find any repository on this URL, please check it'))
157160
break
158161
case 'Bad credentials':
159-
console.log(chalk.red('\nYour username or password is invalid, please check it'))
162+
logExceptOnTest(chalk.red('\nYour username or password is invalid, please check it'))
160163
break
161164
default:
162-
console.log(chalk.red('\nRepository have 0 issues. Nothing to download'))
165+
logExceptOnTest(chalk.red('\nRepository have 0 issues. Nothing to download'))
163166
}
164167
} else {
165168
callback(err, response, JSObject)
@@ -169,46 +172,39 @@ exports.requestBody = function (requestedOptions, callback) {
169172

170173
// take JSON data, convert them into CSV format and return them
171174

172-
exports.convertJSonToCsv = function (jsData) {
173-
const csvData = jsData.map(object => {
175+
const convertJSonToCsv = exports.convertJSonToCsv = function (jsData) {
176+
return jsData.map(object => {
174177
const date = moment(object.created_at).format('L')
175178
const labels = object.labels
176179
const stringLabels = labels.map(label => label.name).toString()
177180
return `"${object.number}"; "${object.title.replace(/"/g, '\'')}"; "${object.html_url}"; "${stringLabels}"; "${object.state}"; "${date}"\n`
178181
}).join('')
179-
180-
return csvData
181-
}
182-
183-
// create a new file and write converted data on him
184-
185-
exports.writeData = function (data, outputFileName) {
186-
console.log('\nWriting data to csv file')
187-
fs.writeFile(outputFileName, data, (err) => {
188-
if (err) throw err
189-
190-
console.log(chalk.yellow(`\nIssues was downloaded, converted and saved to ${outputFileName}`))
191-
})
192182
}
193183

194184
// execute main function with requested options and condition for URL input
195185

196-
exports.execute = function (argvRepository) {
186+
const execute = exports.execute = function (argvRepository) {
197187
if (argvRepository) {
198188
const issuesPerPage = 100
199189
const repoUserName = argvRepository.slice(19, argvRepository.indexOf('/', 19))
200190
const repoUrl = (argvRepository.slice(20 + repoUserName.length, argvRepository.lastIndexOf('/'))) ? argvRepository.slice(20 + repoUserName.length, argvRepository.lastIndexOf('/')) : argvRepository.slice(20 + repoUserName.length)
201191

202192
const startUrl = `https://api.github.com/repos/${repoUserName}/${repoUrl}/issues?per_page=${issuesPerPage}&state=all&page=1`
203193

204-
this.getRequestedOptions(argv.username, argv.password, startUrl, (requestedOptions) => {
205-
this.main([], requestedOptions)
194+
getRequestedOptions(argv.username, argv.password, startUrl, (requestedOptions) => {
195+
main([], requestedOptions)
206196
})
207197
} else {
208198
console.log('Usage: git-issues-downloader [options] URL')
209199
}
210200
}
211201

202+
function logExceptOnTest (string) {
203+
if (process.env.NODE_ENV !== 'test') {
204+
console.log(string)
205+
}
206+
}
207+
212208
const argvRepository = argv._[argv._.length - 1]
213209

214210
this.execute(argvRepository)

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
{
22
"name": "git-issues-downloader",
3-
"version": "0.0.1",
3+
"version": "0.0.2",
44
"main": "app.js",
55
"license": "MIT",
66
"scripts": {
77
"start": "node app.js ; exit 0",
88
"test": "mocha test",
9-
"lint": "standard"
9+
"lint": "standard",
10+
"lint:fix": "standard --fix"
1011
},
1112
"standard": {
1213
"env": [

test/app-test.js

+48-20
Original file line numberDiff line numberDiff line change
@@ -8,54 +8,52 @@ const sinon = require('sinon')
88
const app = require('../app.js')
99
const dummyData = require('./dummy-data')
1010

11-
const responseToObject = app.responseToObject(dummyData.apiResponse)
1211
const getRequestedOptions = app.getRequestedOptions
13-
const requestBody = app.requestBody
1412
const convertJsonToCsv = app.convertJSonToCsv
15-
const writeData = app.writeData
1613
const main = app.main
1714

1815
describe('downloadGitIssues', function () {
19-
describe('Get URL and Number', function () {
16+
process.env.NODE_ENV = 'test'
17+
describe('getUrlAndNumber', function () {
2018
const getUrlAndNumberObject = app.getUrlAndNumber(dummyData.nextPageLink)
2119

22-
it('should return url', function () {
20+
it('return url', function () {
2321
assert.equal(getUrlAndNumberObject.url, 'https://api.github.com/repositories/90146723/issues?per_page=10&state=all&page=2')
2422
})
2523

26-
it('should return page number', function () {
24+
it('return page number', function () {
2725
assert.equal(getUrlAndNumberObject.number, '2')
2826
})
2927
})
3028

31-
describe('Response To Object', function () {
32-
it('should return url of next page', function () {
33-
assert.equal(responseToObject.nextPage.url, 'https://api.github.com/repositories/90146723/issues?per_page=10&state=all&page=2')
29+
describe('responseToObject', function () {
30+
it('return url of next page', function () {
31+
assert.equal(app.responseToObject(dummyData.apiResponse).nextPage.url, 'https://api.github.com/repositories/90146723/issues?per_page=10&state=all&page=2')
3432
})
3533
})
3634

3735
describe('getRequestedOptions', function () {
38-
it('should return object with username and password', function () {
36+
it('return object with username and password', function () {
3937
getRequestedOptions('username', 'password', dummyData.nextPageLink, (done) => {
4038
expect(done).to.deep.equal(dummyData.requestOptions)
4139
})
4240
})
4341
})
4442

4543
describe('convertJSONToCsv', function () {
46-
it('should return converted issues 21', function () {
44+
it('return converted issues without label', function () {
4745
const result = convertJsonToCsv(dummyData.JSONdata21)
4846

4947
expect(result).to.deep.equal(dummyData.issuesResult21)
5048
})
5149

52-
it('should return converted issues 20', function () {
50+
it('return converted issues with label', function () {
5351
const result = convertJsonToCsv(dummyData.JSONdata20)
5452

5553
expect(result).to.deep.equal(dummyData.issuesResult20)
5654
})
5755
})
58-
describe('requestedBody = suggested output', function () {
56+
describe('requestedBody (successful request)', function () {
5957
before(function () {
6058
sinon
6159
.stub(request, 'get')
@@ -66,13 +64,13 @@ describe('downloadGitIssues', function () {
6664
request.get.restore()
6765
})
6866

69-
it('should be called with requested options', function () {
70-
requestBody('', (error, response, body) => {
67+
it('called body with requested options', function () {
68+
app.requestBody('', (error, response, body) => {
7169
expect(body).not.be.empty
7270
})
7371
})
7472
})
75-
describe('requestedBody = bad url', function () {
73+
describe('requestedBody (URL not found)', function () {
7674
before(function () {
7775
sinon
7876
.stub(request, 'get')
@@ -83,14 +81,44 @@ describe('downloadGitIssues', function () {
8381
request.get.restore()
8482
})
8583

86-
it('should invoke error message for bad URL', function () {
87-
requestBody('', (error, response, body) => {
84+
it('invoke error message for bad URL', function () {
85+
app.requestBody('', (error, response, body) => {
8886
})
8987
})
9088
})
91-
describe('writeData', function () {
92-
it('should create file with data', function () {
89+
describe('requestedBody (Bad Credentials)', function () {
90+
before(function () {
91+
sinon
92+
.stub(request, 'get')
93+
.yields(null, null, JSON.stringify(dummyData.bodyForBadCredentials))
94+
})
95+
96+
after(function () {
97+
request.get.restore()
98+
})
99+
100+
it('invoke error message for Bad Credentials', function () {
101+
app.requestBody('', (error, response, body) => {
102+
})
103+
})
104+
})
105+
describe('main', function () {
106+
before(function () {
107+
sinon
108+
.stub(request, 'get')
109+
.yields(null, dummyData.response2Page, JSON.stringify(dummyData.testIssues))
110+
})
111+
112+
after(function () {
113+
request.get.restore()
114+
})
115+
116+
it('successful execute all function', function () {
117+
const main = sinon.spy()
118+
119+
main([], dummyData.requestedOptions)
93120

121+
assert(main.calledOnce)
94122
})
95123
})
96124
})

0 commit comments

Comments
 (0)