From c93b49da320e89fe9e6db140add769f91a7226c1 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Thu, 26 Sep 2019 11:19:30 +0200 Subject: [PATCH 01/16] Change request to fetch --- package.json | 2 -- src/models/base.js | 35 +++++++++++++++++++---------------- src/models/hasTimes.js | 2 +- src/models/issue.js | 4 ++-- src/models/mergeRequest.js | 4 ++-- src/models/owner.js | 3 +++ src/models/project.js | 8 +++++--- src/models/report.js | 2 +- 8 files changed, 33 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 8dc55e8..157e801 100755 --- a/package.json +++ b/package.json @@ -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..e823bc7 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,17 @@ 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 = 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)); + }) }).catch(err => reject(err)); }); } @@ -134,8 +135,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); diff --git a/src/models/hasTimes.js b/src/models/hasTimes.js index 87cd1fe..2d8ca97 100755 --- a/src/models/hasTimes.js +++ b/src/models/hasTimes.js @@ -34,7 +34,7 @@ class hasTimes extends Base { */ getStats() { let promise = this.get(`projects/${this.data.project_id}/${this._type}/${this.iid}/time_stats`); - promise.then(response => this.stats = response.body); + promise.then(response => response.json()).then(response => this.stats = response); return promise; } 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..45190e3 100755 --- a/src/models/owner.js +++ b/src/models/owner.js @@ -37,6 +37,7 @@ 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; @@ -57,6 +58,7 @@ 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(); @@ -122,6 +124,7 @@ class owner extends Base { getProjectsByGroup() { return this.parallel(this.groups, (group, done) => { this.get(`groups/${group.id}/projects`) + .then(response => response.json()) .then(projects => { this.projects = this.projects.concat(projects.body); done(); 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..8fa8b28 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; } From c443babf24ae4d096c4d709b45e5a1067b63f61d Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Thu, 26 Sep 2019 11:30:08 +0200 Subject: [PATCH 02/16] Fixed a bug --- src/models/owner.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/owner.js b/src/models/owner.js index 45190e3..0f2c801 100755 --- a/src/models/owner.js +++ b/src/models/owner.js @@ -39,8 +39,8 @@ class owner extends Base { 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'); From d4469e160acbf074d47c4040ec4b84f9989019c0 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Thu, 26 Sep 2019 11:34:17 +0200 Subject: [PATCH 03/16] Remove body part --- src/models/owner.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/models/owner.js b/src/models/owner.js index 0f2c801..15a55a1 100755 --- a/src/models/owner.js +++ b/src/models/owner.js @@ -60,9 +60,9 @@ class owner extends Base { 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); @@ -126,7 +126,7 @@ class owner extends Base { this.get(`groups/${group.id}/projects`) .then(response => response.json()) .then(projects => { - this.projects = this.projects.concat(projects.body); + this.projects = this.projects.concat(projects); done(); }) .catch(e => done(e)); From 6a3c05697de0bccc48f91a49e7f32cf7cf2af8a3 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Thu, 26 Sep 2019 11:43:04 +0200 Subject: [PATCH 04/16] Use body for getStats() --- src/models/hasTimes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/hasTimes.js b/src/models/hasTimes.js index 2d8ca97..87cd1fe 100755 --- a/src/models/hasTimes.js +++ b/src/models/hasTimes.js @@ -34,7 +34,7 @@ class hasTimes extends Base { */ getStats() { let promise = this.get(`projects/${this.data.project_id}/${this._type}/${this.iid}/time_stats`); - promise.then(response => response.json()).then(response => this.stats = response); + promise.then(response => this.stats = response.body); return promise; } From 034f5caf87178c76e36777e5ab1f75357f841be2 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Tue, 7 Jul 2020 12:00:28 +0200 Subject: [PATCH 05/16] Add an extra function for populating notes --- src/models/report.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/models/report.js b/src/models/report.js index 8fa8b28..cf888ee 100755 --- a/src/models/report.js +++ b/src/models/report.js @@ -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 From c1706daf144f1c87a6f0b47216d1d9f8fd8ef4a6 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Thu, 10 Nov 2022 11:13:49 +0100 Subject: [PATCH 06/16] fix: for getting more than 100 items --- src/models/owner.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/models/owner.js b/src/models/owner.js index 15a55a1..80be53b 100755 --- a/src/models/owner.js +++ b/src/models/owner.js @@ -123,8 +123,7 @@ class owner extends Base { */ getProjectsByGroup() { return this.parallel(this.groups, (group, done) => { - this.get(`groups/${group.id}/projects`) - .then(response => response.json()) + this.all(`groups/${group.id}/projects`) .then(projects => { this.projects = this.projects.concat(projects); done(); From 8cc240a9a350769a2adf6fe6be4eb15f379105ec Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Tue, 16 Jan 2024 14:37:41 +0100 Subject: [PATCH 07/16] fix: for getting more than 100 items in /groups --- package.json | 2 +- src/models/owner.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 157e801..5056400 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" diff --git a/src/models/owner.js b/src/models/owner.js index 15a55a1..80be53b 100755 --- a/src/models/owner.js +++ b/src/models/owner.js @@ -123,8 +123,7 @@ class owner extends Base { */ getProjectsByGroup() { return this.parallel(this.groups, (group, done) => { - this.get(`groups/${group.id}/projects`) - .then(response => response.json()) + this.all(`groups/${group.id}/projects`) .then(projects => { this.projects = this.projects.concat(projects); done(); From 2dc5f980c290bc884e07e98334196b60d97fc545 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Tue, 16 Jan 2024 15:47:21 +0100 Subject: [PATCH 08/16] fix: use header getter for ES6 --- src/models/base.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/base.js b/src/models/base.js index e823bc7..1968272 100755 --- a/src/models/base.js +++ b/src/models/base.js @@ -96,7 +96,7 @@ class base { this.get(path, 1, perPage).then(response => { response.json().then(data => { data.forEach(item => collect.push(item)); - let pages = parseInt(response.headers['x-total-pages']); + let pages = parseInt(response.headers.get('x-total-pages')); if (pages === 1) return resolve(collect); @@ -151,7 +151,7 @@ class base { */ setDump(response, key) { this.config.setDump(key, { - headers: response.headers, + headers: Object.fromEntries(response.headers.entries()), body: response.body }); } From 9297f5fb619d4e0c123e81d70b63c04915a619ca Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Tue, 16 Jan 2024 16:04:13 +0100 Subject: [PATCH 09/16] fix: callback error --- src/models/base.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models/base.js b/src/models/base.js index 1968272..c5b4579 100755 --- a/src/models/base.js +++ b/src/models/base.js @@ -94,9 +94,10 @@ class base { let collect = []; this.get(path, 1, perPage).then(response => { + let pages = response.headers.get('x-total-pages') + response.json().then(data => { data.forEach(item => collect.push(item)); - let pages = parseInt(response.headers.get('x-total-pages')); if (pages === 1) return resolve(collect); From 01d4dac1c7a6c47f5df7f2c8ba765f8a0009c2e5 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Tue, 16 Jan 2024 16:41:41 +0100 Subject: [PATCH 10/16] fix: callback error --- src/models/base.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/models/base.js b/src/models/base.js index c5b4579..c6771fe 100755 --- a/src/models/base.js +++ b/src/models/base.js @@ -94,11 +94,11 @@ class base { let collect = []; this.get(path, 1, perPage).then(response => { - let pages = response.headers.get('x-total-pages') - - response.json().then(data => { + response.json().then(async data => { data.forEach(item => collect.push(item)); + let pages = await response.headers.get('x-total-pages') + if (pages === 1) return resolve(collect); let tasks = base.createGetTasks(path, pages, 2, perPage); From 18582dc542e9641f16446f84ee96c9af6f31910a Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Tue, 16 Jan 2024 16:49:06 +0100 Subject: [PATCH 11/16] fix: callback error --- src/models/base.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/base.js b/src/models/base.js index c6771fe..f8b498d 100755 --- a/src/models/base.js +++ b/src/models/base.js @@ -94,10 +94,10 @@ class base { let collect = []; this.get(path, 1, perPage).then(response => { - response.json().then(async data => { + response.json().then(data => { data.forEach(item => collect.push(item)); - let pages = await response.headers.get('x-total-pages') + let pages = response.headers.get('x-total-pages') if (pages === 1) return resolve(collect); From 835855a476423ac0a0fe52787247cef9dc0b5597 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Tue, 20 Feb 2024 11:56:01 +0100 Subject: [PATCH 12/16] feat: support delete time spent https://docs.gitlab.com/ee/user/project/time_tracking.html#delete-time-spent From GitLab 15.1, there is delete button. --- src/models/hasTimes.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/models/hasTimes.js b/src/models/hasTimes.js index 87cd1fe..340f2be 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,26 @@ 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)}`); let time = new Time(null, created, note, this, this.config); time.seconds = Time.parse(timeString, 8, 5, 4); From 03acdeb8684c3bc4ef2379520683ed404609bf23 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Mon, 27 May 2024 12:01:37 +0200 Subject: [PATCH 13/16] Update hasTimes.js fix: use logical OR instead of bitwise OR operator --- src/models/hasTimes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/hasTimes.js b/src/models/hasTimes.js index 340f2be..339253c 100755 --- a/src/models/hasTimes.js +++ b/src/models/hasTimes.js @@ -84,7 +84,7 @@ class hasTimes extends Base { if(delMatch && delMatch[2]) created = moment(delMatch[2]); // collect minus items - let minusFlag = subMatch | delMatch; + let minusFlag = subMatch || delMatch; let minusMatch = minusFlag ? subMatch ? subMatch[1] : delMatch ? delMatch[1] : 0 : 0; // create a time string and a time object From b726bf01d9212e249d7d31d78734db84e22c9841 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Mon, 27 May 2024 12:58:33 +0200 Subject: [PATCH 14/16] Update hasTimes.js fix: handle the minus time in deletion --- src/models/hasTimes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models/hasTimes.js b/src/models/hasTimes.js index 339253c..ebf7935 100755 --- a/src/models/hasTimes.js +++ b/src/models/hasTimes.js @@ -89,6 +89,7 @@ class hasTimes extends Base { // create a time string and a time object 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); From 93bd7390fa47fe6b8291dfeafe9d90f197e6a5b3 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Mon, 26 May 2025 16:18:03 +0200 Subject: [PATCH 15/16] chore: use git+https instead of git --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5056400..8771701 100755 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "cli-cursor": "^2.1.0", "cli-table": "^0.3.1", "colors": "^1.3.1", - "commander": "kriskbx/commander.js", + "commander": "git+https://github.com/kriskbx/commander.js", "csv-string": "^2.3.2", "find-in-files": "^0.4.0", "hash-sum": "^1.0.2", From 8df1d87a1a3acc1dedf97c3f7873535d72df43c8 Mon Sep 17 00:00:00 2001 From: HongKee Moon Date: Mon, 26 May 2025 16:34:29 +0200 Subject: [PATCH 16/16] chore: update commander.js --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8771701..db3c196 100755 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "cli-cursor": "^2.1.0", "cli-table": "^0.3.1", "colors": "^1.3.1", - "commander": "git+https://github.com/kriskbx/commander.js", + "commander": "^2.19.0", "csv-string": "^2.3.2", "find-in-files": "^0.4.0", "hash-sum": "^1.0.2",