trollolo 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +21 -2
  3. data/.rubocop_todo.yml +105 -270
  4. data/CHANGELOG.md +10 -0
  5. data/CONTRIBUTING.md +6 -4
  6. data/Gemfile +1 -1
  7. data/README.md +10 -1
  8. data/Rakefile +4 -4
  9. data/bin/trollolo +2 -2
  10. data/lib/backup.rb +21 -23
  11. data/lib/burndown_chart.rb +46 -49
  12. data/lib/burndown_data.rb +21 -21
  13. data/lib/card.rb +23 -32
  14. data/lib/checklist.rb +13 -1
  15. data/lib/cli.rb +105 -110
  16. data/lib/column.rb +11 -10
  17. data/lib/empty_column.rb +2 -2
  18. data/lib/scrum/backlog_mover.rb +3 -3
  19. data/lib/scrum/card_type_detection.rb +1 -1
  20. data/lib/scrum/creator.rb +7 -7
  21. data/lib/scrum/prioritizer.rb +1 -1
  22. data/lib/scrum/sprint_board.rb +4 -4
  23. data/lib/scrum/sprint_cleaner.rb +3 -3
  24. data/lib/scrum/sprint_planning_board.rb +2 -4
  25. data/lib/scrum_board.rb +3 -3
  26. data/lib/settings.rb +29 -27
  27. data/lib/trello_service.rb +1 -1
  28. data/lib/trello_wrapper.rb +9 -9
  29. data/lib/version.rb +1 -1
  30. data/spec/data/card.json +19 -0
  31. data/spec/data/trollolorc +3 -0
  32. data/spec/integration/command_line_spec.rb +14 -14
  33. data/spec/integration/create_burndown_spec.rb +25 -25
  34. data/spec/integration/integration_spec_helper.rb +1 -1
  35. data/spec/integration/wrapper/credentials_input_wrapper +7 -11
  36. data/spec/integration/wrapper/empty_config_trollolo_wrapper +2 -2
  37. data/spec/integration/wrapper/trollolo_wrapper +2 -2
  38. data/spec/unit/backup_spec.rb +14 -14
  39. data/spec/unit/burndown_chart_spec.rb +176 -184
  40. data/spec/unit/burndown_data_spec.rb +21 -23
  41. data/spec/unit/card_spec.rb +38 -24
  42. data/spec/unit/cli_spec.rb +57 -57
  43. data/spec/unit/empty_column_spec.rb +1 -1
  44. data/spec/unit/retrieve_data_spec.rb +13 -13
  45. data/spec/unit/scrum/backlog_mover_spec.rb +8 -8
  46. data/spec/unit/scrum/card_type_detection_spec.rb +12 -12
  47. data/spec/unit/scrum/creator_spec.rb +8 -8
  48. data/spec/unit/scrum/prioritizer_spec.rb +19 -19
  49. data/spec/unit/scrum/priority_name_spec.rb +16 -16
  50. data/spec/unit/scrum/sprint_board_spec.rb +3 -3
  51. data/spec/unit/scrum/sprint_cleaner_spec.rb +15 -15
  52. data/spec/unit/scrum/sprint_planning_board_spec.rb +2 -2
  53. data/spec/unit/scrum_board_spec.rb +56 -56
  54. data/spec/unit/settings_spec.rb +23 -23
  55. data/spec/unit/spec_helper.rb +3 -3
  56. data/spec/unit/support/test_data_operations.rb +4 -0
  57. data/spec/unit/support/update_webmock_data +9 -11
  58. data/spec/unit/support/vcr.rb +3 -3
  59. data/spec/unit/support/webmocks.rb +21 -12
  60. data/spec/unit/trello_wrapper_spec.rb +40 -29
  61. data/trollolo.gemspec +3 -3
  62. metadata +8 -5
@@ -3,6 +3,16 @@
3
3
  ## Master (unreleased)
4
4
 
5
5
 
6
+ ## Version 0.2.0
7
+
8
+ * Require Ruby to be >= 2.2.0 and < 2.4.2 until #139 is fixed.
9
+ * `burndown-init` only requires the `--board-id` option. The `--output` option
10
+ is optional and defaults to the current working directory. Fix #103.
11
+ * Allow to define checklists that should not be parsed as task lists. Such lists
12
+ can be added in the trollolorc as `no_task_checklists`.
13
+ * Allow to provide a board id when calling `burndown`. Fix #100.
14
+ * Set the attached image as cover in `burndown --plot-to-board`. Fix #124.
15
+
6
16
  ## Version 0.1.1
