renuo-cli 1.5.0 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 29de8d5a59af51defd1df2ea0829d7839e9e9780e7ad7903eb5f3ab8ad3a0d7b
4
- data.tar.gz: eefb399aa3b6950a841392253d1e002586962358ed3a812c4ec3fee435f3bd1b
2
+ SHA1:
3
+ metadata.gz: 577c679e818462872dd5489f96493b109af10e72
4
+ data.tar.gz: 6d3b7b14afe1fe44c114994d033b9eb8993d7f75
5
5
  SHA512:
6
- metadata.gz: 20aa5b70c11e26303aa7ac1832fe788bd2258b22cb1d516dbfdddaa3db36e67bce5bc40b339f86b9e6661c06e8f9aa0ea03614bbe4049713b9153ceedf0160fd
7
- data.tar.gz: 425889e37eeac52cfafed38422862827c95fea9d482e4d674187a2aab5dc05adaba242d23fcc28fb8d9a97516bb50783a0e22546b4074418609fa81ca1d66c93
6
+ metadata.gz: 552508988e60a07668c338010680213c1f016be6fd8b2170cf129ce7e8937b895bc3b3895bb511c2777daed3958a863c1bb0dce75df937252e6bec0b581c53d4
7
+ data.tar.gz: edc406e3f95a25a38651c6b06b4a950076bff3c25a429da050030b85455838c0e9cdb89ccaec296fd3134985e86e6e25df920af28be6828ab686a3765b3046c0
data/.rspec CHANGED
@@ -1,2 +1,2 @@
1
- --format documentation
2
1
  --color
2
+ --require spec_helper
@@ -1,6 +1,6 @@
1
1
  AllCops:
2
2
  Include:
3
- - 'lib/**/*'
3
+ - 'lib/**/*.rb'
4
4
  - 'spec/**/*'
5
5
  - '*.gemspec'
6
6
  Exclude:
@@ -9,6 +9,7 @@ AllCops:
9
9
  - 'lib/renuo/cli/app/heroku_users'
10
10
  - 'lib/renuo/cli/app/fetch_all_emails'
11
11
  - 'lib/renuo/cli/app/create_heroku_app'
12
+ - 'vendor/**/*'
12
13
 
13
14
  Naming/FileName:
14
15
  Exclude:
