renuo-cli 1.5.0 → 1.7.3

Sign up to get free protection for your applications and to get access to all the features.
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