├── .dockerignore ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── documentation.md ├── package.json ├── preview ├── demo.gif ├── icon.gvdesign └── icon.png ├── readme.md ├── spec ├── _setup.spec.js └── include │ ├── config.spec.js │ └── file-config.spec.js ├── src ├── gtt-cancel.js ├── gtt-config.js ├── gtt-create.js ├── gtt-delete.js ├── gtt-edit.js ├── gtt-log.js ├── gtt-report.js ├── gtt-resume.js ├── gtt-start.js ├── gtt-status.js ├── gtt-stop.js ├── gtt-sync.js ├── gtt.js ├── include │ ├── cli.js │ ├── config.js │ ├── file-config.js │ ├── filesystem.js │ └── tasks.js ├── models │ ├── base.js │ ├── baseFrame.js │ ├── frame.js │ ├── frameCollection.js │ ├── hasTimes.js │ ├── issue.js │ ├── mergeRequest.js │ ├── owner.js │ ├── project.js │ ├── report.js │ ├── reportCollection.js │ └── time.js └── output │ ├── base.js │ ├── csv.js │ ├── dump.js │ ├── markdown.js │ ├── pdf.js │ ├── styles │ ├── highlight │ │ └── default.css │ └── layout │ │ └── default.css │ ├── table.js │ └── xlsx.js ├── upgrade.md └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | **/** -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /.idea/ 3 | **/node_modules 4 | .gtt.yml 5 | .DS_STORE 6 | yarn-error.log 7 | **/._* 8 | out 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "6.11" 5 | - "8" 6 | - "10" 7 | cache: yarn 8 | script: 9 | - yarn build 10 | deploy: 11 | provider: releases 12 | api_key: 13 | secure: "q1kPg8zNInr1zrEsFvuX4r/Sp0DntK9m8uIi6PqpFNci3HnKEx6aLTe6eVaDpBfMyk+OqAxhyyytDmH36OhfEeDFzuR4wnzRM6LI0pJa9faWohY8CXke2/V6mWRGDorSDKUJ2FriJGL4p/hE2jw1ms/tQh7EsYE1ch3HcIKwkuHKhOVw84I4wW0VNHBMQ/RSnWnUENVO9Tfj/jHlZyltADAgJxRhQd9xg4lTyy95lMoM1eTjyrUiiVg9MPxLj8Mzv5mXbRCASxTziS/FBPrOOB8sMIyfN7cL8/e8hfMTuiWBmfWNaRrQTenLpm1IXobV8oANKg6DqS+cawN/zp9AIXsfzOksQib2tswbCBNY9Vj3tNSTpTLuyTFVvvlB/vNB715oTJX0cuHp8e4nW6AqP1DKUTTELwR1lji0YyGLIFQEw9RcTBP0e3LHXuXjgE80iypsZQQVNIHgIiNNvc1VEMJYECkeHsXt5LblMoJcw+tI7JL6LL3gv3g9j/3/dLvlfTBiXJPTxTlPl0zFvM31t4edGkmfVIRFxjpCGnrOfAnclmDt/r8u3A4i29FovbfIRTkEA+naQQWJvjXmkqTTBbAtOCFROT23EcydEoaE2uYI6Dc0AGPnoeZ3NSTRNvT2SjnotyXT0vVFfDIuB4FLtrX7kNEtHvNkBNzptS28i8E=" 14 | file_glob: true 15 | file: out/* 16 | skip_cleanup: true 17 | on: 18 | tags: true 19 | after_success: 20 | - yarn run coveralls 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## How to test the basic functions of gtt 4 | 5 | Create a new project on GitLab if you haven't done so already and write down the project path. e.g. `kriskbx/example-project`. To test groups and subgroups, create a new group with at least one subgroup. Add a new project to both the parent group (e.g. `example-group`) and the subgroup (e.g. `example-group/example-subgroup`) and write down the paths of all the projects and groups. Create at least two issues (write down their id e.g. `42`, `43`) and one merge request (e.g. `13`) in all projects. Or just use our public group over here: https://gitlab.com/gtt 6 | 7 | Then run the following basic commands and check that they work as expected: 8 | 9 | ``` 10 | # Test config command 11 | gtt config 12 | 13 | # Test basic time tracking 14 | gtt start "kriskbx/example-project" 42 15 | gtt stop 16 | gtt log 17 | 18 | # Test cancelling 19 | gtt start "kriskbx/example-project" 42 20 | gtt cancel 21 | gtt log 22 | 23 | # Test merge request 24 | gtt start --type=merge_request "kriskbx/example-project" 13 25 | gtt stop 26 | gtt log 27 | 28 | # Test issue creation 29 | gtt create "krisbxkbx/example-project" "New Issue" 30 | gtt stop 31 | gtt log 32 | 33 | # Test editing 34 | gtt edit 35 | 36 | # Test deletion 37 | gtt start "kriskbx/example-project" 42 38 | gtt stop 39 | gtt delete 40 | 41 | # Test sync, check out issues on GitLab if changes are synced correctly 42 | gtt sync 43 | 44 | # Test basic report 45 | gtt report "kriskbx/example-project" 46 | 47 | # Test report for a single issue and multiple issues 48 | gtt report "kriskbx/example-project" 42 49 | gtt report "kriskbx/example-project" 42 43 50 | 51 | # Test report for a group 52 | gtt report --type=group "example-group" 53 | gtt report --type=group --subgroups "example-group" 54 | 55 | # Test combined reports 56 | gtt report "kriskbx/example-project" "example-group/example-project" 57 | 58 | # Test outputs 59 | gtt report --output=table "kriskbx/example-project" 60 | gtt report --output=markdown "kriskbx/example-project" 61 | gtt report --output=csv "kriskbx/example-project" 62 | gtt report --output=pdf --file=test.pdf "kriskbx/example-project" 63 | gtt report --output=xlsx --file=test.xlsx "kriskbx/example-project" 64 | 65 | # Test timeframes (adjust the values so it includes/excludes your issues) 66 | gtt report --from="2020-06-02" --to="2020-06-03" "kriskbx/example-project" 67 | 68 | # Test filtering (you might need to add milestones, labels and additional stuff to properly test this) 69 | gtt report --closed "kriskbx/example-project" 70 | gtt report --user=username "kriskbx/example-project" 71 | gtt report --milestone=milestone_name "kriskbx/example-project" 72 | gtt report --include_by_labels=critical "kriskbx/example-project" 73 | gtt report --exclude_by_labels=ignore "kriskbx/example-project" 74 | gtt report --query=issues "kriskbx/example-project" 75 | ``` 76 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.2.1-alpine 2 | 3 | ENV GTT_VERSION 1.7.39 4 | ENV EDITOR vi 5 | 6 | WORKDIR /pwd 7 | 8 | RUN yarn global add --prefix /usr/local "gitlab-time-tracker@$GTT_VERSION" 9 | 10 | VOLUME ["/root", "/pwd"] 11 | ENTRYPOINT ["gtt"] 12 | CMD ["--help"] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /documentation.md: -------------------------------------------------------------------------------- 1 | # gtt documentation 2 | 3 | ## contents 4 | 5 | * [requirements](#requirements) 6 | * [installation](#installation) 7 | * [updating](#updating) 8 | * [docker](#docker) 9 | * [commands](#commands) 10 | * [I) configuration](#i-configuration) 11 | * [II) time tracking](#ii-time-tracking) 12 | * [III) reports](#iii-reports) 13 | * [configuration](#configuration) 14 | * [config file](#config-file) 15 | * [time format](#time-format) 16 | * [how to use gtt as a library](#how-to-use-gtt-as-a-library) 17 | * [dumps](#dumps) 18 | * [faqs](#faqs) 19 | * [contributing](#contributing) 20 | * [support further development 🍺](#support-further-development) 21 | * [license](#license) 22 | 23 | ## requirements 24 | 25 | * [node.js](https://nodejs.org/en/download) version >= 6 26 | * [npm](https://github.com/npm/npm) or [yarn](https://yarnpkg.com/en/docs/install) 27 | 28 | ## installation 29 | 30 | Install the gtt command line interface using yarn: 31 | 32 | ```shell 33 | yarn global add gitlab-time-tracker --prefix /usr/local 34 | ``` 35 | 36 | ... or download a compiled binary from [here](https://github.com/kriskbx/gitlab-time-tracker/releases) and put it into your `PATH`. 37 | 38 | Run the config command to create a config file and open it in your default editor. 39 | In linux terminal, you must set your preferred editor in the environment. For example, use `export EDITOR=vim` to edit the files with vim (put this in `.bashrc` or similar to have it always configured). 40 | If nothing happens, open the file manually: `~/.local/share/.gtt` - on Windows: `C:\Users\YourUserName\.gtt\config.yml` 41 | 42 | ```shell 43 | gtt config 44 | ``` 45 | 46 | Add your GitLab API url and your [GitLab API personal token](https://gitlab.com/profile/personal_access_tokens) 47 | to the config file and save it afterwards. **You need to activate the `api` scope when creating your access token.** 48 | 49 | ```yaml 50 | url: https://gitlab.com/api/v4/ 51 | token: 01234567891011 52 | ``` 53 | 54 | ## updating 55 | 56 | **Updating from version <= 1.5? Please [click here](https://github.com/kriskbx/gitlab-time-tracker/blob/master/upgrade.md)!** 57 | 58 | Update gtt via yarn: 59 | 60 | ```shell 61 | yarn global upgrade gitlab-time-tracker 62 | ``` 63 | 64 | ## docker 65 | 66 | You don't need to have node and gtt installed on your system in order to use gtt, 67 | you can use the official [Docker image](https://hub.docker.com/r/kriskbx/gitlab-time-tracker) as well: 68 | 69 | ```shell 70 | docker run \ 71 | --rm -it \ 72 | -v ~:/root \ 73 | -v $(pwd):/pwd \ 74 | kriskbx/gitlab-time-tracker \ 75 | --help 76 | ``` 77 | 78 | `--rm` removes the container after running, `-it` makes it interactive, `-v ~:/root` mounts your home directory to the 79 | home directory inside the container, `-v $(pwd):/pwd` mounts current directory inside the container to gtt be able to read local config. If you want to store the config in another place, mount another directory: 80 | 81 | 82 | ```shell 83 | docker run \ 84 | --rm -it \ 85 | -v /path/to/gtt-config:/root \ 86 | kriskbx/gitlab-time-tracker \ 87 | --help 88 | ``` 89 | 90 | ... or use a Docker volume: 91 | 92 | ```shell 93 | docker volume create gtt-config 94 | 95 | docker run \ 96 | --rm -it \ 97 | -v gtt-config:/root \ 98 | kriskbx/gitlab-time-tracker \ 99 | --help 100 | ``` 101 | 102 | I highly recommend creating an alias and adding it to your `bashrc`: 103 | 104 | ```shell 105 | echo "alias gtt='docker run --rm -it -v ~:/root -v $(pwd):/pwd kriskbx/gitlab-time-tracker'" >>~/.bashrc 106 | ``` 107 | 108 | Now you can simply write `gtt` instead of the bulky Docker command before. Try it out: `gtt --help` 109 | 110 | **Note:** If you want to save reports via the `--file` parameter, make sure to save them in `/root` or another 111 | mounted directory that you have access to on your host machine. Take a look at the [Docker documentation](https://docs.docker.com/engine/tutorials/dockervolumes/) about how Dopcker handles data and volumes. 112 | 113 | ## commands 114 | 115 | ### I) configuration 116 | 117 | *[Click here](#configuration) for a complete list of configuration options.* 118 | 119 | **Edit/create the global configuration file, stored in your home directory:** 120 | 121 | ```shell 122 | gtt config 123 | ``` 124 | 125 | If nothing happens, open the file manually: `~/.gtt/config.yml` - on Windows: `C:\Users\YourUserName\.gtt\config.yml` 126 | 127 | **Edit/create a local configuration file `.gtt.yml` in the current working directory:** 128 | 129 | ```shell 130 | gtt config --local 131 | ``` 132 | 133 | If a local configuration file is present it will extend of the global one and overwrites global settings. 134 | If you don't want to extend the global configuration file, set the `extend` option in your local config to `false`. 135 | So you can use gtt easily on a per project basis. Make sure to add .gtt.yml to your gitignore file if using a local configuration. 136 | 137 | ### II) time tracking 138 | 139 | Time tracking enables you to monitor the time you spent on an issue or merge request locally. 140 | When you're done, you can sync these time records to GitLab: gtt adds the time spent to the given 141 | issue or merge request and automagically keeps everything in sync with your local data. 142 | 143 | Did you forgot to stop time monitoring locally and accidentally synced it to GitLab? 144 | No worries, just edit the local record and run sync again. 145 | 146 | **Start local time monitoring for the given project and issue id:** 147 | 148 | ```shell 149 | gtt start "kriskbx/example-project" 15 150 | gtt start 15 151 | ``` 152 | 153 | If you configured a project in your config you can omit the project. 154 | 155 | **Start local time monitoring for a merge request:** 156 | 157 | ```shell 158 | gtt start --type=merge_request "kriskbx/example-project" 15 159 | gtt start --type=merge_request 15 160 | ``` 161 | 162 | **Start local time monitoring and create a new issue or merge request with the given title:** 163 | 164 | ```shell 165 | gtt create "kriskbx/example-project" "New Issue" 166 | gtt create "New Issue" 167 | gtt create --type=merge_request "kriskbx/example-project" "New Issue" 168 | gtt create --type=merge_request "New Issue" 169 | ``` 170 | 171 | **Show the current time monitoring status:** 172 | 173 | ```shell 174 | gtt status 175 | ``` 176 | 177 | **Stop local time monitoring and save as a new time record:** 178 | 179 | ```shell 180 | gtt stop 181 | ``` 182 | 183 | **Cancel local time monitoring and discard the time record:** 184 | 185 | ```shell 186 | gtt cancel 187 | ``` 188 | 189 | **Show a list of all local time records (including their ids and meta data):** 190 | 191 | ```shell 192 | gtt log 193 | ``` 194 | Note: gtt log uses UTC as default timezone. If you want to display the times in a different timezone, make sure to use `timezone: "Europe/Berlin"` in your config. 195 | 196 | **Edit a local time record by the given id:** 197 | 198 | ```shell 199 | gtt edit 2XZkV5LNM 200 | gtt edit 201 | ``` 202 | 203 | You can omit the id to edit the last added time record. 204 | 205 | **Delete a local time record by the given id:** 206 | 207 | ```shell 208 | gtt delete 2XZkV5LNM 209 | gtt delete 210 | ``` 211 | 212 | You can omit the id to delete the last added time record. 213 | 214 | **Sync local time records with time tracking data on Gitlab:** 215 | 216 | ```shell 217 | gtt sync 218 | gtt sync --proxy="http://localhost:8888" 219 | ``` 220 | 221 | You can pass an url to the proxy option if you want to use a proxy server. 222 | 223 | ### III) reports 224 | 225 | Get a report for the time tracking data stored on GitLab. If you want to include your local data make sure to sync it 226 | before running the report command. The report command has a lot of options to filter data and output, make sure to 227 | read through the docs before using it. 228 | 229 | #### Get a report 230 | 231 | ```shell 232 | gtt report ["namespace/project"] [issue_id] 233 | gtt report "kriskbx/example-project" 234 | gtt report "kriskbx/example-project" 145 235 | gtt report "kriskbx/example-project" 145 209 45 54 236 | gtt report 237 | gtt report 145 238 | gtt report 123 345 123 239 | ``` 240 | 241 | If you configured a project in your config file you can omit it. By passing a or multiple ids you can limit the 242 | report to the given issues or merge requests. *Note: if you're passing a or multiple ids, gtt will fetch issues 243 | with these ids by default. If you want to fetch merge requests you can change the query type using the 244 | `--query` option.* 245 | 246 | #### Query groups and subgroups 247 | 248 | ```shell 249 | gtt report ["namespace"] --type=group 250 | gtt report example-group --type=group 251 | gtt report example-group --type=group --subgroups 252 | ``` 253 | 254 | Query all projects from the given group and combine the data into a single report. The option `--subgroups` 255 | includes subgroups. 256 | 257 | #### Query multiple projects or groups and combine the data in one report 258 | 259 | ```shell 260 | gtt report ["namespace/project"] ["namespace/project"] ... 261 | gtt report "kriskbx/example-project" "kriskbx/example-project-2" 262 | gtt report ["namespace"] ["namespace"] ... --type=group 263 | gtt report example-group example-group-2 --type=group 264 | ``` 265 | 266 | *Hint: use the `project_id` or `project_namespace` columns in your report.* 267 | 268 | #### Choose an output for your report 269 | 270 | ```shell 271 | gtt report --output=table 272 | gtt report --output=markdown 273 | gtt report --output=csv 274 | gtt report --output=pdf --file=filename.pdf 275 | gtt report --output=xlsx --file=filename.xlsx 276 | ``` 277 | 278 | Defaults to `table`. `csv` and `markdown` can be printed to stdout, `pdf` and `xlsx` need the file parameter. 279 | 280 | #### Print the output to a file 281 | 282 | ```shell 283 | gtt report --file=filename.txt 284 | ``` 285 | 286 | #### Only get time records from the given time frame 287 | 288 | ```shell 289 | gtt report --from="2017-03-01" --to="2017-04-01" 290 | ``` 291 | 292 | *Note: `--from` defaults to 1970-01-01, `--to` defaults to now. Make sure to use an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compatible time/date format.* 293 | 294 | There are some quick shorthands: 295 | 296 | ```shell 297 | gtt report --today 298 | gtt report --this_week 299 | gtt report --this_month 300 | ``` 301 | 302 | #### Include closed issues/merge requests 303 | 304 | ```shell 305 | gtt report --closed 306 | ``` 307 | 308 | #### Limit to the given user 309 | 310 | ```shell 311 | gtt report --user=username 312 | ``` 313 | 314 | #### Limit to the given milestone 315 | 316 | ```shell 317 | gtt report --milestone=milestone_name 318 | ``` 319 | 320 | #### Limit issues and merge requests to the given labels 321 | 322 | ```shell 323 | gtt report --include_by_labels=critical --include_by_labels=important 324 | ``` 325 | 326 | #### Exclude issues and merge requests that have the given labels 327 | 328 | ```shell 329 | gtt report --exclude_by_labels=wont-fix --exclude_by_labels=ignore 330 | ``` 331 | 332 | #### Limit the query to the given data type 333 | 334 | ```shell 335 | gtt report --query=issues 336 | gtt report --query=merge_requests 337 | ``` 338 | 339 | #### Include issues and merge requests in the report that have no time records 340 | 341 | ```shell 342 | gtt report --show_without_times 343 | ``` 344 | 345 | #### Limit the parts in the final report 346 | 347 | ```shell 348 | gtt report --report=stats --report=issues 349 | ``` 350 | 351 | *Note: These parts are available: `stats`, `issues`, `merge_requests`, `records`* 352 | 353 | #### Hide headlines in the report 354 | 355 | ```shell 356 | gtt report --no_headlines 357 | ``` 358 | 359 | #### Hide warnings in the report 360 | 361 | ```shell 362 | gtt report --no_warnings 363 | ``` 364 | 365 | #### Set date format for the report 366 | 367 | ```shell 368 | gtt report --date_format="DD.MM.YYYY HH:mm:ss" 369 | ``` 370 | 371 | *Note: [Click here](http://momentjs.com/docs/#/displaying/format/) for a further documentation on the date format.* 372 | 373 | #### Set time format for the report 374 | 375 | ```shell 376 | gtt report --time_format="[%sign][%days>d ][%hours>h ][%minutes>m ][%seconds>s]" 377 | ``` 378 | 379 | *Note: [Click here](#time-format) for a further documentation on the time format.* 380 | 381 | #### Set hours per day 382 | 383 | ```shell 384 | gtt report --hours_per_day=6 385 | ``` 386 | 387 | #### Set columns included in the time record table 388 | 389 | ```shell 390 | gtt report --record_columns=user --record_columns=date --record_columns=time 391 | ``` 392 | 393 | *Note: Available columns to choose from: `user`, `date`, `type`, `iid`, `project_id`, `project_namespace`, `time`* 394 | 395 | #### Set columns included in the issue table 396 | 397 | ```shell 398 | gtt report --issue_columns=iid --issue_columns=title --issue_columns=time_username 399 | ``` 400 | 401 | *Note: Available columns to choose from: `id`, `iid`, `title`, `project_id`, 402 | `project_namespace`, `description`, `labels`, `milestone`, `assignee`, `author`, 403 | `closed`, `updated_at`, `created_at`, `due_date`, `state`, `spent`, `total_spent`, `total_estimate`* 404 | 405 | *You can also include columns that show the total time spent by a specific user 406 | by following this convention: `time_username`* 407 | 408 | #### Set columns included in the merge request table 409 | 410 | ```shell 411 | gtt report --merge_request_columns=iid --merge_request_columns=title --merge_request_columns=time_username 412 | ``` 413 | 414 | *Note: Available columns to choose from: `id`, `iid`, `title`, `project_id`, 415 | `project_namespace`, `description`, `labels`, `milestone`, `assignee`, `author`, 416 | `updated_at`, `created_at`, `state`, `spent`, `total_spent`, `total_estimate`* 417 | 418 | *You can also include columns that show the total time spent by a specific user 419 | by following this convention: `time_username`* 420 | 421 | #### Add columns for each project member to issue and merge request table, including their total time spent 422 | 423 | ```shell 424 | gtt report --user_columns 425 | ``` 426 | 427 | #### Only include the given labels in the report 428 | 429 | ```shell 430 | gtt report --include_labels=pending --include_labels=approved 431 | ``` 432 | 433 | #### Exclude the given labels from the report 434 | 435 | ```shell 436 | gtt report --exclude_labels=bug --exclude_labels=feature 437 | ``` 438 | 439 | #### Use a proxy server 440 | 441 | ```shell 442 | gtt report --proxy="http://localhost:8080" 443 | ``` 444 | 445 | #### Output verbose debug information 446 | 447 | ```shell 448 | gtt report --verbose 449 | ``` 450 | 451 | ## configuration 452 | 453 | The configuration uses the [yaml file format](http://www.yaml.org/spec/1.2/spec.html). 454 | [Click here](#i-configuration) for more information how to edit and create a config file. 455 | 456 | ### Config File 457 | 458 | Here's a sample configuration file including all available options: 459 | 460 | ```yaml 461 | # Url to the gitlab api 462 | # Make sure there's a trailing slash 463 | # [required] 464 | url: http://gitlab.com/api/v4/ 465 | 466 | # GitLab personal api token 467 | # [required] 468 | token: abcdefghijklmnopqrst 469 | 470 | # Use a proxy server 471 | # defaults to false 472 | proxy: http://localhost:8080 473 | 474 | # Don't check SSL certificate 475 | # defaults to false 476 | insecure: true 477 | 478 | # Project 479 | # defaults to false 480 | project: namespace/projectname 481 | 482 | # Include closed issues and merge requests 483 | # defaults to false 484 | closed: true 485 | 486 | # Limit to the given milestone 487 | # defaults to false 488 | milestone: milestone_name 489 | 490 | # Exclude issues and merge requests that have the given labels 491 | # defaults to an empty array 492 | excludeByLabels: 493 | - wont-fix 494 | - ignore 495 | 496 | # Limit issues and merge requests to the given labels 497 | # defaults to an empty array 498 | includeByLabels: 499 | - critical 500 | - important 501 | 502 | # Limit the query to the given data type 503 | # available: issues, merge_requests 504 | # defaults to [issues, merge_requests] 505 | query: 506 | - issues 507 | 508 | # Limit the parts in the final report 509 | # available: stats, issues, merge_requests, records 510 | # defaults to [stats, issues, merge_requests, records] 511 | report: 512 | - stats 513 | - records 514 | 515 | # Hide headlines in the report 516 | # defaults to false 517 | noHeadlines: true 518 | 519 | # Hide warnings in the report 520 | # defaults to false 521 | noWarnings: true 522 | 523 | # Include issues and merge requests in the report that have no time records 524 | # defaults to false 525 | showWithoutTimes: true 526 | 527 | # Hours per day 528 | # defaults to 8 529 | hoursPerDay: 8 530 | 531 | # Days per week 532 | # defaults to 5 533 | daysPerWeek: 5 534 | 535 | # Weeks per month 536 | # defaults to 4 537 | weeksPerMonth: 4 538 | 539 | # Include the given columns in the issue table 540 | # See --issue_columns option for more information 541 | # defaults to iid, title, spent, total_estimate 542 | issueColumns: 543 | - iid 544 | - title 545 | - total_estimate 546 | 547 | # Include the given columns in the merge request table 548 | # See --merge_request_columns option for more information 549 | # defaults to iid, title, spent, total_estimate 550 | mergeRequestColumns: 551 | - iid 552 | - title 553 | - total_estimate 554 | 555 | # Include the given columns in the time record table 556 | # See --record_columns option for more information 557 | # defaults to user, date, type, iid, time 558 | recordColumns: 559 | - user 560 | - iid 561 | - time 562 | 563 | # Add columns for each project member to issue and 564 | # merge request table, including their total time spent 565 | # defaults to true 566 | userColumns: true 567 | 568 | # Date format 569 | # Click here for format options: http://momentjs.com/docs/#/displaying/format/ 570 | # defaults to DD.MM.YYYY HH:mm:ss 571 | dateFormat: DD.MM.YYYY HH:mm:ss 572 | 573 | # Time format 574 | # See time format configuration below 575 | # defaults to "[%sign][%days>d ][%hours>h ][%minutes>m ][%seconds>s]" 576 | timeFormat: "[%sign][%days>d ][%hours>h ][%minutes>m ][%seconds>s]" 577 | 578 | # Time format for different parts of the report 579 | # Instead of specifying one global time format you can specify one for every 580 | # part of the report and the log command 581 | timeFormat: 582 | log: "[%sign][%hours_overall]" 583 | stats: "[%sign][%days_overall]" 584 | issues: "[%sign][%hours_overall]" 585 | merge_requests: "[%sign][%hours_overall]" 586 | records: "[%sign][%days>d ][%hours>h ][%minutes>m ][%seconds>s]" 587 | 588 | # Change your timezone 589 | # default: UTC 590 | timezone: "Europe/Berlin" 591 | 592 | # Output type 593 | # Available: csv, table, markdown, pdf, xlsx 594 | # defaults to table 595 | output: markdown 596 | 597 | # Exclude the given labels from the report 598 | # defaults to an empty array 599 | excludeLabels: 600 | - bug 601 | - feature 602 | 603 | # Only include the given labels in the report 604 | # defaults to an empty array 605 | includeLabels: 606 | - pending 607 | - approved 608 | 609 | # Only works if using a local configuration file! 610 | # Extend the global configuration if set to true, pass a string to extend 611 | # the configuration file stored at the given path 612 | # defaults to true 613 | extend: true 614 | 615 | # Change number of concurrent connections/http queries 616 | # Note: Handle with care, we don't want to spam GitLabs API too much 617 | # defaults to 10 618 | _parallel: 20 619 | 620 | # Change rows per page (max. 100) 621 | # defaults to 100 622 | _perPage: 100 623 | 624 | # Verbose output 625 | _verbose: false 626 | 627 | # Check access token validity up front 628 | _checkToken: false 629 | 630 | # Skip parsing the issue/merge_request description for time records 631 | _skipDescriptionParsing: false 632 | ``` 633 | 634 | ### Time format 635 | 636 | ##### `[%sign]`, `[%days]`, `[%hours]`, `[%minutes]`, `[%seconds]` 637 | 638 | Prints out the raw data. 639 | 640 | **Example config:** 641 | 642 | ```yaml 643 | timeFormat: "[%sign][%days]d [%hours]h [%minutes]m [%seconds]s" 644 | ``` 645 | 646 | **Example outputs:** 647 | 648 | ```shell 649 | 0d 0h 30m 15s 650 | -1d 10h 15m 0s 651 | ``` 652 | 653 | ##### `[%days>string]`, `[%hours>string]`, `[%minutes>string]`, `[%seconds>string]` 654 | 655 | This is a conditional: it prints out the raw data and appends the given string if the 656 | data is greater than zero. 657 | 658 | **Example config:** 659 | 660 | ```yaml 661 | timeFormat: "[%sign][%days>d ][%hours>h ][%minutes>m ][%seconds>s]" 662 | ``` 663 | 664 | **Example outputs:** 665 | 666 | ```shell 667 | 30m 15s 668 | -1d 10h 15m 669 | ``` 670 | 671 | ##### `[%Days]`, `[%Hours]`, `[%Minutes]`, `[%Seconds]`, `[%Days>string]`, `[%Hours>string]`, `[%Minutes>string]`, `[%Seconds>string]` 672 | 673 | First letter uppercase adds leading zeros. 674 | 675 | **Example config:** 676 | 677 | ```yaml 678 | timeFormat: "[%sign][%Days]:[%Hours]:[%Minutes]:[%Seconds]" 679 | ``` 680 | 681 | **Example outputs:** 682 | 683 | ```shell 684 | 00:00:30:15 685 | -01:10:15:00 686 | ``` 687 | 688 | ##### `[%days_overall]`, `[%hours_overall]`, `[%minutes_overall]`, `[%seconds_overall]` 689 | 690 | Instead of printing out the second-, minute-, hour-, day-part of the duration this 691 | prints the complete time in either seconds, minutes, hours or days. 692 | 693 | **Example config:** 694 | 695 | ```yaml 696 | timeFormat: "[%sign][%minutes_overall]" 697 | ``` 698 | 699 | **Example outputs:** 700 | 701 | ```shell 702 | 30.25 703 | 1095 704 | ``` 705 | 706 | ##### `[%days_overall_comma]`, `[%hours_overall_comma]`, `[%minutes_overall_comma]`, `[%seconds_overall_comma]` 707 | 708 | Use a comma for float values instead of a point. (Useful for some european countries) 709 | 710 | **Example config:** 711 | 712 | ```yaml 713 | timeFormat: "[%sign][%minutes_overall]" 714 | ``` 715 | 716 | **Example outputs:** 717 | 718 | ```shell 719 | 30,25 720 | 1095 721 | ``` 722 | 723 | ##### `[%hours_overall:2]`, `[%days_overall:3]` 724 | 725 | You can ceil any float value by adding the number of decimals to keep separated with a `:`. 726 | 727 | **Example config:** 728 | 729 | ```yaml 730 | timeFormat: "[%sign][%hours_overall:2]" 731 | ``` 732 | 733 | **Example outputs:** 734 | 735 | ```shell 736 | 0,51 737 | 18,25 738 | ``` 739 | 740 | ## how to use gtt as a library 741 | 742 | Add as a dependency using yarn: 743 | 744 | ```shell 745 | yarn add gitlab-time-tracker 746 | ``` 747 | 748 | ... or using npm: 749 | 750 | ```shell 751 | npm install --save gitlab-time-tracker 752 | ``` 753 | 754 | Use it in your project: 755 | 756 | ```js 757 | // require modules 758 | const Config = require('gitlab-time-tracker/src/include/config'); 759 | const Report = require('gitlab-time-tracker/src/models/report'); 760 | 761 | // create a default config 762 | let config = new Config(); 763 | 764 | // set required parameters 765 | config.set('token', 'abcdefghijklmnopqrst'); 766 | config.set('project', 'namespace/project'); 767 | 768 | // create report 769 | let report = new Report(config); 770 | 771 | // query and process data 772 | try { 773 | await report.getProject() 774 | await report.getIssues() 775 | await report.getMergeRequests() 776 | await report.processIssues() 777 | await report.processMergeRequests() 778 | } catch (error) { 779 | console.log(error) 780 | } 781 | 782 | // access data on report 783 | report.issues.forEach(issue => { 784 | // time records on issue 785 | console.log(issue.times); 786 | // time spent of single time record 787 | console.log(issue.times[0].time); 788 | }); 789 | report.mergeRequests.forEach(mergeRequest => { 790 | // time records on merge requests 791 | console.log(mergeRequest.times); 792 | // user of single time record 793 | console.log(mergeRequest.times[0].user); 794 | }); 795 | ``` 796 | 797 | ## dumps 798 | 799 | Starting with 1.7.4 gtt can dump the results of all API requests within a report and use it on another machine without access to the GitLab instance itself. This is very useful for debugging purposes. If you stumble upon a bug which could be unique to your set of data, please rerun the report with these options to save a dump to the given file: `--output=dump --file=/path/dump.json` Check your dump for sensitive information and provide it when asked. 800 | 801 | ## faqs 802 | 803 | #### What is the difference between 'total spent' and 'spent'? 804 | 805 | `total spent` is the total amount of time spent in all issues after filtering. 806 | It can include times outside the queried time frame. `spent` on the other hand 807 | is the total amount of time spent in the given time frame. 808 | 809 | ## contributing 810 | 811 | I would love to integrate unit testing in this project, but unfortunately my knowledge of 812 | testing in the JavaScript/Node.js world is very limited. (I'm actually a PHP dev) 813 | So this would be a very helpful thing to contribute but of course all contributions are very welcome. 814 | 815 | * Please work in your own branch, e.g. `integrate-awesome-feature`, `fix-awful-bug`, `improve-this-crappy-docs` 816 | * Create a pull request to the `dev` branch 817 | 818 | ## support further development 819 | 820 | gtt is an open source project, developed and maintained completely in my free time. 821 | 822 | If you enjoy using gtt you can support the project by [contributing](#contributing) to the code base, 823 | sharing it to your colleagues and co-workers or monetarily by [donating via PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DN9YVDKFGC6V6). 824 | Every type of support is helpful and thank you very much if you consider supporting the project 825 | or already have done so. 💜 826 | 827 | ## license 828 | 829 | GPL v2 830 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitlab-time-tracker", 3 | "version": "1.7.39", 4 | "description": "A command line interface for GitLabs time tracking feature.", 5 | "bugs": { 6 | "url": "https://github.com/kriskbx/gitlab-time-tracker/issues" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/kriskbx/gitlab-time-tracker.git" 11 | }, 12 | "main": "src/gtt.js", 13 | "scripts": { 14 | "test": "NODE_ENV=test mocha 'spec/**/*.spec.js'", 15 | "cover": "istanbul cover _mocha $(find ./spec -name \"*.spec.js\")", 16 | "coveralls": "yarn run cover -- --report lcovonly && cat ./coverage/lcov.info | coveralls", 17 | "build": "pkg src/gtt.js -o out/gtt -c package.json" 18 | }, 19 | "bin": { 20 | "gtt": "src/gtt.js" 21 | }, 22 | "engines": { 23 | "node": ">=6.11" 24 | }, 25 | "pkg": { 26 | "scripts": "src/**/*.js", 27 | "targets": [ 28 | "node10-linux-x64", 29 | "node10-macos-x64", 30 | "node10-win-x64" 31 | ] 32 | }, 33 | "author": "kriskbx", 34 | "license": "GPL-2.0", 35 | "dependencies": { 36 | "app-module-path": "^2.2.0", 37 | "async": "^2.6.1", 38 | "camelcase": "^4.1.0", 39 | "cli-cursor": "^2.1.0", 40 | "cli-table": "^0.3.1", 41 | "colors": "^1.3.1", 42 | "commander": "kriskbx/commander.js", 43 | "csv-string": "^2.3.2", 44 | "find-in-files": "^0.4.0", 45 | "hash-sum": "^1.0.2", 46 | "hashids": "^1.1.1", 47 | "markdown-pdf": "^9.0.0", 48 | "markdown-table": "^1.1.0", 49 | "moment": "^2.22.2", 50 | "moment-timezone": "^0.5.21", 51 | "node-spinner": "^0.0.4", 52 | "open": "^0.0.5", 53 | "progress": "^2.0.0", 54 | "prompt": "^1.0.0", 55 | "read-yaml": "^1.1.0", 56 | "request": "^2.87.0", 57 | "request-promise-native": "^1.0.4", 58 | "shelljs": "^0.8.3", 59 | "tempfile": "^2.0.0", 60 | "throttled-queue": "^1.0.7", 61 | "underscore": "^1.9.1", 62 | "xdg-basedir": "^3.0.0", 63 | "xlsx": "^0.13.5" 64 | }, 65 | "devDependencies": { 66 | "chai": "^4.1.2", 67 | "coveralls": "^2.13.1", 68 | "istanbul": "^0.4.5", 69 | "mocha": "^5", 70 | "mocha-lcov-reporter": "^1.3.0", 71 | "pkg": "^4.3.4", 72 | "sinon": "^3.2.1" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /preview/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriskbx/gitlab-time-tracker/f3022eb6ef3c695482a7915971e68cc122d8ac88/preview/demo.gif -------------------------------------------------------------------------------- /preview/icon.gvdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriskbx/gitlab-time-tracker/f3022eb6ef3c695482a7915971e68cc122d8ac88/preview/icon.gvdesign -------------------------------------------------------------------------------- /preview/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriskbx/gitlab-time-tracker/f3022eb6ef3c695482a7915971e68cc122d8ac88/preview/icon.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![gtt](https://raw.githubusercontent.com/kriskbx/gitlab-time-tracker/master/preview/icon.png) 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 |

