renuo-cli 4.7.0 → 4.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/bin/setup +0 -1
- data/lib/renuo/cli/app/create_aws_project.rb +2 -0
- data/lib/renuo/cli/app/github_pull_request_creator.rb +13 -6
- data/lib/renuo/cli/app/github_replace.rb +6 -6
- data/lib/renuo/cli/version.rb +3 -1
- data/lib/renuo/cli.rb +3 -37
- data/renuo-cli.gemspec +2 -2
- metadata +8 -20
- data/lib/renuo/cli/app/redmine/csv_base_service.rb +0 -56
- data/lib/renuo/cli/app/redmine/issue.rb +0 -25
- data/lib/renuo/cli/app/release_xing.rb +0 -35
- data/lib/renuo/cli/app/toggl/detail.rb +0 -32
- data/lib/renuo/cli/app/toggl/time_entry.rb +0 -32
- data/lib/renuo/cli/app/toggl/user.rb +0 -26
- data/lib/renuo/cli/app/toggl/workspace.rb +0 -19
- data/lib/renuo/cli/app/toggl_redmine_comparator.rb +0 -150
- data/lib/renuo/cli/app/work.rb +0 -96
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90d3b8e87a9471eda0e8c3562f8f9d0be7163f94bcf4e90eb3f05fe4618b0d87
|
4
|
+
data.tar.gz: a512b8151eb5a140cb35c15f7ec85744c9e3f8f3bc01ba81e0bc75afa3a98a48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cab9dc3ae7914973a79137af6372e4ca94e5a725f57329da4cdd06babbd437e0024ff263b586c0dbe2394fa191d92ccdb6093c9d9ccca86cc5fc3af339f7ce65
|
7
|
+
data.tar.gz: e6dc36d8a6dbae546175f29a372c27ad85d30bccc23350c387f5c57dd487a548323dab6cc8be04748ed3ade3ffe37e93eccb01e9c07054345a3a22a50c3636a3
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.4.1
|
data/bin/setup
CHANGED
@@ -2,18 +2,25 @@
|
|
2
2
|
|
3
3
|
class GithubPullRequestCreator
|
4
4
|
def run(opts)
|
5
|
-
validate_options(opts)
|
6
5
|
create_pr(opts)
|
7
6
|
end
|
8
7
|
|
9
8
|
private
|
10
9
|
|
11
|
-
def
|
12
|
-
|
10
|
+
def create_pr(opts)
|
11
|
+
command = [
|
12
|
+
"gh pr create",
|
13
|
+
"--assignee @me",
|
14
|
+
pr_title(opts),
|
15
|
+
"--body \"#{pr_body(opts)}\"",
|
16
|
+
("--draft" if opts.draft)
|
17
|
+
].compact.join(" ")
|
18
|
+
|
19
|
+
puts `#{command}`
|
13
20
|
end
|
14
21
|
|
15
|
-
def
|
16
|
-
|
22
|
+
def pr_title(opts)
|
23
|
+
opts.title ? "--title \"#{opts.title}\"" : "--fill"
|
17
24
|
end
|
18
25
|
|
19
26
|
def pr_body(opts)
|
@@ -25,6 +32,6 @@ class GithubPullRequestCreator
|
|
25
32
|
|
26
33
|
def redmine_ticket_number(opts)
|
27
34
|
current_branch = `git branch --show-current`.strip
|
28
|
-
current_branch.match(/(\d+)/)&.captures&.first
|
35
|
+
opts.redmine_ticket || current_branch.match(/(\d+)/)&.captures&.first
|
29
36
|
end
|
30
37
|
end
|
@@ -6,7 +6,7 @@ class GithubReplace
|
|
6
6
|
validate_options opts
|
7
7
|
|
8
8
|
opts.projects.split(",").each do |repo|
|
9
|
-
base = clone_and_checkout repo
|
9
|
+
base = clone_and_checkout opts, repo
|
10
10
|
find_and_replace args, repo, opts.files.split(",")
|
11
11
|
create_pr opts, repo, base
|
12
12
|
end
|
@@ -26,9 +26,9 @@ class GithubReplace
|
|
26
26
|
abort ">> specify comma-separated files to check" unless opts.files
|
27
27
|
end
|
28
28
|
|
29
|
-
def clone_and_checkout(repo)
|
29
|
+
def clone_and_checkout(opts, repo)
|
30
30
|
puts `cd #{repo} || git clone --depth=1 git@github.com:renuo/#{repo}.git`
|
31
|
-
puts `cd #{repo} &&
|
31
|
+
puts `cd #{repo} && git checkout -b "#{opts.branch}"`
|
32
32
|
`cd #{repo} && git branch -rl origin/main origin/master`.strip
|
33
33
|
end
|
34
34
|
|
@@ -43,9 +43,9 @@ class GithubReplace
|
|
43
43
|
|
44
44
|
def create_pr(opts, repo, base)
|
45
45
|
puts `cd "#{repo}" \
|
46
|
-
&&
|
47
|
-
&&
|
48
|
-
&&
|
46
|
+
&& git add . \
|
47
|
+
&& git commit -S -m "#{opts.title}" \
|
48
|
+
&& git push --set-upstream origin "#{opts.branch}" \
|
49
49
|
&& gh pr create -a @me -b "#{opts.body}" -t "#{opts.title}" -B "#{base}" -H "#{opts.branch}"`
|
50
50
|
end
|
51
51
|
end
|
data/lib/renuo/cli/version.rb
CHANGED
data/lib/renuo/cli.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "renuo/cli/version"
|
4
|
+
require "active_support"
|
5
|
+
require "active_support/core_ext"
|
4
6
|
require "rubygems"
|
5
7
|
require "gems"
|
6
8
|
require "colorize"
|
@@ -16,14 +18,11 @@ require "renuo/cli/app/create_aws_project"
|
|
16
18
|
require "renuo/cli/app/create_heroku_app"
|
17
19
|
require "renuo/cli/app/configure_sentry"
|
18
20
|
require "renuo/cli/app/create_new_logins"
|
19
|
-
require "renuo/cli/app/work"
|
20
21
|
require "renuo/cli/app/release_project"
|
21
22
|
require "renuo/cli/app/fetch_emails"
|
22
23
|
require "renuo/cli/app/heroku_users"
|
23
24
|
require "renuo/cli/app/setup_uptimerobot"
|
24
|
-
require "renuo/cli/app/release_xing"
|
25
25
|
require "renuo/cli/app/configure_semaphore"
|
26
|
-
require "renuo/cli/app/toggl_redmine_comparator"
|
27
26
|
require "renuo/cli/app/renuo_version"
|
28
27
|
require "renuo/cli/app/create_slidev_presentation"
|
29
28
|
require "renuo/cli/app/commit_leaderboard_stage"
|
@@ -142,19 +141,6 @@ module Renuo
|
|
142
141
|
end
|
143
142
|
end
|
144
143
|
|
145
|
-
command "work" do |c|
|
146
|
-
c.syntax = "renuo work"
|
147
|
-
c.summary = "A utility command to start working on a ticket."
|
148
|
-
c.description = "When you start working on a feature, it will jump to the project folder, " \
|
149
|
-
"start a new feature with the ticket number," \
|
150
|
-
'move the ticket in "In progress" state on Redmine and start Toggl with the ' \
|
151
|
-
"ticket number or add the ticket number to the running one if there was none."
|
152
|
-
c.example "renuo work start kinova 1234", "start working on ticket 1234 on kinova project"
|
153
|
-
c.action do |args|
|
154
|
-
Work.new.run(args)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
144
|
command "release" do |c|
|
159
145
|
c.syntax = "renuo release"
|
160
146
|
c.summary = "Release a projects state of develop (on github) to main in one command."
|
@@ -222,16 +208,6 @@ module Renuo
|
|
222
208
|
end
|
223
209
|
end
|
224
210
|
|
225
|
-
command "release-xing" do |c|
|
226
|
-
c.syntax = "renuo release-xing"
|
227
|
-
c.summary = "Release Xing to preview."
|
228
|
-
c.description = "Xing needs to be released manually. " \
|
229
|
-
"You need a VPN connection open before runnign this command."
|
230
|
-
c.action do |_args|
|
231
|
-
ReleaseXing.new.run
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
211
|
command "configure-semaphore" do |c|
|
236
212
|
c.syntax = "renuo configure-semaphore"
|
237
213
|
c.summary = "Adds standard semaphore configuration files to a project and creates the notifications"
|
@@ -242,16 +218,6 @@ module Renuo
|
|
242
218
|
end
|
243
219
|
end
|
244
220
|
|
245
|
-
command "toggl-redmine" do |c|
|
246
|
-
c.syntax = "renuo toggl-redmine"
|
247
|
-
c.summary = "Compares your time entries between Toggl and Redmine"
|
248
|
-
c.description = "This command extracts your time entries in Toggl and in Redmine and checks if they are " \
|
249
|
-
"consistant. It can help you in your time booking to find if you booked something wrong"
|
250
|
-
c.action do |_args|
|
251
|
-
TogglRedmineComparator.call
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
221
|
command "create-slidev-presentation" do |c|
|
256
222
|
c.syntax = "renuo create-slidev-presentation [presentation-name]"
|
257
223
|
c.summary = "Creates a new Slidev presentation with the Renuo theme"
|
@@ -319,7 +285,7 @@ module Renuo
|
|
319
285
|
c.summary = "Creates a PR for the current branch"
|
320
286
|
c.description = "Creates a PR, assigns it to you and automatically inserts the Redmine ticket number into " \
|
321
287
|
"the PR body"
|
322
|
-
c.option "--title <title>", String, "The title of the pull request"
|
288
|
+
c.option "--title <title>", String, "The title of the pull request (optional)"
|
323
289
|
c.option "--redmine-ticket <number>", Integer, "The redmine ticket number (optional)"
|
324
290
|
c.option "--draft", "Mark the PR as draft"
|
325
291
|
c.example "Create a PR with the title 'Implement XYZ'",
|
data/renuo-cli.gemspec
CHANGED
@@ -23,9 +23,9 @@ Gem::Specification.new do |spec|
|
|
23
23
|
|
24
24
|
spec.required_ruby_version = ">= 3.1.0"
|
25
25
|
|
26
|
-
spec.add_dependency "
|
26
|
+
spec.add_dependency "activesupport", "~> 8.0.0"
|
27
27
|
spec.add_dependency "colorize", "~> 0"
|
28
|
-
spec.add_dependency "commander", "~>
|
28
|
+
spec.add_dependency "commander", "~> 5.0"
|
29
29
|
spec.add_dependency "gems", "~> 1.1"
|
30
30
|
spec.add_dependency "redcarpet", "~> 3.0"
|
31
31
|
spec.add_dependency "terminal-table"
|
metadata
CHANGED
@@ -1,29 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: renuo-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Renuo AG
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-01-15 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
13
|
+
name: activesupport
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
16
15
|
requirements:
|
17
16
|
- - "~>"
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
18
|
+
version: 8.0.0
|
20
19
|
type: :runtime
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
23
|
- - "~>"
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
25
|
+
version: 8.0.0
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
27
|
name: colorize
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,14 +43,14 @@ dependencies:
|
|
44
43
|
requirements:
|
45
44
|
- - "~>"
|
46
45
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
46
|
+
version: '5.0'
|
48
47
|
type: :runtime
|
49
48
|
prerelease: false
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
51
50
|
requirements:
|
52
51
|
- - "~>"
|
53
52
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
53
|
+
version: '5.0'
|
55
54
|
- !ruby/object:Gem::Dependency
|
56
55
|
name: gems
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -340,10 +339,7 @@ files:
|
|
340
339
|
- lib/renuo/cli/app/local_storage.rb
|
341
340
|
- lib/renuo/cli/app/name_display.rb
|
342
341
|
- lib/renuo/cli/app/package.json
|
343
|
-
- lib/renuo/cli/app/redmine/csv_base_service.rb
|
344
|
-
- lib/renuo/cli/app/redmine/issue.rb
|
345
342
|
- lib/renuo/cli/app/release_project.rb
|
346
|
-
- lib/renuo/cli/app/release_xing.rb
|
347
343
|
- lib/renuo/cli/app/renuo_version.rb
|
348
344
|
- lib/renuo/cli/app/secrets_fetcher.rb
|
349
345
|
- lib/renuo/cli/app/services/cloudfront_config_service.rb
|
@@ -357,15 +353,9 @@ files:
|
|
357
353
|
- lib/renuo/cli/app/templates/semaphore/semaphore.yml.erb
|
358
354
|
- lib/renuo/cli/app/templates/slidev/README.md.erb
|
359
355
|
- lib/renuo/cli/app/templates/slidev/package.json.erb
|
360
|
-
- lib/renuo/cli/app/toggl/detail.rb
|
361
|
-
- lib/renuo/cli/app/toggl/time_entry.rb
|
362
|
-
- lib/renuo/cli/app/toggl/user.rb
|
363
|
-
- lib/renuo/cli/app/toggl/workspace.rb
|
364
|
-
- lib/renuo/cli/app/toggl_redmine_comparator.rb
|
365
356
|
- lib/renuo/cli/app/upgrade_laptop.rb
|
366
357
|
- lib/renuo/cli/app/upgrade_laptop/upgrade_laptop_execution.rb
|
367
358
|
- lib/renuo/cli/app/upgrade_laptop/upgrade_mac_os.rb
|
368
|
-
- lib/renuo/cli/app/work.rb
|
369
359
|
- lib/renuo/cli/version.rb
|
370
360
|
- notification.yml
|
371
361
|
- renuo-cli.gemspec
|
@@ -376,7 +366,6 @@ licenses:
|
|
376
366
|
- MIT
|
377
367
|
metadata:
|
378
368
|
rubygems_mfa_required: 'true'
|
379
|
-
post_install_message:
|
380
369
|
rdoc_options: []
|
381
370
|
require_paths:
|
382
371
|
- lib
|
@@ -391,8 +380,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
391
380
|
- !ruby/object:Gem::Version
|
392
381
|
version: '0'
|
393
382
|
requirements: []
|
394
|
-
rubygems_version: 3.
|
395
|
-
signing_key:
|
383
|
+
rubygems_version: 3.6.2
|
396
384
|
specification_version: 4
|
397
385
|
summary: The Renuo CLI automates some common workflows.
|
398
386
|
test_files: []
|
@@ -1,56 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "csv"
|
4
|
-
|
5
|
-
module Redmine
|
6
|
-
class CsvBaseService
|
7
|
-
API_LOCATION = "https://redmine.renuo.ch"
|
8
|
-
|
9
|
-
def initialize(token)
|
10
|
-
@token = token
|
11
|
-
end
|
12
|
-
|
13
|
-
def get
|
14
|
-
http_response = http_request(generate_url)
|
15
|
-
encoded_body = http_response.body.force_encoding("ISO-8859-1").encode("UTF-8")
|
16
|
-
csv = parse_csv(encoded_body)
|
17
|
-
parse_results(csv)
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def generate_url
|
23
|
-
URI("#{API_LOCATION}/time_entries/report.csv?#{query}&key=#{@token}")
|
24
|
-
end
|
25
|
-
|
26
|
-
def http_request(url)
|
27
|
-
req = Net::HTTP::Get.new(url)
|
28
|
-
Net::HTTP.start(url.hostname, url.port, use_ssl: true) do |http|
|
29
|
-
http.request(req)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def query
|
34
|
-
# to be implemented in concrete service
|
35
|
-
end
|
36
|
-
|
37
|
-
def parse_results(_csv)
|
38
|
-
# to be implemented in concrete service
|
39
|
-
end
|
40
|
-
|
41
|
-
def parse_csv(body)
|
42
|
-
separated_csv_entries = CSV.parse(body, col_sep: ",")
|
43
|
-
keys = separated_csv_entries.shift[1..-2]
|
44
|
-
entries = separated_csv_entries.shift[1..-2]
|
45
|
-
keys.zip(entries).to_h
|
46
|
-
rescue CSV::MalformedCSVError
|
47
|
-
raise_bad_data_error
|
48
|
-
end
|
49
|
-
|
50
|
-
def raise_bad_data_error
|
51
|
-
error = "Malformed CSV, please use comma delimiters (Redmine language setting?)"
|
52
|
-
Rails.logger.error error
|
53
|
-
raise Redmine::BadData, error
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_resource"
|
4
|
-
|
5
|
-
module Redmine
|
6
|
-
class Issue < ActiveResource::Base
|
7
|
-
STATUSES = {
|
8
|
-
to_start: 9,
|
9
|
-
planned: 15,
|
10
|
-
in_progress: 2,
|
11
|
-
qa: 12
|
12
|
-
}.freeze
|
13
|
-
|
14
|
-
self.site = "https://redmine.renuo.ch"
|
15
|
-
self.include_root_in_json = true
|
16
|
-
|
17
|
-
def self.headers
|
18
|
-
{ "X-Redmine-API-Key" => RenuoCliConfig.redmine_api_key }
|
19
|
-
end
|
20
|
-
|
21
|
-
def html_url
|
22
|
-
"#{self.class.site}issues/#{id}"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class ReleaseXing
|
4
|
-
TMP_FOLDER_NAME = "_RENUO_RELEASE_TEMP_#{rand(100_000_000)}".freeze
|
5
|
-
MOVE_TO_TMP_FOLDER = "mkdir -p #{TMP_FOLDER_NAME} && cd #{TMP_FOLDER_NAME}".freeze
|
6
|
-
|
7
|
-
def run
|
8
|
-
checkout_project
|
9
|
-
cmd_in_folder "git remote add -f renuo git@github.com:renuo/xing-campaign.git"
|
10
|
-
cmd_in_folder "git pull"
|
11
|
-
cmd_in_folder "git fetch renuo develop"
|
12
|
-
cmd_in_folder "git subtree pull --prefix public renuo develop --squash"
|
13
|
-
cmd_in_folder "git push"
|
14
|
-
ensure
|
15
|
-
cleanup
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def checkout_project
|
21
|
-
system "#{MOVE_TO_TMP_FOLDER} && git clone git@gitlab.dc.xing.com:renuo-ag/wunsch-arbeitgeber.xing.com.git"
|
22
|
-
end
|
23
|
-
|
24
|
-
def cmd_in_folder(command)
|
25
|
-
system "#{move_and_cd} && #{command}"
|
26
|
-
end
|
27
|
-
|
28
|
-
def move_and_cd
|
29
|
-
"#{MOVE_TO_TMP_FOLDER} && cd wunsch-arbeitgeber.xing.com"
|
30
|
-
end
|
31
|
-
|
32
|
-
def cleanup
|
33
|
-
system("rm -rf #{TMP_FOLDER_NAME}")
|
34
|
-
end
|
35
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_resource"
|
4
|
-
|
5
|
-
module Toggl
|
6
|
-
module CustomJsonFormat
|
7
|
-
include ActiveResource::Formats::JsonFormat
|
8
|
-
|
9
|
-
# rubocop:disable Style/ModuleFunction
|
10
|
-
extend self
|
11
|
-
# rubocop:enable Style/ModuleFunction
|
12
|
-
|
13
|
-
def decode(json)
|
14
|
-
ActiveSupport::JSON.decode(json)["data"]
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
class Detail < ActiveResource::Base
|
19
|
-
self.format = CustomJsonFormat
|
20
|
-
self.site = "https://toggl.com/reports/api/v2"
|
21
|
-
self.include_root_in_json = true
|
22
|
-
self.include_format_in_path = false
|
23
|
-
|
24
|
-
def self.user
|
25
|
-
RenuoCliConfig.toggl_api_token
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.password
|
29
|
-
"api_token"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_resource"
|
4
|
-
|
5
|
-
module Toggl
|
6
|
-
class TimeEntry < ActiveResource::Base
|
7
|
-
self.site = "https://www.toggl.com/api/v8/"
|
8
|
-
self.include_root_in_json = true
|
9
|
-
self.include_format_in_path = false
|
10
|
-
|
11
|
-
def self.user
|
12
|
-
RenuoCliConfig.toggl_api_token
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.password
|
16
|
-
"api_token"
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.current
|
20
|
-
data = get(:current)["data"]
|
21
|
-
data ? new(data) : nil
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.start(params)
|
25
|
-
post(:start, {}, params.to_json)
|
26
|
-
end
|
27
|
-
|
28
|
-
def stop
|
29
|
-
put(:stop)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Toggl
|
4
|
-
class User < ActiveResource::Base
|
5
|
-
self.site = "https://www.toggl.com/api/v8/"
|
6
|
-
self.include_root_in_json = true
|
7
|
-
self.include_format_in_path = false
|
8
|
-
|
9
|
-
def self.user
|
10
|
-
RenuoCliConfig.toggl_api_token
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.password
|
14
|
-
"api_token"
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.custom_method_collection_url(method_name, options)
|
18
|
-
prefix_options, query_options = split_options(options)
|
19
|
-
"#{prefix(prefix_options)}#{method_name}#{format_extension}#{query_string(query_options)}"
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.me
|
23
|
-
new(get(:me)["data"])
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_resource"
|
4
|
-
|
5
|
-
module Toggl
|
6
|
-
class Workspace < ActiveResource::Base
|
7
|
-
self.site = "https://www.toggl.com/api/v8/"
|
8
|
-
self.include_root_in_json = true
|
9
|
-
self.include_format_in_path = false
|
10
|
-
|
11
|
-
def self.user
|
12
|
-
RenuoCliConfig.toggl_api_token
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.password
|
16
|
-
"api_token"
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,150 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "commander"
|
4
|
-
require "csv"
|
5
|
-
require_relative "environments"
|
6
|
-
require_relative "fetch_emails"
|
7
|
-
require "renuo/cli/app/toggl/workspace"
|
8
|
-
require "renuo/cli/app/toggl/detail"
|
9
|
-
require "renuo/cli/app/toggl/user"
|
10
|
-
require "terminal-table"
|
11
|
-
require "colorize"
|
12
|
-
require "active_support/core_ext/numeric/time"
|
13
|
-
|
14
|
-
# rubocop:disable Metrics/ClassLength
|
15
|
-
class TogglRedmineComparator
|
16
|
-
class << self
|
17
|
-
def call(days_behind = 7)
|
18
|
-
report = {}
|
19
|
-
since_date = days_behind.days.before(Date.yesterday).strftime("%F")
|
20
|
-
until_date = Date.yesterday.strftime("%F")
|
21
|
-
extract_redmine(report, since_date, until_date)
|
22
|
-
extract_toggl(report, since_date, until_date)
|
23
|
-
report = report.sort.reverse.to_h
|
24
|
-
print_table(report)
|
25
|
-
report
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def print_table(report)
|
31
|
-
rows = []
|
32
|
-
report.each do |date, value|
|
33
|
-
rows << colorize_table_row(date, value)
|
34
|
-
rows << :separator
|
35
|
-
end
|
36
|
-
rows.pop
|
37
|
-
table = Terminal::Table.new headings: %w[Day Redmine Toggl].map(&:cyan), rows:,
|
38
|
-
style: { padding_left: 2, padding_right: 2,
|
39
|
-
border_x: "-".blue, border_y: "|".blue, border_i: "+".blue }
|
40
|
-
puts table
|
41
|
-
end
|
42
|
-
|
43
|
-
def colorize_table_row(date, value)
|
44
|
-
printed_day = date.strftime("%F %a")
|
45
|
-
printed_redmine = to_time(value[:redmine])
|
46
|
-
printed_toggl = to_time(value[:toggl])
|
47
|
-
colorize_method = colorization_for_value(value)
|
48
|
-
[printed_day, printed_redmine, printed_toggl].map { |v| v.colorize(colorize_method) }
|
49
|
-
end
|
50
|
-
|
51
|
-
def colorization_for_value(value)
|
52
|
-
if more_toggl?(value)
|
53
|
-
:red
|
54
|
-
elsif more_redmine?(value)
|
55
|
-
:default
|
56
|
-
else
|
57
|
-
:green
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def extract_redmine(report, since_date, _until_date)
|
62
|
-
encoded_body = perform_redmine_call(since_date)
|
63
|
-
csv = convert_redmine_csv(encoded_body)
|
64
|
-
csv.each do |date, entry|
|
65
|
-
report[Date.parse(date)] ||= default_value
|
66
|
-
report[Date.parse(date)][:redmine] = to_seconds(entry) if entry.present?
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def convert_redmine_csv(encoded_body)
|
71
|
-
separated_csv_entries = CSV.parse(encoded_body, col_sep: ",")
|
72
|
-
keys = separated_csv_entries.shift[1..-2]
|
73
|
-
entries = separated_csv_entries.shift[1..-2]
|
74
|
-
keys.zip(entries)
|
75
|
-
end
|
76
|
-
|
77
|
-
def perform_redmine_call(since_date)
|
78
|
-
query = generate_redmine_query(since_date)
|
79
|
-
url = URI("https://redmine.renuo.ch/time_entries/report.csv?#{query}")
|
80
|
-
req = Net::HTTP::Get.new(url)
|
81
|
-
req["X-Redmine-API-Key"] = RenuoCliConfig.redmine_api_key
|
82
|
-
response = Net::HTTP.start(url.hostname, url.port, use_ssl: true) { |http| http.request(req) }
|
83
|
-
response.body.force_encoding("ISO-8859-1").encode("UTF-8")
|
84
|
-
end
|
85
|
-
|
86
|
-
def generate_redmine_query(since_date)
|
87
|
-
URI.encode_www_form(
|
88
|
-
[["utf8", "✓"], ["criteria[]", "user"],
|
89
|
-
["f[]", "spent_on"], ["f[]", "user_id"],
|
90
|
-
["op[spent_on]", ">="], ["op[user_id]", "="],
|
91
|
-
["v[spent_on][]", since_date], ["v[user_id][]", "me"],
|
92
|
-
["f[]", ""],
|
93
|
-
["c[]", "project"], ["c[]", "spent_on"], ["c[]", "user"], ["c[]", "activity"], ["c[]", "issue"],
|
94
|
-
["c[]", "comments"], ["c[]", "hours"], %w[columns day], ["criteria[]", ""]]
|
95
|
-
)
|
96
|
-
end
|
97
|
-
|
98
|
-
def extract_toggl(report, since_date, until_date)
|
99
|
-
user_id = Toggl::User.me.id
|
100
|
-
workspace_ids = Toggl::Workspace.all.map(&:id)
|
101
|
-
|
102
|
-
workspace_ids.each do |workspace_id|
|
103
|
-
time_entries = Toggl::Detail.where(since: since_date, until: until_date,
|
104
|
-
user_agent: "renuo-cli", workspace_id:, user_ids: user_id)
|
105
|
-
parse_toggl_entries(report, time_entries)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def parse_toggl_entries(report, time_entries)
|
110
|
-
time_entries
|
111
|
-
.reject { |te| te.end.nil? }
|
112
|
-
.group_by { |time_entry| Date.parse(time_entry.end) }
|
113
|
-
.each do |date, grouped_time_entries|
|
114
|
-
report[date] ||= default_value
|
115
|
-
report[date][:toggl] += grouped_time_entries.sum(&:dur)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def default_value
|
120
|
-
{ redmine: 0.0, toggl: 0.0 }
|
121
|
-
end
|
122
|
-
|
123
|
-
def to_time(value)
|
124
|
-
sec = value / 1000.0
|
125
|
-
min, _sec = sec.divmod(60.0)
|
126
|
-
hour, min = min.divmod(60.0)
|
127
|
-
format("%<hour>02d:%<min>02d", hour:, min:)
|
128
|
-
end
|
129
|
-
|
130
|
-
def to_seconds(value)
|
131
|
-
hours, minutes = value.to_d.divmod(1.0)
|
132
|
-
(hours * 60 * 60 * 1000) + (minutes * 60 * 60 * 1000)
|
133
|
-
end
|
134
|
-
|
135
|
-
def non_working_day?(value)
|
136
|
-
[value[:redmine], value[:toggl]].all?(&:zero?)
|
137
|
-
end
|
138
|
-
|
139
|
-
BUFFER = 20_000
|
140
|
-
|
141
|
-
def more_toggl?(value)
|
142
|
-
(value[:toggl] - value[:redmine]) > BUFFER
|
143
|
-
end
|
144
|
-
|
145
|
-
def more_redmine?(value)
|
146
|
-
(value[:redmine] - value[:toggl]) > BUFFER
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
# rubocop:enable Metrics/ClassLength
|
data/lib/renuo/cli/app/work.rb
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "fileutils"
|
4
|
-
require "renuo/cli/app/redmine/issue"
|
5
|
-
require "renuo/cli/app/toggl/time_entry"
|
6
|
-
|
7
|
-
class Work
|
8
|
-
ACTIONS = %w[start].freeze
|
9
|
-
|
10
|
-
def run(args)
|
11
|
-
ActiveResource::Base.logger = Logger.new($stdout)
|
12
|
-
@action, @project_name, @ticket_number = args
|
13
|
-
validate_action
|
14
|
-
validate_project_name
|
15
|
-
validate_ticket_number
|
16
|
-
start_feature_branch
|
17
|
-
update_redmine_ticket
|
18
|
-
start_toggl
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
# TODO: I want to implement also the stop action.
|
24
|
-
def validate_action
|
25
|
-
abort(">> No action given. It must be start") unless ACTIONS.include? @action
|
26
|
-
end
|
27
|
-
|
28
|
-
def validate_project_name
|
29
|
-
abort(">> No project name given.") unless @project_name
|
30
|
-
end
|
31
|
-
|
32
|
-
def validate_ticket_number
|
33
|
-
abort(">> No ticket number given.") unless @ticket_number
|
34
|
-
issue = Redmine::Issue.find(@ticket_number)
|
35
|
-
open_statuses = Redmine::Issue::STATUSES.values_at(:to_start, :planned, :in_progress, :qa)
|
36
|
-
return if open_statuses.include?(issue.status.id)
|
37
|
-
|
38
|
-
system("open #{issue.html_url}")
|
39
|
-
abort(">> Ticket should be in an open status")
|
40
|
-
end
|
41
|
-
|
42
|
-
def start_feature_branch
|
43
|
-
project_folder = `autojump #{@project_name}`.strip
|
44
|
-
system("cd #{project_folder} && git stash && git checkout develop " \
|
45
|
-
"&& git pull && git flow feature start #{@ticket_number}")
|
46
|
-
end
|
47
|
-
|
48
|
-
def update_redmine_ticket
|
49
|
-
issue = Redmine::Issue.find(@ticket_number)
|
50
|
-
issue.status_id = Redmine::Issue::STATUSES[:in_progress]
|
51
|
-
issue.save
|
52
|
-
system("open #{issue.html_url}")
|
53
|
-
end
|
54
|
-
|
55
|
-
def start_toggl
|
56
|
-
current_time_entry = Toggl::TimeEntry.current
|
57
|
-
if current_time_entry.nil?
|
58
|
-
create_toggl_time_entry
|
59
|
-
elsif current_time_entry.description.nil?
|
60
|
-
update_toggl_time_entry(current_time_entry.id)
|
61
|
-
else
|
62
|
-
existing_toggl(current_time_entry)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def existing_toggl(current_time_entry)
|
67
|
-
say("A timer '#{current_time_entry.description}' was already running.")
|
68
|
-
if current_time_entry.description.to_i == @ticket_number.to_i
|
69
|
-
say("I will keep using it")
|
70
|
-
else
|
71
|
-
say("I stopped it and started a new time entry.")
|
72
|
-
stop_toggl_time_entry(current_time_entry.id)
|
73
|
-
create_toggl_time_entry
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def update_toggl_time_entry(time_entry_id)
|
78
|
-
say("A timer was already running but without a project assigned. I updated the current time entry.")
|
79
|
-
|
80
|
-
time_entry = Toggl::TimeEntry.find(time_entry_id)
|
81
|
-
time_entry.description = @ticket_number.to_s
|
82
|
-
time_entry.tags = [@project_name.to_s]
|
83
|
-
time_entry.created_with = "curl"
|
84
|
-
time_entry.save
|
85
|
-
end
|
86
|
-
|
87
|
-
def create_toggl_time_entry
|
88
|
-
Toggl::TimeEntry.start(time_entry: { description: @ticket_number.to_s,
|
89
|
-
tags: [@project_name.to_s],
|
90
|
-
created_with: "curl" })
|
91
|
-
end
|
92
|
-
|
93
|
-
def stop_toggl_time_entry(time_entry_id)
|
94
|
-
Toggl::TimeEntry.find(time_entry_id).stop
|
95
|
-
end
|
96
|
-
end
|