@@ -0,0 +1,19 @@
1
+ version: v1.0
2
+ name: master-deploy
3
+ agent:
4
+ machine:
5
+ type: e1-standard-2
6
+ os_image: ubuntu1804
7
+
8
+ blocks:
9
+ - name: master-deploy
10
+ task:
11
+ secrets:
12
+ - name: rubygems-deploy
13
+ jobs:
14
+ - name: master-deploy
15
+ commands:
16
+ - checkout --use-cache
17
+ - gem build renuo-cli
18
+ - chmod 0600 ~/.gem/credentials
19
+ - gem push renuo-cli-*.gem
@@ -0,0 +1,39 @@
1
+ version: "v1.0"
2
+ name: renuo-cli
3
+ agent:
4
+ machine:
5
+ type: e1-standard-2
6
+ os_image: ubuntu1804
7
+ auto_cancel:
8
+ running:
9
+ when: "true"
10
+
11
+ blocks:
12
+ - name: tests
13
+ execution_time_limit:
14
+ minutes: 10
15
+ task:
16
+ secrets:
17
+ - name: renuo-cli
18
+ env_vars:
19
+ - name: RAILS_ENV
20
+ value: test
21
+ prologue:
22
+ commands:
23
+ - checkout --use-cache
24
+ - cache restore
25
+ - gem update bundler
26
+ - bundle install -j 4 --path vendor/bundle
27
+ - cache store
28
+ jobs:
29
+ - name: tests
30
+ commands:
31
+ - bin/check
32
+ epilogue:
33
+ on_fail:
34
+ commands:
35
+ - artifact push job --expire-in 2w log/test.log
36
+ - artifact push job --expire-in 2w tmp/screenshots
37
+ promotions:
38
+ - name: master
39
+ pipeline_file: master-deploy.yml
data/README.md CHANGED
@@ -1,10 +1,6 @@
1
- [![Build Status Master](https://semaphoreci.com/api/v1/projects/a6f6bfd7-e48b-4035-858d-fc7cfa190608/1707289/badge.svg)](https://semaphoreci.com/renuo/renuo-cli)
2
- [![Coverage Status](https://coveralls.io/repos/renuo/renuo-cli/badge.svg?branch=master&service=github)](https://coveralls.io/github/renuo/renuo-cli?branch=master)
3
- [![Code Climate](https://codeclimate.com/github/renuo/renuo-cli/badges/gpa.svg)](https://codeclimate.com/github/renuo/renuo-cli)
1
+ # Renuo CLI
4
2
 
5
- # Renuo::Cli
6
-
7
- The Renuo command line. Used for various (internal) stuff.
3
+ The [Renuo](http://renuo.ch) command line. Used for various Renuo tasks
8
4
 
9
5
  ## Installation
10
6
 
@@ -36,18 +32,17 @@ To release a new version, update the version number in `version.rb`, and then ru
36
32
 
37
33
  ## Release
38
34
 
39
- To be able to release you must be logged in with an owners account of [rubygems.org/gems/renuo-cli](https://rubygems.org/gems/renuo-cli).
35
+ * Bump the version number in `lib/renuo/cli/version.rb` and commit to `master`
36
+
37
+ ### Automatic
38
+
39
+ * Deploy through Semaphore console by starting a promotion to master
40
+
41
+ ### Manual
40
42
 
41
43
  ```sh
42
- git flow release start [.....]
43
- # adjust version.rb
44
- bundle install
45
- git commit -av
46
- git flow release finish [.....]
47
- git push origin develop:develop
48
- git push origin master:master --tags
49
44
  gem build renuo-cli.gemspec
50
- gem push renuo-cli-X.Y.Z.gem (adapt the version)
45
+ gem push renuo-cli-*.gem
51
46
  ```
52
47
 
53
48
  ## Contributing
@@ -59,4 +54,3 @@ the [Contributor Covenant](contributor-covenant.org) code of conduct.
59
54
  ## License
60
55
 
61
56
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
62
-
data/Rakefile CHANGED
@@ -1,11 +1,8 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
- require 'coveralls/rake/task'
4
3
  require 'cucumber/rake/task'
5
4
 
6
5
  RSpec::Core::RakeTask.new(:spec)
7
6
  Cucumber::Rake::Task.new
8
- Coveralls::RakeTask.new
9
7
 
10
- task test_with_coveralls: [:spec, :cucumber, 'coveralls:push']
11
8
  task default: %i[spec cucumber]
@@ -2,6 +2,7 @@ require 'renuo/cli/version'
2
2
  require 'rubygems'
3
3
  require 'gems'
4
4
  require 'colorize'
5
+ require 'renuo/cli/app/services/renuo_cli_config'
5
6
  require 'renuo/cli/app/name_display'
6
7
  require 'renuo/cli/app/local_storage'
7
8
  require 'renuo/cli/app/list_large_git_files'
@@ -11,11 +12,14 @@ require 'renuo/cli/app/create_aws_project'
11
12
  require 'renuo/cli/app/create_heroku_app'
12
13
  require 'renuo/cli/app/configure_sentry'
13
14
  require 'renuo/cli/app/create_new_logins'
14
- require 'renuo/cli/app/release_project.rb'
15
- require 'renuo/cli/app/fetch_emails.rb'
16
- require 'renuo/cli/app/heroku_users.rb'
15
+ require 'renuo/cli/app/work'
16
+ require 'renuo/cli/app/release_project'
17
+ require 'renuo/cli/app/fetch_emails'
18
+ require 'renuo/cli/app/heroku_users'
17
19
  require 'renuo/cli/app/setup_uptimerobot'
18
20
  require 'renuo/cli/app/release_xing'
21
+ require 'renuo/cli/app/configure_semaphore'
22
+ require 'renuo/cli/app/toggl_redmine_comparator'
19
23
 
20
24
  module Renuo
21
25
  class CLI
@@ -128,6 +132,19 @@ module Renuo
128
132
  end
129
133
  end
130
134
 
135
+ command 'work' do |c|
136
+ c.syntax = 'renuo work'
137
+ c.summary = 'A utility command to start working on a ticket.'
138
+ c.description = 'When you start working on a feature, it will jump to the project folder, '\
139
+ 'start a new feature with the ticket number,'\
140
+ 'move the ticket in "In progress" state on Redmine and start Toggl with the '\
141
+ 'ticket number or add the ticket number to the running one if there was none.'
142
+ c.example 'renuo work start kinova 1234', 'start working on ticket 1234 on kinova project'
143
+ c.action do|args|
144
+ Work.new.run(args)
145
+ end
146
+ end
147
+
131
148
  command 'release' do |c|
132
149
  c.syntax = 'renuo release'
133
150
  c.summary = 'Release a projects state of develop (on github) to master in one command.'
@@ -203,6 +220,26 @@ module Renuo
203
220
  ReleaseXing.new.run
204
221
  end
205
222
  end
223
+
224
+ command 'configure-semaphore' do |c|
225
+ c.syntax = 'renuo configure-semaphore'
226
+ c.summary = 'Adds standard semaphore configuration files to a project and creates the notifications'
227
+ c.description = 'Run this command with a project, to add the semaphore configuration files '\
228
+ 'and create notifications.'
229
+ c.action do |args|
230
+ ConfigureSemaphore.new.call
231
+ end
232
+ end
233
+
234
+ command 'toggl-redmine' do |c|
235
+ c.syntax = 'renuo toggl-redmine'
236
+ c.summary = 'Compares your time entries between Toggl and Redmine'
237
+ c.description = 'This command extracts your time entries in Toggl and in Redmine and checks if they are'\
238
+ ' consistant. It can help you in your time booking to find if you booked something wrong'
239
+ c.action do |args|
240
+ TogglRedmineComparator.call
241
+ end
242
+ end
206
243
  end
207
244
  end
208
245
  end
@@ -0,0 +1,58 @@
1
+ require 'commander'
2
+ require_relative './environments'
3
+
4
+ class ConfigureSemaphore
5
+ attr_accessor :project_name, :environment
6
+
7
+ def initialize
8
+ @project_name = File.basename(Dir.getwd)
9
+ end
10
+
11
+ def call
12
+ return unless semaphore_cli_installed?
13
+ FileUtils.mkdir_p('.semaphore')
14
+ write_or_warn('.semaphore/semaphore.yml', render('templates/semaphore.yml.erb'))
15
+ %w[master develop testing].each do |environment|
16
+ @environment = environment
17
+ write_or_warn(".semaphore/#{environment}-deploy.yml", render('templates/semaphore-deploy.yml.erb'))
18
+ end
19
+ create_semaphore_notification
20
+ create_semaphore_secrets
21
+ end
22
+
23
+ private
24
+
25
+ def semaphore_cli_installed?
26
+ semaphore_cli_installed = `sem context | grep '*'`.strip == '* renuo_semaphoreci_com'
27
+ warn('You need to install and configure Semaphore CLI to run this command.') unless semaphore_cli_installed
28
+ semaphore_cli_installed
29
+ end
30
+
31
+ def create_semaphore_notification
32
+ system("sem create notifications #{project_name} "\
33
+ "--projects #{project_name} "\
34
+ '--branches "master,develop,testing" '\
35
+ "--slack-channels \"#project-#{project_name}\" "\
36
+ '--slack-endpoint "https://hooks.slack.com/services/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"')
37
+ end
38
+
39
+ def create_semaphore_secrets
40
+ system("sem create secret #{project_name}")
41
+ end
42
+
43
+ def render(template_file)
44
+ file_path = File.join(File.dirname(__FILE__), template_file)
45
+ semaphore_template = File.read(file_path)
46
+ renderer = ERB.new(semaphore_template)
47
+ renderer.result(binding)
48
+ end
49
+
50
+ def write_or_warn(file_path, content)
51
+ if File.exist?(file_path)
52
+ warn("#{file_path} exists already. I will not overwrite it.")
53
+ else
54
+ File.write(file_path, content)
55
+ say("#{file_path} added.")
56
+ end
57
+ end
58
+ end
@@ -2,16 +2,34 @@ require 'net/http'
2
2
  require 'uri'
3
3
 
4
4
  class FetchEmails
5
- def initialize; end
5
+ def initialize
6
+ @email_list_url = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vSqPiedBeGk0N75cxZApEohj5LrIWlHWUxTjfhkmK9aOsUltcqCn24sD1haIasUjVfd8UT8VdUKUc4h/pub?gid=703649940&single=true&output=csv'
7
+ end
6
8
 
7
9
  def fetch_emails
8
- uri = URI.parse('https://docs.google.com/spreadsheets/d/1rJFJQCgpz6GajMlGf0anKwVl5AY6TrYjMnJ4W-_0MK4/pub?gid=703649940&single=true&output=csv')
9
- response = Net::HTTP.get_response(uri)
10
- response.body.gsub(/\r\n/, "\n").split("\n").reject { |add| add == 'n/a' }
10
+ response = get_emails(@email_list_url)
11
+ response = handle_redirection(response) if response.is_a?(Net::HTTPRedirection)
12
+ format_response(response)
11
13
  end
12
14
 
13
15
  def run(_args)
14
- say '# Here is a complete lit of Renuo email addresses'.colorize :green
16
+ say '# Here is a complete list of Renuo email addresses'.colorize :green
15
17
  say fetch_emails.join("\n")
16
18
  end
19
+
20
+ private
21
+
22
+ def get_emails(url)
23
+ uri = URI.parse(url)
24
+ Net::HTTP.get_response(uri)
25
+ end
26
+
27
+ def handle_redirection(response)
28
+ location = response['location']
29
+ get_emails(location)
30
+ end
31
+
32
+ def format_response(response)
33
+ response.body.gsub(/\r\n/, "\n").split("\n").reject { |add| add == 'n/a' }
34
+ end
17
35
  end
@@ -0,0 +1,54 @@
1
+ require 'csv'
2
+
3
+ module Redmine
4
+ class CsvBaseService
5
+ API_LOCATION = 'https://redmine.renuo.ch'.freeze
6
+
7
+ def initialize(token)
8
+ @token = token
9
+ end
10
+
11
+ def get
12
+ http_response = http_request(generate_url)
13
+ encoded_body = http_response.body.force_encoding('ISO-8859-1').encode('UTF-8')
14
+ csv = parse_csv(encoded_body)
15
+ parse_results(csv)
16
+ end
17
+
18
+ private
19
+
20
+ def generate_url
21
+ URI("#{API_LOCATION}/time_entries/report.csv?#{query}&key=#{@token}")
22
+ end
23
+
24
+ def http_request(url)
25
+ req = Net::HTTP::Get.new(url)
26
+ Net::HTTP.start(url.hostname, url.port, use_ssl: true) do |http|
27
+ http.request(req)
28
+ end
29
+ end
30
+
31
+ def query
32
+ # to be implemented in concrete service
33
+ end
34
+
35
+ def parse_results(_csv)
36
+ # to be implemented in concrete service
37
+ end
38
+
39
+ def parse_csv(body)
40
+ separated_csv_entries = CSV.parse(body, col_sep: ',')
41
+ keys = separated_csv_entries.shift[1..-2]
42
+ entries = separated_csv_entries.shift[1..-2]
43
+ Hash[keys.zip(entries)]
44
+ rescue CSV::MalformedCSVError
45
+ raise_bad_data_error
46
+ end
47
+
48
+ def raise_bad_data_error
49
+ error = 'Malformed CSV, please use comma delimiters (Redmine language setting?)'
50
+ Rails.logger.error error
51
+ raise Redmine::BadData, error
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_resource'
2
+
3
+ module Redmine
4
+ class Issue < ActiveResource::Base
5
+ STATUSES = {
6
+ to_start: 9,
7
+ planned: 15,
8
+ in_progress: 2,
9
+ qa: 12
10
+ }.freeze
11
+
12
+ self.site = 'https://redmine.renuo.ch'
13
+ self.include_root_in_json = true
14
+
15
+ def self.headers
16
+ { 'X-Redmine-API-Key' => RenuoCliConfig.redmine_api_key }
17
+ end
18
+
19
+ def html_url
20
+ "#{self.class.site}issues/#{id}"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ require 'yaml'
2
+
3
+ class RenuoCliConfig
4
+ CONFIG_FILE_PATH = "#{File.expand_path('~')}/.renuo_cli".freeze
5
+
6
+ class << self
7
+ def redmine_api_key
8
+ get_config_value('REDMINE_API_KEY', 'https://redmine.renuo.ch/my/account')
9
+ end
10
+
11
+ def toggl_api_token
12
+ get_config_value('TOGGL_API_TOKEN', 'https://toggl.com/app/profile')
13
+ end
14
+
15
+ private
16
+
17
+ def get_config_value(name, open_url)
18
+ FileUtils.touch(CONFIG_FILE_PATH)
19
+ config = YAML.load_file(CONFIG_FILE_PATH, fallback: {})
20
+ value = config[name]
21
+ return value if value
22
+ system("open #{open_url}")
23
+ value = ask("You haven't set your #{name}, yet. Please provide one:")
24
+ config[name] = value
25
+ File.write(CONFIG_FILE_PATH, config.to_yaml)
26
+ value
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ version: v1.0
2
+ name: <%= environment %>-deploy
3
+ agent:
4
+ machine:
5
+ type: e1-standard-2
6
+ os_image: ubuntu1804
7
+
8
+ blocks:
9
+ - name: <%= environment %>-deploy
10
+ task:
11
+ secrets:
12
+ - name: heroku-deploy
13
+ env_vars:
14
+ - name: HEROKU_REMOTE
15
+ value: https://git.heroku.com/<%= project_name %>-<%= environment %>.git
16
+ jobs:
17
+ - name: <%= environment %>-deploy
18
+ commands:
19
+ - checkout --use-cache
20
+ - ssh-keyscan -H heroku.com >> ~/.ssh/known_hosts
21
+ - chmod 600 ~/.ssh/id_rsa_semaphore_heroku
22
+ - ssh-add ~/.ssh/id_rsa_semaphore_heroku
23
+ - git config --global url.ssh://git@heroku.com/.insteadOf https://git.heroku.com/
24
+ - git remote add heroku $HEROKU_REMOTE
25
+ - git push heroku -f $SEMAPHORE_GIT_BRANCH:master
@@ -0,0 +1,70 @@
1
+ version: "v1.0"
2
+ name: <%= project_name %>
3
+ agent:
4
+ machine:
5
+ type: e1-standard-2
6
+ os_image: ubuntu1804
7
+ auto_cancel:
8
+ running:
9
+ when: "true"
10
+
11
+ blocks:
12
+ - name: cache
13
+ execution_time_limit:
14
+ minutes: 10
15
+ dependencies: []
16
+ task:
17
+ secrets:
18
+ - name: <%= project_name %>
19
+ jobs:
20
+ - name: cache
21
+ commands:
22
+ - checkout --use-cache
23
+ - cache restore nvm-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),nvm-$SEMAPHORE_GIT_BRANCH,nvm-develop,nvm-master
24
+ - cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-$SEMAPHORE_GIT_BRANCH,gems-develop,gems-master
25
+ - cache restore yarn-cache-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),yarn-cache-$SEMAPHORE_GIT_BRANCH,yarn-cache-develop,yarn-cache-master
26
+ - cache restore node-modules-$SEMAPHORE_GIT_BRANCH-$(checksum yarn.lock),node-modules-$SEMAPHORE_GIT_BRANCH,node-modules-develop,node-modules-master
27
+ - bundle install --deployment -j 4 --path vendor/bundle
28
+ - nvm install
29
+ - bin/yarn install --cache-folder ~/.cache/yarn
30
+ - cache store
31
+ - name: tests
32
+ execution_time_limit:
33
+ minutes: 10
34
+ dependencies: ['cache']
35
+ task:
36
+ secrets:
37
+ - name: <%= project_name %>
38
+ env_vars:
39
+ - name: DATABASE_URL
40
+ value: postgresql://postgres@localhost/test?encoding=utf8
41
+ - name: RAILS_ENV
42
+ value: test
43
+ prologue:
44
+ commands:
45
+ - checkout --use-cache
46
+ - cache restore nvm-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),nvm-$SEMAPHORE_GIT_BRANCH,nvm-develop,nvm-master
47
+ - cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-$SEMAPHORE_GIT_BRANCH,gems-develop,gems-master
48
+ - cache restore yarn-cache-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),yarn-cache-$SEMAPHORE_GIT_BRANCH,yarn-cache-develop,yarn-cache-master
49
+ - cache restore node-modules-$SEMAPHORE_GIT_BRANCH-$(checksum yarn.lock),node-modules-$SEMAPHORE_GIT_BRANCH,node-modules-develop,node-modules-master
50
+ - bundle install --deployment --path vendor/bundle
51
+ - bin/yarn install --cache-folder ~/.cache/yarn
52
+ - sem-service start postgres
53
+ - bundle exec rails db:create db:schema:load
54
+ jobs:
55
+ - name: tests
56
+ commands:
57
+ - bin/check
58
+ promotions:
59
+ - name: develop
60
+ pipeline_file: develop-deploy.yml
61
+ auto_promote:
62
+ when: "result = 'passed' and branch = 'develop'"
63
+ - name: master
64
+ pipeline_file: master-deploy.yml
65
+ auto_promote:
66
+ when: "result = 'passed' and branch = 'master'"
67
+ - name: testing
68
+ pipeline_file: testing-deploy.yml
69
+ auto_promote:
70
+ when: "result = 'passed' and branch = 'testing'"
@@ -0,0 +1,30 @@
1
+ require 'active_resource'
2
+
3
+ module Toggl
4
+ module CustomJsonFormat
5
+ include ActiveResource::Formats::JsonFormat
6
+
7
+ # rubocop:disable Style/ModuleFunction
8
+ extend self
9
+ # rubocop:enable Style/ModuleFunction
10
+
11
+ def decode(json)
12
+ ActiveSupport::JSON.decode(json)['data']
13
+ end
14
+ end
15
+
16
+ class Detail < ActiveResource::Base
17
+ self.format = CustomJsonFormat
18
+ self.site = 'https://toggl.com/reports/api/v2'
19
+ self.include_root_in_json = true
20
+ self.include_format_in_path = false
21
+
22
+ def self.user
23
+ RenuoCliConfig.toggl_api_token
24
+ end
25
+
26
+ def self.password
27
+ 'api_token'
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ require 'active_resource'
2
+
3
+ module Toggl
4
+ class TimeEntry < 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.current
18
+ data = get(:current)['data']
19
+ data ? new(data) : nil
20
+ end
21
+
22
+ def self.start(params)
23
+ post(:start, {}, params.to_json)
24
+ end
25
+
26
+ def stop
27
+ put(:stop)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ module Toggl
2
+ class User < ActiveResource::Base
3
+ self.site = 'https://www.toggl.com/api/v8/'
4
+ self.include_root_in_json = true
5
+ self.include_format_in_path = false
6
+
7
+ def self.user
8
+ RenuoCliConfig.toggl_api_token
9
+ end
10
+
11
+ def self.password
12
+ 'api_token'
13
+ end
14
+
15
+ def self.custom_method_collection_url(method_name, options)
16
+ prefix_options, query_options = split_options(options)
17
+ "#{prefix(prefix_options)}#{method_name}#{format_extension}#{query_string(query_options)}"
18
+ end
19
+
20
+ def self.me
21
+ new(get(:me)['data'])
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ require 'active_resource'
2
+
3
+ module Toggl
4
+ class Workspace < 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
+ end
17
+ end
@@ -0,0 +1,145 @@
1
+ require 'commander'
2
+ require 'csv'
3
+ require_relative './environments'
4
+ require_relative './fetch_emails'
5
+ require 'renuo/cli/app/toggl/workspace'
6
+ require 'renuo/cli/app/toggl/detail'
7
+ require 'renuo/cli/app/toggl/user'
8
+ require 'terminal-table'
9
+ require 'colorize'
10
+
11
+ class TogglRedmineComparator
12
+ class << self
13
+ def call(days_behind = 7)
14
+ report = {}
15
+ since_date = days_behind.days.before(Date.yesterday).strftime('%F')
16
+ until_date = Date.yesterday.strftime('%F')
17
+ extract_redmine(report, since_date, until_date)
18
+ extract_toggl(report, since_date, until_date)
19
+ report = report.sort.reverse.to_h
20
+ print_table(report)
21
+ report
22
+ end
23
+
24
+ private
25
+
26
+ def print_table(report)
27
+ rows = []
28
+ report.each do |date, value|
29
+ rows << colorize_table_row(date, value)
30
+ rows << :separator
31
+ end
32
+ rows.pop
33
+ table = Terminal::Table.new headings: %w[Day Redmine Toggl].map(&:cyan), rows: rows,
34
+ style: { padding_left: 2, padding_right: 2,
35
+ border_x: '-'.blue, border_y: '|'.blue, border_i: '+'.blue }
36
+ puts table
37
+ end
38
+
39
+ def colorize_table_row(date, value)
40
+ printed_day = date.strftime('%F %a')
41
+ printed_redmine = to_time(value[:redmine])
42
+ printed_toggl = to_time(value[:toggl])
43
+ colorize_method = colorization_for_value(value)
44
+ [printed_day, printed_redmine, printed_toggl].map { |v| v.colorize(colorize_method) }
45
+ end
46
+
47
+ def colorization_for_value(value)
48
+ if more_toggl?(value)
49
+ :red
50
+ elsif more_redmine?(value)
51
+ :default
52
+ else
53
+ :green
54
+ end
55
+ end
56
+
57
+ def extract_redmine(report, since_date, _until_date)
58
+ encoded_body = perform_redmine_call(since_date)
59
+ csv = convert_redmine_csv(encoded_body)
60
+ csv.each do |date, entry|
61
+ report[Date.parse(date)] ||= default_value
62
+ report[Date.parse(date)][:redmine] = to_seconds(entry) if entry.present?
63
+ end
64
+ end
65
+
66
+ def convert_redmine_csv(encoded_body)
67
+ separated_csv_entries = CSV.parse(encoded_body, col_sep: ',')
68
+ keys = separated_csv_entries.shift[1..-2]
69
+ entries = separated_csv_entries.shift[1..-2]
70
+ keys.zip(entries)
71
+ end
72
+
73
+ def perform_redmine_call(since_date)
74
+ query = generate_redmine_query(since_date)
75
+ url = URI("https://redmine.renuo.ch/time_entries/report.csv?#{query}")
76
+ req = Net::HTTP::Get.new(url)
77
+ req['X-Redmine-API-Key'] = RenuoCliConfig.redmine_api_key
78
+ response = Net::HTTP.start(url.hostname, url.port, use_ssl: true) { |http| http.request(req) }
79
+ response.body.force_encoding('ISO-8859-1').encode('UTF-8')
80
+ end
81
+
82
+ def generate_redmine_query(since_date)
83
+ URI.encode_www_form(
84
+ [['utf8', '✓'], ['criteria[]', 'user'],
85
+ ['f[]', 'spent_on'], ['f[]', 'user_id'],
86
+ ['op[spent_on]', '>='], ['op[user_id]', '='],
87
+ ['v[spent_on][]', since_date], ['v[user_id][]', 'me'],
88
+ ['f[]', ''],
89
+ ['c[]', 'project'], ['c[]', 'spent_on'], ['c[]', 'user'], ['c[]', 'activity'], ['c[]', 'issue'],
90
+ ['c[]', 'comments'], ['c[]', 'hours'], %w[columns day], ['criteria[]', '']]
91
+ )
92
+ end
93
+
94
+ def extract_toggl(report, since_date, until_date)
95
+ user_id = Toggl::User.me.id
96
+ workspace_ids = Toggl::Workspace.all.map(&:id)
97
+
98
+ workspace_ids.each do |workspace_id|
99
+ time_entries = Toggl::Detail.where(since: since_date, until: until_date,
100
+ user_agent: 'renuo-cli', workspace_id: workspace_id, user_ids: user_id)
101
+ parse_toggl_entries(report, time_entries)
102
+ end
103
+ end
104
+
105
+ def parse_toggl_entries(report, time_entries)
106
+ time_entries
107
+ .reject { |te| te.end.nil? }
108
+ .group_by { |time_entry| Date.parse(time_entry.end) }
109
+ .each do |date, grouped_time_entries|
110
+ report[date] ||= default_value
111
+ report[date][:toggl] += grouped_time_entries.sum(&:dur)
112
+ end
113
+ end
114
+
115
+ def default_value
116
+ { redmine: 0.0, toggl: 0.0 }
117
+ end
118
+
119
+ def to_time(value)
120
+ sec = value / 1000.0
121
+ min, _sec = sec.divmod(60.0)
122
+ hour, min = min.divmod(60.0)
123
+ format('%02d:%02d', hour, min)
124
+ end
125
+
126
+ def to_seconds(value)
127
+ hours, minutes = value.to_d.divmod(1.0)
128
+ (hours * 60 * 60 * 1000) + (minutes * 60 * 60 * 1000)
129
+ end
130
+
131
+ def non_working_day?(value)
132
+ [value[:redmine], value[:toggl]].all? { |v| v == 0.0 }
133
+ end
134
+
135
+ BUFFER = 20_000
136
+
137
+ def more_toggl?(value)
138
+ (value[:toggl] - value[:redmine]) > BUFFER
139
+ end
140
+
141
+ def more_redmine?(value)
142
+ (value[:redmine] - value[:toggl]) > BUFFER
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,94 @@
1
+ require 'fileutils'
2
+ require 'renuo/cli/app/redmine/issue'
3
+ require 'renuo/cli/app/toggl/time_entry'
4
+
5
+ class Work
6
+ ACTIONS = %w[start].freeze
7
+
8
+ def run(args)
9
+ ActiveResource::Base.logger = Logger.new(STDOUT)
10
+ @action, @project_name, @ticket_number = args
11
+ validate_action
12
+ validate_project_name
13
+ validate_ticket_number
14
+ start_feature_branch
15
+ update_redmine_ticket
16
+ start_toggl
17
+ end
18
+
19
+ private
20
+
21
+ # TODO: I want to implement also the stop action.
22
+ def validate_action
23
+ abort('>> No action given. It must be start') unless ACTIONS.include? @action
24
+ end
25
+
26
+ def validate_project_name
27
+ abort('>> No project name given.') unless @project_name
28
+ end
29
+
30
+ def validate_ticket_number
31
+ abort('>> No ticket number given.') unless @ticket_number
32
+ issue = Redmine::Issue.find(@ticket_number)
33
+ open_statuses = Redmine::Issue::STATUSES.values_at(:to_start, :planned, :in_progress, :qa)
34
+ return if open_statuses.include?(issue.status.id)
35
+
36
+ system("open #{issue.html_url}")
37
+ abort('>> Ticket should be in an open status')
38
+ end
39
+
40
+ def start_feature_branch
41
+ project_folder = `autojump #{@project_name}`.strip
42
+ system("cd #{project_folder} && git stash && git checkout develop "\
43
+ "&& git pull && git flow feature start #{@ticket_number}")
44
+ end
45
+
46
+ def update_redmine_ticket
47
+ issue = Redmine::Issue.find(@ticket_number)
48
+ issue.status_id = Redmine::Issue::STATUSES[:in_progress]
49
+ issue.save
50
+ system("open #{issue.html_url}")
51
+ end
52
+
53
+ def start_toggl
54
+ current_time_entry = Toggl::TimeEntry.current
55
+ if current_time_entry.nil?
56
+ create_toggl_time_entry
57
+ elsif current_time_entry.description.nil?
58
+ update_toggl_time_entry(current_time_entry.id)
59
+ else
60
+ existing_toggl(current_time_entry)
61
+ end
62
+ end
63
+
64
+ def existing_toggl(current_time_entry)
65
+ say("A timer '#{current_time_entry.description}' was already running.")
66
+ if current_time_entry.description.to_i == @ticket_number.to_i
67
+ say('I will keep using it')
68
+ else
69
+ say('I stopped it and started a new time entry.')
70
+ stop_toggl_time_entry(current_time_entry.id)
71
+ create_toggl_time_entry
72
+ end
73
+ end
74
+
75
+ def update_toggl_time_entry(time_entry_id)
76
+ say('A timer was already running but without a project assigned. I updated the current time entry.')
77
+
78
+ time_entry = Toggl::TimeEntry.find(time_entry_id)
79
+ time_entry.description = @ticket_number.to_s
80
+ time_entry.tags = [@project_name.to_s]
81
+ time_entry.created_with = 'curl'
82
+ time_entry.save
83
+ end
84
+
85
+ def create_toggl_time_entry
86
+ Toggl::TimeEntry.start(time_entry: { description: @ticket_number.to_s,
87
+ tags: [@project_name.to_s],
88
+ created_with: 'curl' })
89
+ end
90
+
91
+ def stop_toggl_time_entry(time_entry_id)
92
+ Toggl::TimeEntry.find(time_entry_id).stop
93
+ end
94
+ end
@@ -1,6 +1,6 @@
1
1
  module Renuo
2
2
  module Cli
3
- VERSION = '1.5.0'.freeze
3
+ VERSION = '1.7.3'.freeze
4
4
  NAME = 'renuo-cli'.freeze
5
5
  end
6
6
  end
@@ -17,14 +17,15 @@ Gem::Specification.new do |spec|
17
17
  spec.executables << 'renuo'
18
18
  spec.require_paths = ['lib']
19
19
 
20
+ spec.add_dependency 'activeresource', '~> 5.1.0'
20
21
  spec.add_dependency 'colorize', '~> 0'
21
22
  spec.add_dependency 'commander', '~> 4.0'
22
23
  spec.add_dependency 'gems', '~> 1.1'
23
24
  spec.add_dependency 'redcarpet', '~> 3.0'
25
+ spec.add_dependency 'terminal-table'
24
26
 
25
27
  spec.add_development_dependency 'aruba', '~> 0.14.5'
26
28
  spec.add_development_dependency 'bundler', '~> 2.0'
27
- spec.add_development_dependency 'coveralls', '~> 0.8.9'
28
29
  spec.add_development_dependency 'cucumber', '~> 3.1'
29
30
  spec.add_development_dependency 'dotenv', '~> 2.7.2'
30
31
  spec.add_development_dependency 'mdl', '~> 0.4.0'
@@ -32,7 +33,8 @@ Gem::Specification.new do |spec|
32
33
  spec.add_development_dependency 'rake', '~> 10.0'
33
34
  spec.add_development_dependency 'rspec', '~> 3.5'
34
35
  spec.add_development_dependency 'rubocop', '0.55.0'
35
- spec.add_development_dependency 'simplecov', '0.10.0' # TODO: update and fix coverage
36
+ spec.add_development_dependency 'simplecov'
37
+ spec.add_development_dependency 'simplecov-console'
36
38
  spec.add_development_dependency 'vcr', '~> 4.0.0'
37
39
  spec.add_development_dependency 'webmock', '~> 3.5.1'
38
40
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: renuo-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Renuo AG
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-24 00:00:00.000000000 Z
11
+ date: 2020-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activeresource
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.1.0
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: colorize
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -67,47 +81,47 @@ dependencies:
67
81
  - !ruby/object:Gem::Version
68
82
  version: '3.0'
69
83
  - !ruby/object:Gem::Dependency
70
- name: aruba
84
+ name: terminal-table
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - "~>"
87
+ - - ">="
74
88
  - !ruby/object:Gem::Version
75
- version: 0.14.5
76
- type: :development
89
+ version: '0'
90
+ type: :runtime
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - "~>"
94
+ - - ">="
81
95
  - !ruby/object:Gem::Version
82
- version: 0.14.5
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
- name: bundler
98
+ name: aruba
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
101
  - - "~>"
88
102
  - !ruby/object:Gem::Version
89
- version: '2.0'
103
+ version: 0.14.5
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
- version: '2.0'
110
+ version: 0.14.5
97
111
  - !ruby/object:Gem::Dependency
98
- name: coveralls
112
+ name: bundler
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
115
  - - "~>"
102
116
  - !ruby/object:Gem::Version
103
- version: 0.8.9
117
+ version: '2.0'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
122
  - - "~>"
109
123
  - !ruby/object:Gem::Version
110
- version: 0.8.9
124
+ version: '2.0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: cucumber
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -210,16 +224,30 @@ dependencies:
210
224
  name: simplecov
211
225
  requirement: !ruby/object:Gem::Requirement
212
226
  requirements:
213
- - - '='
227
+ - - ">="
214
228
  - !ruby/object:Gem::Version
215
- version: 0.10.0
229
+ version: '0'
216
230
  type: :development
217
231
  prerelease: false
218
232
  version_requirements: !ruby/object:Gem::Requirement
219
233
  requirements:
220
- - - '='
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: simplecov-console
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
221
242
  - !ruby/object:Gem::Version
222
- version: 0.10.0
243
+ version: '0'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
223
251
  - !ruby/object:Gem::Dependency
224
252
  name: vcr
225
253
  requirement: !ruby/object:Gem::Requirement
@@ -257,14 +285,14 @@ executables:
257
285
  extensions: []
258
286
  extra_rdoc_files: []
259
287
  files:
260
- - ".coveralls.yml"
261
288
  - ".editorconfig"
262
289
  - ".gitignore"
263
290
  - ".mdlrc"
264
291
  - ".rspec"
265
292
  - ".rubocop.yml"
266
293
  - ".ruby-version"
267
- - ".travis.yml"
294
+ - ".semaphore/master-deploy.yml"
295
+ - ".semaphore/semaphore.yml"
268
296
  - CODE_OF_CONDUCT.md
269
297
  - Gemfile
270
298
  - LICENSE.txt
@@ -276,6 +304,7 @@ files:
276
304
  - bin/run
277
305
  - bin/setup
278
306
  - lib/renuo/cli.rb
307
+ - lib/renuo/cli/app/configure_semaphore.rb
279
308
  - lib/renuo/cli/app/configure_sentry.rb
280
309
  - lib/renuo/cli/app/create_aws_project.rb
281
310
  - lib/renuo/cli/app/create_heroku_app.rb
@@ -288,15 +317,26 @@ files:
288
317
  - lib/renuo/cli/app/list_large_git_files.rb
289
318
  - lib/renuo/cli/app/local_storage.rb
290
319
  - lib/renuo/cli/app/name_display.rb
320
+ - lib/renuo/cli/app/redmine/csv_base_service.rb
321
+ - lib/renuo/cli/app/redmine/issue.rb
291
322
  - lib/renuo/cli/app/release_project.rb
292
323
  - lib/renuo/cli/app/release_xing.rb
293
324
  - lib/renuo/cli/app/services/cloudfront_config_service.rb
294
325
  - lib/renuo/cli/app/services/markdown_parser_service.rb
326
+ - lib/renuo/cli/app/services/renuo_cli_config.rb
295
327
  - lib/renuo/cli/app/setup_uptimerobot.rb
328
+ - lib/renuo/cli/app/templates/semaphore-deploy.yml.erb
329
+ - lib/renuo/cli/app/templates/semaphore.yml.erb
330
+ - lib/renuo/cli/app/toggl/detail.rb
331
+ - lib/renuo/cli/app/toggl/time_entry.rb
332
+ - lib/renuo/cli/app/toggl/user.rb
333
+ - lib/renuo/cli/app/toggl/workspace.rb
334
+ - lib/renuo/cli/app/toggl_redmine_comparator.rb
296
335
  - lib/renuo/cli/app/upgrade_laptop.rb
297
336
  - lib/renuo/cli/app/upgrade_laptop/run_command.rb
298
337
  - lib/renuo/cli/app/upgrade_laptop/upgrade_laptop_execution.rb
299
338
  - lib/renuo/cli/app/upgrade_laptop/upgrade_mac_os.rb
339
+ - lib/renuo/cli/app/work.rb
300
340
  - lib/renuo/cli/version.rb
301
341
  - renuo-cli.gemspec
302
342
  - vcr_cassettes/successful_uptimerobot_calls.yml
@@ -320,7 +360,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
320
360
  - !ruby/object:Gem::Version
321
361
  version: '0'
322
362
  requirements: []
323
- rubygems_version: 3.0.6
363
+ rubyforge_project:
364
+ rubygems_version: 2.6.14
324
365
  signing_key:
325
366
  specification_version: 4
326
367
  summary: The Renuo CLI automates some common workflows.
@@ -1 +0,0 @@
1
-
@@ -1,12 +0,0 @@
1
- language: ruby
2
- cache: bundler
3
- rvm:
4
- - 2.1
5
- - 2.2
6
- - 2.3.1
7
- before_install: gem install bundler -v 1.10.6
8
- install:
9
- - bundle install --retry=3
10
- script:
11
- - bundle exec rake test_with_coveralls
12
- - bundle exec rubocop