10 | 11 | ## ⚠️ Attention 12 | 13 | This repository is not maintained anymore. If you still want to use gtt and get regular updates, I recommend checking out this fork: https://github.com/ndu2/gitlab-time-tracker 14 | 15 | ## introduction 16 | 17 | gtt is a fully featured command line interface for GitLab's time tracking feature. It monitors the time you spent on an issue or merge request locally and syncs it to GitLab. It also allows you to create reports in various formats from time tracking data stored on GitLab. **Looking for a graphical user interface? Check out the [gtt taskbar/menubar app](https://github.com/kriskbx/gitlab-time-tracker-taskbar) for Linux, Mac & Windows!** 18 | 19 | ![gtt demo](https://raw.githubusercontent.com/kriskbx/gitlab-time-tracker/master/preview/demo.gif) 20 | 21 | ## documentation 22 | 23 | How to install and use gtt? You can find the documentation [here](https://github.com/kriskbx/gitlab-time-tracker/blob/master/documentation.md). 24 | 25 | ## support further development 26 | 27 | Please support the development of this free software by [donating or sharing](https://github.com/kriskbx/gitlab-time-tracker/blob/master/documentation.md#support-further-development)! 28 | 29 | ## license 30 | 31 | gtt is open-source software licensed under the [GPL V2 license](https://github.com/kriskbx/gitlab-time-tracker/blob/master/LICENSE). 32 | -------------------------------------------------------------------------------- /spec/_setup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon'); 2 | const chai = require('chai'); 3 | 4 | beforeEach(function () { 5 | this.sandbox = sinon.sandbox.create() 6 | }); 7 | 8 | afterEach(function () { 9 | this.sandbox.restore() 10 | }); -------------------------------------------------------------------------------- /spec/include/config.spec.js: -------------------------------------------------------------------------------- 1 | const config = require('./../../src/include/config'); 2 | const expect = require('chai').expect; 3 | 4 | describe('The config class', () => { 5 | it('stores data', () => { 6 | let Config = new config(), 7 | data = 'value_' + Math.random(), 8 | key = 'key_' + Math.random(); 9 | 10 | Config.set(key, data); 11 | 12 | expect(Config.get(key)).to.equal(data); 13 | }); 14 | 15 | it('doesnt store data if the value is null or undefined until you force it', () => { 16 | let Config = new config(), 17 | data = 'value_' + Math.random(), 18 | key = 'key_' + Math.random(); 19 | 20 | Config.set(key, data); 21 | 22 | Config.set(key, null); 23 | expect(Config.get(key)).to.equal(data); 24 | 25 | Config.set(key, undefined); 26 | expect(Config.get(key)).to.equal(data); 27 | 28 | Config.set(key, null, true); 29 | expect(Config.get(key)).to.equal(null); 30 | 31 | Config.set(key, undefined, true); 32 | expect(Config.get(key)).to.equal(undefined); 33 | }); 34 | 35 | it('returns moment instances for dates', () => { 36 | let Config = new config(), 37 | dates = ['from', 'to']; 38 | 39 | dates.forEach(date => { 40 | Config.set(date, "2017-08-01"); 41 | expect(typeof Config.get(date).format).to.equal("function"); 42 | }); 43 | }); 44 | 45 | it('returns values from objects and falls back to the default', () => { 46 | let Config = new config(), 47 | objectsWithDefaults = ['timeFormat'], 48 | defaults = Object.assign({}, Config.data), 49 | stringData = 'booze', 50 | objectData = { 51 | foo: 'bar', 52 | baz: 'bar' 53 | }; 54 | 55 | objectsWithDefaults.forEach(key => { 56 | Config.set(key, objectData); 57 | 58 | expect(Config.get(key, 'foo')).to.equal('bar'); 59 | expect(Config.get(key, 'baz')).to.equal('bar'); 60 | expect(Config.get(key, 'not_a_real_key')).to.equal(defaults[key]); 61 | 62 | Config.set(key, stringData); 63 | 64 | expect(Config.get(key, 'foo')).to.equal('booze'); 65 | expect(Config.get(key, 'baz')).to.equal('booze'); 66 | expect(Config.get(key, 'not_a_real_key')).to.equal('booze'); 67 | }); 68 | }); 69 | 70 | it('makes durations human readable', () => { 71 | let Config = new config(), 72 | humanReadable = "1d 4h 30m 10s", 73 | seconds = 45010; 74 | 75 | expect(Config.toHumanReadable(seconds)).to.equal(humanReadable); 76 | }); 77 | }); -------------------------------------------------------------------------------- /spec/include/file-config.spec.js: -------------------------------------------------------------------------------- 1 | describe('The file config class', () => { 2 | 3 | }); -------------------------------------------------------------------------------- /src/gtt-cancel.js: -------------------------------------------------------------------------------- 1 | const program = require('commander'); 2 | const colors = require('colors'); 3 | const moment = require('moment'); 4 | 5 | const Config = require('./include/file-config'); 6 | const Cli = require('./include/cli'); 7 | const Tasks = require('./include/tasks'); 8 | 9 | program 10 | .option('--verbose', 'show verbose output') 11 | .parse(process.argv); 12 | 13 | Cli.verbose = program.verbose; 14 | 15 | let config = new Config(process.cwd()); 16 | let tasks = new Tasks(config); 17 | 18 | tasks.cancel() 19 | .then(frames => { 20 | frames.forEach(frame => { 21 | if(!frame.resource.new) 22 | return console.log(`Cancel project ${frame.project.magenta} ${frame.resource.type.blue} ${('#' + frame.resource.id).blue}, started ${moment(frame.start).fromNow().green}`) 23 | 24 | console.log(`Cancel project ${frame.project.magenta} for new ${frame.resource.type} "${(frame.resource.id).blue}", started ${moment(frame.start).fromNow().green}`) 25 | }) 26 | }) 27 | .catch(error => Cli.error(error)); -------------------------------------------------------------------------------- /src/gtt-config.js: -------------------------------------------------------------------------------- 1 | const program = require('commander'); 2 | 3 | const Config = require('./include/file-config'); 4 | const Fs = require('./include/filesystem'); 5 | 6 | let config = new Config(process.cwd()); 7 | 8 | program 9 | .option('-l, --local', 'edit the local configuration file') 10 | .parse(process.argv); 11 | 12 | if (program.local) { 13 | config.assertLocalConfig(); 14 | } 15 | 16 | Fs.open(program.local ? config.local : config.global); -------------------------------------------------------------------------------- /src/gtt-create.js: -------------------------------------------------------------------------------- 1 | const colors = require('colors'); 2 | const moment = require('moment'); 3 | const program = require('commander'); 4 | 5 | const Config = require('./include/file-config'); 6 | const Cli = require('./include/cli'); 7 | const Tasks = require('./include/tasks'); 8 | 9 | program 10 | .arguments('[project] [title]') 11 | .option('-t, --type ', 'specify resource type: issue, merge_request') 12 | .option('--verbose', 'show verbose output') 13 | .parse(process.argv); 14 | 15 | Cli.verbose = program.verbose; 16 | 17 | let config = new Config(process.cwd()), 18 | tasks = new Tasks(config), 19 | type = program.type ? program.type : 'issue', 20 | title = program.args.length === 1 ? program.args[0] : program.args[1], 21 | project = program.args.length === 2 ? program.args[0] : null; 22 | 23 | if (program.args.length < 2 && !config.get('project')) 24 | Cli.error('No project set'); 25 | 26 | if (!title) 27 | Cli.error('Wrong or missing title'); 28 | 29 | tasks.start(project, type, title) 30 | .then(frame => console.log(`Starting project ${config.get('project').magenta} and create ${type} "${title.blue}" at ${moment().format('HH:mm').green}`)) 31 | .catch(error => Cli.error(error)); -------------------------------------------------------------------------------- /src/gtt-delete.js: -------------------------------------------------------------------------------- 1 | const program = require('commander'); 2 | 3 | const Frame = require('./models/frame'); 4 | const Config = require('./include/file-config'); 5 | const Cli = require('./include/cli'); 6 | const Fs = require('./include/filesystem'); 7 | 8 | program 9 | .arguments('[id]') 10 | .parse(process.argv); 11 | 12 | let config = new Config(process.cwd()); 13 | let id = program.args[0]; 14 | 15 | if ( 16 | (!id || !Fs.exists(Fs.join(config.frameDir, id + '.json'))) 17 | && -Infinity === (id = Fs.newest(config.frameDir)) 18 | ) 19 | Cli.error('No record found.'); 20 | 21 | let file = Fs.join(config.frameDir, id.replace('.json', '') + '.json'); 22 | let frame = Frame.fromFile(config, file).stopMe(); 23 | Fs.remove(file); 24 | console.log(`Deleting record ${frame.id.magenta}`); -------------------------------------------------------------------------------- /src/gtt-edit.js: -------------------------------------------------------------------------------- 1 | const program = require('commander'); 2 | 3 | const Config = require('./include/file-config'); 4 | const Cli = require('./include/cli'); 5 | const Fs = require('./include/filesystem'); 6 | 7 | program 8 | .arguments('[id]') 9 | .parse(process.argv); 10 | 11 | let config = new Config(process.cwd()); 12 | let id = program.args[0]; 13 | 14 | if ( 15 | (!id || !Fs.exists(Fs.join(config.frameDir, id + '.json'))) 16 | && -Infinity === (id = Fs.newest(config.frameDir)) 17 | ) 18 | Cli.error('No record found.'); 19 | 20 | Fs.open(Fs.join(config.frameDir, id.replace('.json', '') + '.json')); -------------------------------------------------------------------------------- /src/gtt-log.js: -------------------------------------------------------------------------------- 1 | const _ = require('underscore'); 2 | const program = require('commander'); 3 | const colors = require('colors'); 4 | const moment = require('moment-timezone'); 5 | 6 | const Config = require('./include/file-config'); 7 | const Cli = require('./include/cli'); 8 | const Time = require('./models/time'); 9 | const Tasks = require('./include/tasks'); 10 | 11 | program 12 | .option('--verbose', 'show verbose output') 13 | .option('--hours_per_day ', 'hours per day for human readable time formats') 14 | .option('--time_format ', 'time format') 15 | .parse(process.argv); 16 | 17 | Cli.verbose = program.verbose; 18 | 19 | let config = new Config(__dirname).set('hoursPerDay', program.hours_per_day), 20 | tasks = new Tasks(config), 21 | timeFormat = config.set('timeFormat', program.time_format).get('timeFormat', 'log'); 22 | 23 | function toHumanReadable(input) { 24 | return Time.toHumanReadable(Math.ceil(input), config.get('hoursPerDay'), timeFormat); 25 | } 26 | 27 | tasks.log() 28 | .then(({frames, times}) => { 29 | Object.keys(frames).sort().forEach(date => { 30 | if (!frames.hasOwnProperty(date)) return; 31 | 32 | console.log(`${moment(date).format('MMMM Do YYYY')} (${toHumanReadable(times[date])})`.green); 33 | frames[date] 34 | .sort((a, b) => a.start.isBefore(b.start) ? -1 : 1) 35 | .forEach(frame => { 36 | let issue = frame.resource.new ? `new ${frame.resource.type + ' "' + frame.resource.id.blue}"` : `${(frame.resource.type + ' #' + frame.resource.id).blue}`; 37 | console.log(` ${frame.id} ${frame.start.clone().format('HH:mm').green} to ${frame.stop.clone().format('HH:mm').green}\t${toHumanReadable(frame.duration)}\t\t${frame.project.magenta}\t\t${issue}`) 38 | }); 39 | }); 40 | } 41 | ) 42 | .catch(error => Cli.error(error)); -------------------------------------------------------------------------------- /src/gtt-report.js: -------------------------------------------------------------------------------- 1 | const _ = require('underscore'); 2 | const fs = require('fs'); 3 | const program = require('commander'); 4 | const moment = require('moment'); 5 | 6 | const Cli = require('./include/cli'); 7 | const Config = require('./include/file-config'); 8 | const Report = require('./models/report'); 9 | const Owner = require('./models/owner'); 10 | const ReportCollection = require('./models/reportCollection'); 11 | 12 | const Output = { 13 | table: require('./output/table'), 14 | csv: require('./output/csv'), 15 | pdf: require('./output/pdf'), 16 | markdown: require('./output/markdown'), 17 | dump: require('./output/dump'), 18 | xlsx: require('./output/xlsx') 19 | }; 20 | 21 | // this collects options 22 | function collect(val, arr) { 23 | if (!arr) arr = []; 24 | arr.push(val); 25 | 26 | return _.uniq(arr); 27 | } 28 | 29 | // set options 30 | program 31 | .arguments('[project] [ids]') 32 | .option('-e --type ', 'specify the query type: project, user, group') 33 | .option('--subgroups', 'include sub groups') 34 | .option('--url ', 'URL to GitLabs API') 35 | .option('--token ', 'API access token') 36 | .option('-p --proxy ', 'use a proxy server with the given url') 37 | .option('--insecure', 'don\'t check certificates') 38 | .option('-f --from ', 'query times that are equal or greater than the given date') 39 | .option('-t --to ', 'query times that are equal or smaller than the given date') 40 | .option('--today', 'ignores --from and --to and queries entries for today') 41 | .option('--this_week', 'ignores --from and --to and queries entries for this week') 42 | .option('--this_month', 'ignores --from and --to and queries entries for this month') 43 | .option('-c --closed', 'include closed issues') 44 | .option('-m --milestone ', 'include issues from the given milestone') 45 | .option('--hours_per_day ', 'hours per day for human readable time formats') 46 | .option('-u --user ', 'only query times from the given user') 47 | .option('-q --query ', 'query the given data types: issues, merge_requests', collect, null) 48 | .option('-r --report ', 'include in the report: stats, issues, merge_requests, records', collect, null) 49 | .option('-o --output ', 'use the given output') 50 | .option('-l --file ', 'save report to the given file') 51 | .option('--include_by_labels ', 'only include issues that have the given labels', collect, null) 52 | .option('--exclude_by_labels ', 'exclude issues that have the given labels', collect, null) 53 | .option('--include_labels ', 'only include the given labels in the report', collect, null) 54 | .option('--exclude_labels ', 'exclude the given labels in the report', collect, null) 55 | .option('--date_format ', 'use the given date format in the report', collect, null) 56 | .option('--time_format