diff --git a/Dockerfile b/Dockerfile index 605215f..17f1e79 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:8.2.1-alpine -ENV GTT_VERSION 1.6.11 +ENV GTT_VERSION 1.7.0 RUN yarn global add --prefix /usr/local "gitlab-time-tracker@$GTT_VERSION" diff --git a/package-lock.json b/package-lock.json index 9076704..3cdc760 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gitlab-time-tracker", - "version": "1.6.5", + "version": "1.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d423b78..ddf9b53 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitlab-time-tracker", - "version": "1.6.11", + "version": "1.7.0", "description": "A command line interface for GitLabs time tracking feature.", "bugs": { "url": "https://github.com/kriskbx/gitlab-time-tracker/issues" diff --git a/readme.md b/readme.md index 6c41e93..3253ffd 100755 --- a/readme.md +++ b/readme.md @@ -717,6 +717,23 @@ timeFormat: "[%sign][%minutes_overall]" 1095 ``` +##### `[%hours_overall:2]`, `[%days_overall:3]` + +You can ceil any float value by adding the number of decimals to keep separated with a `:`. + +**Example config:** + +```yaml +timeFormat: "[%sign][%hours_overall:2]" +``` + +**Example outputs:** + +```shell +0,51 +18,25 +``` + ## how to use gtt as a library Add as a dependency using yarn: diff --git a/src/gtt-list.js b/src/gtt-list.js new file mode 100644 index 0000000..82c8575 --- /dev/null +++ b/src/gtt-list.js @@ -0,0 +1,39 @@ +const program = require('commander'); +const colors = require('colors'); +const moment = require('moment'); +const Table = require('cli-table'); + + +const Config = require('./include/file-config'); +const Cli = require('./include/cli'); +const Tasks = require('./include/tasks'); + +program + .arguments('[project]') + .option('--verbose', 'show verbose output') + .option('-t, --type ', 'specify resource type: issue, merge_request') + .option('-c, --closed', 'show closed issues (instead of opened only)') + .option('--my', 'show only issues assigned to me') + .parse(process.argv); + +Cli.verbose = program.verbose; + +let config = new Config(process.cwd()), + tasks = new Tasks(config), + type = program.type ? program.type : 'issue', + project = program.args[0]; + +tasks.list(project, program.closed ? 'closed' : 'opened', program.my, program.type) + .then(issues => { + let table = new Table({ + style : {compact : true, 'padding-left' : 1} + }); + if (issues.length == 0) { + console.log("No ${programm.type}s found."); + } + issues.forEach(issue => { + table.push([issue.iid.toString().magenta, issue.title.green + "\n" + issue.data.web_url.gray, issue.state]) + }) + console.log(table.toString()); + }) + .catch(error => Cli.error(error)); \ No newline at end of file diff --git a/src/gtt-log.js b/src/gtt-log.js index cc7547c..16c6b41 100755 --- a/src/gtt-log.js +++ b/src/gtt-log.js @@ -18,9 +18,7 @@ Cli.verbose = program.verbose; let config = new Config(__dirname).set('hoursPerDay', program.hours_per_day), tasks = new Tasks(config), - timeFormat = config.set('timeFormat', program.time_format).get('timeFormat'); - -timeFormat = _.isObject(timeFormat) && timeFormat['log'] ? timeFormat['log'] : timeFormat; + timeFormat = config.set('timeFormat', program.time_format).get('timeFormat', 'log'); function toHumanReadable(input) { return Time.toHumanReadable(Math.ceil(input), config.get('hoursPerDay'), timeFormat); diff --git a/src/gtt.js b/src/gtt.js index 0dedde0..af3a444 100755 --- a/src/gtt.js +++ b/src/gtt.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -const version = '1.6.11'; +const version = '1.7.0'; const program = require('commander'); program @@ -11,6 +11,7 @@ program .command('stop', 'stop monitoring time') .command('resume [project]', 'resume monitoring time for last stopped record') .command('cancel', 'cancel and discard active monitoring time') + .command('list [project]', 'list all open issues') .command('log', 'log recorded time records') .command('sync', 'sync local time records to GitLab') .command('edit [id]', 'edit time record by the given id') diff --git a/src/include/tasks.js b/src/include/tasks.js index 79a3b16..2bc2fe9 100755 --- a/src/include/tasks.js +++ b/src/include/tasks.js @@ -196,6 +196,11 @@ class tasks { }); } + list(project, state, my, type) { + this.config.set('project', project); + return (new classes[type](this.config, {})).list(this.config.get('project'), state, my); + } + /** * * @param project diff --git a/src/models/issue.js b/src/models/issue.js index ddf7c5c..994cab0 100755 --- a/src/models/issue.js +++ b/src/models/issue.js @@ -29,6 +29,24 @@ class issue extends hasTimes { return promise; } + list(project, state, my) { + return new Promise((resolve, reject) => { + let promise; + const query = `scope=${my ? "assigned-to-me" : "all"}&state=${state}`; + if (project) { + promise = this.get(`projects/${encodeURIComponent(project)}/issues?${query}`); + } else { + promise = this.get(`issues/?${query}`); + } + promise.then(response => { + const issues = response.body.map(issue => new this.constructor(this.config, issue)) + resolve(issues) + }); + promise.catch(error => reject(error)) + }) + } + + /* * properties */ diff --git a/src/models/mergeRequest.js b/src/models/mergeRequest.js index 5ea2f95..a67eb8d 100755 --- a/src/models/mergeRequest.js +++ b/src/models/mergeRequest.js @@ -25,6 +25,23 @@ class mergeRequest extends hasTimes { return promise; } + + list(project, state, my) { + return new Promise((resolve, reject) => { + let promise; + const query = `scope=${my ? "assigned-to-me" : "all"}&state=${state}`; + if (project) { + promise = this.get(`projects/${encodeURIComponent(project)}/merge_requests?${query}`); + } else { + promise = this.get(`merge_requests/?${query}`); + } + promise.then(response => { + const issues = response.body.map(issue => new this.constructor(this.config, issue)) + resolve(issues) + }); + promise.catch(error => reject(error)) + }) + } /* * properties diff --git a/src/models/time.js b/src/models/time.js index 7ecb7b2..7bd267e 100755 --- a/src/models/time.js +++ b/src/models/time.js @@ -5,6 +5,8 @@ const defaultTimeFormat = '[%sign][%days>d ][%hours>h ][%minutes>m ][%seconds>s] const mappings = ['complete', 'sign', 'weeks', 'days', 'hours', 'minutes', 'seconds']; const regex = /^(?:([-])\s*)?(?:(\d+)w\s*)?(?:(\d+)d\s*)?(?:(\d+)h\s*)?(?:(\d+)m\s*)?(?:(\d+)s\s*)?$/; const conditionalRegex = /(\[\%([^\>\]]*)\>([^\]]*)\])/ig; +const roundedRegex = /(\[\%([^\>\]]*)\:([^\]]*)\])/ig; +const conditionalSimpleRegex = /([0-9]*)\>(.*)/ig; const defaultRegex = /(\[\%([^\]]*)\])/ig; Number.prototype.padLeft = function (n, str) { @@ -121,6 +123,20 @@ class time { inserts.seconds = ((input % secondsInADay) % secondsInAnHour) % secondsInAMinute; inserts.Seconds = inserts.seconds.padLeft(2, 0); + // rounded + while ((match = roundedRegex.exec(format)) !== null) { + if (match.index === roundedRegex.lastIndex) roundedRegex.lastIndex++; + let time, conditionalMatch, decimals = match[3]; + + if ((conditionalMatch = conditionalSimpleRegex.exec(decimals)) !== null) { + decimals = conditionalMatch[1] + } + + decimals = parseInt(decimals); + time = Math.ceil(inserts[match[2]] * Math.pow(10, decimals)) / Math.pow(10, decimals); + output = output.replace(match[0], time !== 0 && conditionalMatch ? time + conditionalMatch[2] : time); + } + // conditionals while ((match = conditionalRegex.exec(format)) !== null) { if (match.index === conditionalRegex.lastIndex) conditionalRegex.lastIndex++;