diff --git a/package.json b/package.json index 8dc55e8..db3c196 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitlab-time-tracker", - "version": "1.7.39", + "version": "1.7.40", "description": "A command line interface for GitLabs time tracking feature.", "bugs": { "url": "https://github.com/kriskbx/gitlab-time-tracker/issues" @@ -39,7 +39,7 @@ "cli-cursor": "^2.1.0", "cli-table": "^0.3.1", "colors": "^1.3.1", - "commander": "kriskbx/commander.js", + "commander": "^2.19.0", "csv-string": "^2.3.2", "find-in-files": "^0.4.0", "hash-sum": "^1.0.2", @@ -53,8 +53,6 @@ "progress": "^2.0.0", "prompt": "^1.0.0", "read-yaml": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.4", "shelljs": "^0.8.3", "tempfile": "^2.0.0", "underscore": "^1.9.1", diff --git a/src/models/base.js b/src/models/base.js index a7ca006..f8b498d 100755 --- a/src/models/base.js +++ b/src/models/base.js @@ -1,5 +1,3 @@ -const request = require('request-promise-native'); -const url = require('url'); const async = require('async'); const crypto = require('crypto'); @@ -36,9 +34,10 @@ class base { data.private_token = this.token; return new Promise((resolve, reject) => { - request.post(`${this.url}${path}`, { + fetch(`${this.url}${path}`, { + method: 'POST', json: true, - body: data, + body: JSON.stringify(data), insecure: this._insecure, proxy: this._proxy, resolveWithFullResponse: true, @@ -67,7 +66,7 @@ class base { path += `&page=${page}&per_page=${perPage}`; return new Promise((resolve, reject) => { - request(`${this.url}${path}`, { + fetch(`${this.url}${path}`, { json: true, insecure: this._insecure, proxy: this._proxy, @@ -95,15 +94,18 @@ class base { let collect = []; this.get(path, 1, perPage).then(response => { - response.body.forEach(item => collect.push(item)); - let pages = parseInt(response.headers['x-total-pages']); - - if (pages === 1) return resolve(collect); - - let tasks = base.createGetTasks(path, pages, 2, perPage); - this.getParallel(tasks, collect, runners).then(() => { - resolve(collect); - }).catch(error => reject(error)); + response.json().then(data => { + data.forEach(item => collect.push(item)); + + let pages = response.headers.get('x-total-pages') + + if (pages === 1) return resolve(collect); + + let tasks = base.createGetTasks(path, pages, 2, perPage); + this.getParallel(tasks, collect, runners).then(() => { + resolve(collect); + }).catch(error => reject(error)); + }) }).catch(err => reject(err)); }); } @@ -134,8 +136,10 @@ class base { */ getParallel(tasks, collect = [], runners = this._parallel) { return this.parallel(tasks, (task, done) => { - this.get(task.path, task.page, task.perPage).then((response) => { - response.body.forEach(item => collect.push(item)); + this.get(task.path, task.page, task.perPage) + .then(response => response.json()) + .then(items => { + items.forEach(item => collect.push(item)); done(); }).catch(error => done(error)); }, runners); @@ -148,7 +152,7 @@ class base { */ setDump(response, key) { this.config.setDump(key, { - headers: response.headers, + headers: Object.fromEntries(response.headers.entries()), body: response.body }); } diff --git a/src/models/hasTimes.js b/src/models/hasTimes.js index 87cd1fe..ebf7935 100755 --- a/src/models/hasTimes.js +++ b/src/models/hasTimes.js @@ -7,6 +7,7 @@ const Time = require('./time'); const regex = /added (.*) of time spent(?: at (.*))?/i; const subRegex = /subtracted (.*) of time spent(?: at (.*))?/i; const removeRegex = /Removed time spent/i; +const delRegex = /deleted (.*) of spent time from (.*)/i; /** * base model for models that have times @@ -68,21 +69,27 @@ class hasTimes extends Base { }); let promise = this.parallel(this.notes, (note, done) => { - let created = moment(note.created_at), match, subMatch; + let created = moment(note.created_at), match, subMatch, delMatch; if ( // // filter out user notes !note.system || // filter out notes that are no time things - !(match = regex.exec(note.body)) && !(subMatch = subRegex.exec(note.body)) && !removeRegex.exec(note.body) + !(match = regex.exec(note.body)) && !(subMatch = subRegex.exec(note.body)) && !(delMatch = delRegex.exec(note.body)) && !removeRegex.exec(note.body) ) return done(); // change created date when explicitly defined if(match && match[2]) created = moment(match[2]); if(subMatch && subMatch[2]) created = moment(subMatch[2]); + if(delMatch && delMatch[2]) created = moment(delMatch[2]); + + // collect minus items + let minusFlag = subMatch || delMatch; + let minusMatch = minusFlag ? subMatch ? subMatch[1] : delMatch ? delMatch[1] : 0 : 0; // create a time string and a time object - let timeString = match ? match[1] : (subMatch ? `-${subMatch[1]}` : `-${Time.toHumanReadable(timeSpent)}`); + let timeString = match ? match[1] : (minusMatch ? `-${minusMatch}` : `-${Time.toHumanReadable(timeSpent)}`); + timeString = delMatch ? minusMatch.startsWith('-') ? minusMatch.replace('-', '') : `-${minusMatch}` : timeString; let time = new Time(null, created, note, this, this.config); time.seconds = Time.parse(timeString, 8, 5, 4); diff --git a/src/models/issue.js b/src/models/issue.js index adeed56..779a04d 100755 --- a/src/models/issue.js +++ b/src/models/issue.js @@ -21,8 +21,8 @@ class issue extends hasTimes { promise = this.get(`projects/${encodeURIComponent(project)}/issues/${id}`); } - promise.then(issue => { - this.data = issue.body; + promise.then(response => response.json()).then(issue => { + this.data = issue; return promise; }); diff --git a/src/models/mergeRequest.js b/src/models/mergeRequest.js index 5ea2f95..1c45430 100755 --- a/src/models/mergeRequest.js +++ b/src/models/mergeRequest.js @@ -18,8 +18,8 @@ class mergeRequest extends hasTimes { promise = this.get(`projects/${encodeURIComponent(project)}/merge_requests/${id}`); } - promise.then(issue => { - this.data = issue.body; + promise.then(response => response.json()).then(issue => { + this.data = issue; return promise; }); diff --git a/src/models/owner.js b/src/models/owner.js index 55bdd68..80be53b 100755 --- a/src/models/owner.js +++ b/src/models/owner.js @@ -37,9 +37,10 @@ class owner extends Base { getGroup() { return new Promise((resolve, reject) => { this.get(`groups`) + .then(response => response.json()) .then(groups => { - if (groups.body.length === 0) return reject('Group not found'); - groups = groups.body; + if (groups.length === 0) return reject('Group not found'); + groups = groups; let filtered = groups.filter(group => group.full_path === this.config.get('project')); if (filtered.length === 0) return reject('Group not found'); @@ -57,10 +58,11 @@ class owner extends Base { getSubGroups() { return new Promise((resolve, reject) => { this.get(`groups`) + .then(response => response.json()) .then(groups => { - if (groups.body.length === 0) return resolve(); + if (groups.length === 0) return resolve(); - let filtered = this._filterGroupsByParents(groups.body, this.groups.map(g => g.id)); + let filtered = this._filterGroupsByParents(groups, this.groups.map(g => g.id)); if (filtered.length === 0) return resolve(); this.groups = this.groups.concat(filtered); @@ -121,9 +123,9 @@ class owner extends Base { */ getProjectsByGroup() { return this.parallel(this.groups, (group, done) => { - this.get(`groups/${group.id}/projects`) + this.all(`groups/${group.id}/projects`) .then(projects => { - this.projects = this.projects.concat(projects.body); + this.projects = this.projects.concat(projects); done(); }) .catch(e => done(e)); diff --git a/src/models/project.js b/src/models/project.js index eba3d5b..af48fb6 100755 --- a/src/models/project.js +++ b/src/models/project.js @@ -21,7 +21,7 @@ class project extends Base { */ make(name) { let promise = this.get(`projects/${encodeURIComponent(name)}`); - promise.then(project => this.data = project.body); + promise.then(response => response.json()).then(project => this.data = project); return promise; } @@ -33,16 +33,18 @@ class project extends Base { members() { return new Promise((resolve, reject) => { this.get(`projects/${this.id}/members`) + .then(response => response.json()) .then(response => { - this.projectMembers = this.projectMembers.concat(response.body); + this.projectMembers = this.projectMembers.concat(response); return new Promise(r => r()); }) .then(() => { if (!this.data.namespace || !this.data.namespace.kind || this.data.namespace.kind !== "group") return resolve(); this.get(`groups/${this.data.namespace.id}/members`) + .then(response => response.json()) .then(response => { - this.projectMembers = this.projectMembers.concat(response.body); + this.projectMembers = this.projectMembers.concat(response); resolve(); }) .catch(e => reject(e)); diff --git a/src/models/report.js b/src/models/report.js index c5fad8f..cf888ee 100755 --- a/src/models/report.js +++ b/src/models/report.js @@ -68,7 +68,7 @@ class report extends Base { */ getProject() { let promise = this.get(`projects/${encodeURIComponent(this.config.get('project'))}`); - promise.then(project => this.setProject(project.body)); + promise.then(response => response.json()).then(project => this.setProject(project)); return promise; } @@ -143,6 +143,38 @@ class report extends Base { return promise; } + /** + * process all notes the given input + * @param input + * @param model + * @param advance + * @returns {*|Promise} + */ + processNote(input, model, advance = false) { + let collect = []; + + let promise = this.parallel(this[input], (data, done) => { + + let item = new model(this.config, data); + item.project_namespace = this.projects[item.project_id]; + + item.getNotes() + .then(() => { + collect.push(item); + + if (advance) advance(); + return done(); + }); + + + // collect items, query times & stats + collect.push(); + }); + + promise.then(() => this[input] = collect); + return promise; + } + /** * merge another report into this report * @param report @@ -164,6 +196,15 @@ class report extends Base { return this.process('issues', Issue, advance); } + /** + * process notes + * @param advance + * @returns {Promise} + */ + processNotes(advance = false) { + return this.processNote('issues', Issue, advance); + } + /** * process merge requests * @param advance