7
17
 
8
18
  * Fix the bug introduced whith always setting the burndown chart as the cover
@@ -1,12 +1,14 @@
1
+ # CONTRIBUTING GUIDE
2
+
1
3
  Contributions are welcome! :smile:
2
4
 
3
- # Report bugs or suggest changes
5
+ ## Report bugs or suggest changes
4
6
 
5
7
  To report bugs, suggest changes or provide ideas please open GitHub issues.
6
8
 
7
9
  To discuss anything please [contact Cornelius](mailto:cschum@suse.de).
8
10
 
9
- # Contribute code
11
+ ## Contribute code
10
12
 
11
13
  To contribute code please open pull requests.
12
14
 
@@ -14,7 +16,7 @@ Ensure that rspec and rubocop pass locally before sending your PR and always tha
14
16
 
15
17
  If your changes include important new features or bug fixes please add them to the [Master (unreleased) section fo the CHANGELOG.md](https://github.com/openSUSE/trollolo/blob/master/CHANGELOG.md#master-unreleased)
16
18
 
17
- ## To run rspec test
19
+ ### To run rspec test
18
20
 
19
21
  To run all the rspec test:
20
22
 
@@ -28,7 +30,7 @@ To only run the test in the line 415 of the file:
28
30
 
29
31
  `bundle exec rspec spec/unit/burndown_chart_spec.rb:415`
30
32
 
31
- ## To run rubocop
33
+ ### To run rubocop
32
34
 
33
35
  To run Rubocop displaying cop names in offense messages:
34
36
 
data/Gemfile CHANGED
@@ -14,6 +14,6 @@ group :test do
14
14
  gem 'pry'
15
15
  gem 'ronn', '>=0.7.3'
16
16
  gem 'rake'
17
- gem 'rubocop', '~> 0.49.1'
17
+ gem 'rubocop', '0.52.1'
18
18
  gem 'rspec-mocks'
19
19
  end
data/README.md CHANGED
@@ -21,6 +21,8 @@ which demonstrates the expected structure.
21
21
 
22
22
  ## Installation
23
23
 
24
+ You need to have Ruby install. Trollolo works with Ruby 2.2-2.4.1, but currently it doesn't work with 2.4.2. See [#139](https://github.com/openSUSE/trollolo/issues/139) for more information.
25
+
24
26
  You can install Trollolo as gem with `gem install trollolo`.
25
27
 
26
28
  For the chart generation you will need a working matplotlib installation and
@@ -91,7 +93,14 @@ the generated data and results to make it even more automatic.
91
93
 
92
94
  The work flow goes as follows:
93
95
 
94
- Create an initial working directory for the burndown chart generation:
96
+ Start the workflow by using the current directory as working directory and
97
+ initialize it for the burndown chart generation:
98
+
99
+ trollolo burndown-init --board-id=MYBOARDID
100
+
101
+ By default, trollolo uses the current directory as working directory, if you
102
+ want to specify another directory as working directory, use the `--output`
103
+ option as follows:
95
104
 
96
105
  trollolo burndown-init --board-id=MYBOARDID --output=WORKING_DIR
97
106
 
data/Rakefile CHANGED
@@ -1,12 +1,12 @@
1
1
  namespace :man_pages do
2
2
  task :build do
3
- puts " Building man pages"
4
- system "ronn man/*.md"
3
+ puts ' Building man pages'
4
+ system 'ronn man/*.md'
5
5
  end
6
6
  end
7
7
 
8
8
  namespace :gem do
9
- task :build => ["man_pages:build"] do
10
- system "gem build trollolo.gemspec"
9
+ task build: ['man_pages:build'] do
10
+ system 'gem build trollolo.gemspec'
11
11
  end
12
12
  end
@@ -18,12 +18,12 @@
18
18
 
19
19
  require_relative '../lib/trollolo'
20
20
 
21
- config_path = ENV["TROLLOLO_CONFIG_PATH"] || File.expand_path("~/.trollolorc")
21
+ config_path = ENV['TROLLOLO_CONFIG_PATH'] || File.expand_path('~/.trollolorc')
22
22
 
23
23
  Cli.settings = Settings.new(config_path)
24
24
 
25
25
  # Set debug flag, so thor throws exceptions on error
26
- ENV["THOR_DEBUG"] = "1"
26
+ ENV['THOR_DEBUG'] = '1'
27
27
  begin
28
28
  Cli.check_unknown_options!
29
29
  Cli.start
@@ -4,9 +4,9 @@ class Backup
4
4
 
5
5
  attr_accessor :directory
6
6
 
7
- def initialize settings
7
+ def initialize(settings)
8
8
  @settings = settings
9
- @directory = File.expand_path("~/.trollolo/backup")
9
+ @directory = File.expand_path('~/.trollolo/backup')
10
10
  end
11
11
 
12
12
  def backup(board_id)
@@ -17,7 +17,7 @@ class Backup
17
17
 
18
18
  data = trello.backup(board_id)
19
19
 
20
- File.open(File.join(backup_path, "board.json"), "w") do |f|
20
+ File.open(File.join(backup_path, 'board.json'), 'w') do |f|
21
21
  f.write(data)
22
22
  end
23
23
  end
@@ -31,35 +31,33 @@ class Backup
31
31
 
32
32
  backup_path = File.join(@directory, board_id)
33
33
 
34
- board = JSON.parse(File.read(File.join(backup_path, "board.json")))
34
+ board = JSON.parse(File.read(File.join(backup_path, 'board.json')))
35
35
 
36
- out.puts board["name"]
36
+ out.puts board['name']
37
37
 
38
38
  lists = {}
39
- board["lists"].each do |list|
40
- lists[list["id"]] = []
39
+ board['lists'].each do |list|
40
+ lists[list['id']] = []
41
41
  end
42
- board["cards"].each do |card|
43
- if lists[card["idList"]]
44
- lists[card["idList"]].push(card)
45
- end
42
+ board['cards'].each do |card|
43
+ lists[card['idList']].push(card) if lists[card['idList']]
46
44
  end
47
45
 
48
- board["lists"].each do |list|
46
+ board['lists'].each do |list|
49
47
  out.puts " #{list['name']}"
50
- lists[list["id"]].each do |card|
51
- out.puts " " + card["name"]
52
- if options["show-descriptions"]
53
- if !card["desc"].empty?
54
- out.puts " Description"
55
- out.puts " " + card["desc"]
48
+ lists[list['id']].each do |card|
49
+ out.puts ' ' + card['name']
50
+ if options['show-descriptions']
51
+ unless card['desc'].empty?
52
+ out.puts ' Description'
53
+ out.puts ' ' + card['desc']
56
54
  end
57
55
  end
58
- card["checklists"].each do |checklist|
59
- out.puts " " + checklist["name"]
60
- checklist["checkItems"].each do |checklist_item|
61
- out.puts " " + checklist_item["name"] + " (" +
62
- checklist_item["state"] + ")"
56
+ card['checklists'].each do |checklist|
57
+ out.puts ' ' + checklist['name']
58
+ checklist['checkItems'].each do |checklist_item|
59
+ out.puts ' ' + checklist_item['name'] + ' (' +
60
+ checklist_item['state'] + ')'
63
61
  end
64
62
  end
65
63
  end
@@ -24,53 +24,53 @@ class BurndownChart
24
24
  @settings = settings
25
25
 
26
26
  @data = {
27
- "meta" => {
28
- "board_id" => nil,
29
- "sprint" => 1,
30
- "total_days" => 10,
31
- "weekend_lines" => [ 3.5, 8.5 ]
27
+ 'meta' => {
28
+ 'board_id' => nil,
29
+ 'sprint' => 1,
30
+ 'total_days' => 10,
31
+ 'weekend_lines' => [ 3.5, 8.5 ]
32
32
  },
33
- "days" => []
33
+ 'days' => []
34
34
  }
35
35
  end
36
36
 
37
37
  def sprint
38
- @data["meta"]["sprint"]
38
+ @data['meta']['sprint']
39
39
  end
40
40
 
41
- def sprint= s
42
- @data["meta"]["sprint"] = s
41
+ def sprint=(s)
42
+ @data['meta']['sprint'] = s
43
43
  end
44
44
 
45
45
  def board_id
46
- @data["meta"]["board_id"]
46
+ @data['meta']['board_id']
47
47
  end
48
48
 
49
- def board_id= id
50
- @data["meta"]["board_id"] = id
49
+ def board_id=(id)
50
+ @data['meta']['board_id'] = id
51
51
  end
52
52
 
53
53
  def days
54
- @data["days"]
54
+ @data['days']
55
55
  end
56
56
 
57
57
  def merge_meta_data_from_board(burndown_data)
58
58
  if burndown_data.meta
59
59
  m = burndown_data.meta
60
- if m["sprint"] == @data["meta"]["sprint"].to_i
61
- @data["meta"] = @data["meta"].merge(m)
60
+ if m['sprint'] == @data['meta']['sprint'].to_i
61
+ @data['meta'] = @data['meta'].merge(m)
62
62
  end
63
63
  end
64
64
  end
65
65
 
66
66
  def replace_entry(date, new_entry)
67
67
  days.each_with_index do |entry, idx|
68
- days[idx] = new_entry if entry["date"] == date.to_s
68
+ days[idx] = new_entry if entry['date'] == date.to_s
69
69
  end
70
70
  end
71
71
 
72
72
  def entry_exists?(date)
73
- days.any? { |entry| entry["date"] == date.to_s }
73
+ days.any? { |entry| entry['date'] == date.to_s }
74
74
  end
75
75
 
76
76
  def add_data(burndown_data)
@@ -82,29 +82,25 @@ class BurndownChart
82
82
  end
83
83
  end
84
84
 
85
- def read_data filename
85
+ def read_data(filename)
86
86
  @data = YAML.load_file filename
87
- not_done_columns = @data["meta"]["not_done_columns"]
88
- if not_done_columns
89
- @settings.not_done_columns = not_done_columns
90
- end
87
+ not_done_columns = @data['meta']['not_done_columns']
88
+ @settings.not_done_columns = not_done_columns if not_done_columns
91
89
  end
92
90
 
93
- def write_data filename
94
- @data["days"].each do |day|
95
- [ "story_points_extra", "tasks_extra" ].each do |key|
96
- if day[key] && day[key]["done"] == 0
97
- day.delete key
98
- end
91
+ def write_data(filename)
92
+ @data['days'].each do |day|
93
+ %w[story_points_extra tasks_extra].each do |key|
94
+ day.delete key if day[key] && day[key]['done'] == 0
99
95
  end
100
96
  end
101
97
 
102
98
  begin
103
- File.open( filename, "w" ) do |file|
99
+ File.open( filename, 'w' ) do |file|
104
100
  file.write @data.to_yaml
105
101
  end
106
102
  rescue Errno::ENOENT
107
- raise TrolloloError.new( "'#{filename}' not found" )
103
+ raise TrolloloError, "'#{filename}' not found"
108
104
  end
109
105
  end
110
106
 
@@ -117,7 +113,7 @@ class BurndownChart
117
113
 
118
114
  begin
119
115
  uri = URI.parse(url)
120
- push = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/json' })
116
+ push = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
121
117
  push.body = burndown_data.to_hash.to_json
122
118
 
123
119
  Net::HTTP.start(uri.hostname, uri.port) do |http|
@@ -127,13 +123,13 @@ class BurndownChart
127
123
  # Instead of catching 20 different exceptions which can be
128
124
  # thrown by URI and Http::, StandardError is catched.
129
125
  # Fix this if there is a better solution
130
- raise TrolloloError.new("pushing to endpoint failed: #{e.message}")
126
+ raise TrolloloError, "pushing to endpoint failed: #{e.message}"
131
127
  end
132
128
  end
133
129
 
134
130
 
135
131
  def burndown_data_filename
136
- "burndown-data-#{sprint.to_s.rjust(2, "0")}.yaml"
132
+ "burndown-data-#{sprint.to_s.rjust(2, '0')}.yaml"
137
133
  end
138
134
 
139
135
  def setup(burndown_dir, board_id)
@@ -172,10 +168,8 @@ class BurndownChart
172
168
  last_sprint = sprint
173
169
  Dir.glob("#{burndown_dir}/burndown-data-*.yaml").each do |file|
174
170
  file =~ /burndown-data-(.*).yaml/
175
- current_sprint = $1.to_i
176
- if current_sprint > last_sprint
177
- last_sprint = current_sprint
178
- end
171
+ current_sprint = Regexp.last_match(1).to_i
172
+ last_sprint = current_sprint if current_sprint > last_sprint
179
173
  end
180
174
  last_sprint
181
175
  end
@@ -187,14 +181,15 @@ class BurndownChart
187
181
  begin
188
182
  read_data burndown_data_path
189
183
  rescue Errno::ENOENT
190
- raise TrolloloError.new( "'#{burndown_data_path}' not found" )
184
+ raise TrolloloError, "'#{burndown_data_path}' not found"
191
185
  end
192
- return burndown_data_path
186
+ burndown_data_path
193
187
  end
194
188
 
195
189
  def update(options)
196
190
  burndown_data_path = load_sprint(options['output'] || Dir.pwd, options[:sprint_number])
197
191
 
192
+ @data['meta']['board_id'] = options['board-id'] if options.key?('board-id')
198
193
  burndown_data = BurndownData.new(@settings)
199
194
  burndown_data.board_id = board_id
200
195
  burndown_data.fetch
@@ -204,12 +199,10 @@ class BurndownChart
204
199
  write_data(burndown_data_path)
205
200
 
206
201
  if options[:plot] || options[:plot_to_board]
207
- BurndownChart.plot(self.sprint, options)
202
+ BurndownChart.plot(sprint, options)
208
203
  end
209
204
 
210
- if options.has_key?('push-to-api')
211
- push_to_api(options['push-to-api'], data)
212
- end
205
+ push_to_api(options['push-to-api'], data) if options.key?('push-to-api')
213
206
 
214
207
  if options[:plot_to_board]
215
208
  trello = TrelloWrapper.new(@settings)
@@ -217,17 +210,21 @@ class BurndownChart
217
210
  name = options['output'] ? options['output'] : '.'
218
211
  name += "/burndown-#{sprint.to_s.rjust(2, '0')}.png"
219
212
  card_id = board.burndown_card_id
220
- trello.add_attachment(card_id, name)
221
- trello.make_cover(card_id, "burndown-#{sprint.to_s.rjust(2, '0')}.png")
213
+
214
+ response = trello.add_attachment(card_id, name)
215
+
216
+ if /{\"id\":\"(?<attachment_id>\w+)\"/ =~ response
217
+ trello.make_cover_with_id(card_id, attachment_id)
218
+ end
222
219
  end
223
220
  end
224
221
 
225
222
  def create_next_sprint(burndown_dir, options = {})
226
223
  load_sprint(burndown_dir)
227
- self.sprint = options[:sprint_number] || (self.sprint + 1)
228
- @data["meta"]["total_days"] = options[:total_days] if options[:total_days]
229
- @data["meta"]["weekend_lines"] = options[:weekend_lines] unless options[:weekend_lines].blank?
230
- @data["days"] = []
224
+ self.sprint = options[:sprint_number] || (sprint + 1)
225
+ @data['meta']['total_days'] = options[:total_days] if options[:total_days]
226
+ @data['meta']['weekend_lines'] = options[:weekend_lines] unless options[:weekend_lines].blank?
227
+ @data['days'] = []
231
228
  write_data File.join(burndown_dir, burndown_data_filename)
232
229
  end
233
230
 
@@ -52,37 +52,37 @@ class BurndownData
52
52
 
53
53
  def to_hash
54
54
  base = {
55
- "date" => date_time.to_date.to_s,
56
- "updated_at" => date_time.to_s,
57
- "story_points" => {
58
- "total" => story_points.total,
59
- "open" => story_points.open
55
+ 'date' => date_time.to_date.to_s,
56
+ 'updated_at' => date_time.to_s,
57
+ 'story_points' => {
58
+ 'total' => story_points.total,
59
+ 'open' => story_points.open
60
60
  },
61
- "tasks" => {
62
- "total" => tasks.total,
63
- "open" => tasks.open
61
+ 'tasks' => {
62
+ 'total' => tasks.total,
63
+ 'open' => tasks.open
64
64
  },
65
- "story_points_extra" => {
66
- "done" => extra_story_points.done
65
+ 'story_points_extra' => {
66
+ 'done' => extra_story_points.done
67
67
  },
68
- "tasks_extra" => {
69
- "done" => extra_tasks.done
68
+ 'tasks_extra' => {
69
+ 'done' => extra_tasks.done
70
70
  }
71
71
  }
72
72
  if fast_lane_cards.total > 0
73
- base["fast_lane"] = {
74
- "total" => fast_lane_cards.total,
75
- "open" => fast_lane_cards.open
73
+ base['fast_lane'] = {
74
+ 'total' => fast_lane_cards.total,
75
+ 'open' => fast_lane_cards.open
76
76
  }
77
77
  end
78
78
  if unplanned_story_points.total > 0
79
- base["unplanned_story_points"] = {
80
- "total" => unplanned_story_points.total,
81
- "open" => unplanned_story_points.open
79
+ base['unplanned_story_points'] = {
80
+ 'total' => unplanned_story_points.total,
81
+ 'open' => unplanned_story_points.open
82
82
  }
83
- base["unplanned_tasks"] = {
84
- "total" => unplanned_tasks.total,
85
- "open" => unplanned_tasks.open
83
+ base['unplanned_tasks'] = {
84
+ 'total' => unplanned_tasks.total,
85
+ 'open' => unplanned_tasks.open
86
86
  }
87
87
  end
88
88
  base