trollolo 0.1.1 → 0.2.0
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 +4 -4
- data/.rubocop.yml +21 -2
- data/.rubocop_todo.yml +105 -270
- data/CHANGELOG.md +10 -0
- data/CONTRIBUTING.md +6 -4
- data/Gemfile +1 -1
- data/README.md +10 -1
- data/Rakefile +4 -4
- data/bin/trollolo +2 -2
- data/lib/backup.rb +21 -23
- data/lib/burndown_chart.rb +46 -49
- data/lib/burndown_data.rb +21 -21
- data/lib/card.rb +23 -32
- data/lib/checklist.rb +13 -1
- data/lib/cli.rb +105 -110
- data/lib/column.rb +11 -10
- data/lib/empty_column.rb +2 -2
- data/lib/scrum/backlog_mover.rb +3 -3
- data/lib/scrum/card_type_detection.rb +1 -1
- data/lib/scrum/creator.rb +7 -7
- data/lib/scrum/prioritizer.rb +1 -1
- data/lib/scrum/sprint_board.rb +4 -4
- data/lib/scrum/sprint_cleaner.rb +3 -3
- data/lib/scrum/sprint_planning_board.rb +2 -4
- data/lib/scrum_board.rb +3 -3
- data/lib/settings.rb +29 -27
- data/lib/trello_service.rb +1 -1
- data/lib/trello_wrapper.rb +9 -9
- data/lib/version.rb +1 -1
- data/spec/data/card.json +19 -0
- data/spec/data/trollolorc +3 -0
- data/spec/integration/command_line_spec.rb +14 -14
- data/spec/integration/create_burndown_spec.rb +25 -25
- data/spec/integration/integration_spec_helper.rb +1 -1
- data/spec/integration/wrapper/credentials_input_wrapper +7 -11
- data/spec/integration/wrapper/empty_config_trollolo_wrapper +2 -2
- data/spec/integration/wrapper/trollolo_wrapper +2 -2
- data/spec/unit/backup_spec.rb +14 -14
- data/spec/unit/burndown_chart_spec.rb +176 -184
- data/spec/unit/burndown_data_spec.rb +21 -23
- data/spec/unit/card_spec.rb +38 -24
- data/spec/unit/cli_spec.rb +57 -57
- data/spec/unit/empty_column_spec.rb +1 -1
- data/spec/unit/retrieve_data_spec.rb +13 -13
- data/spec/unit/scrum/backlog_mover_spec.rb +8 -8
- data/spec/unit/scrum/card_type_detection_spec.rb +12 -12
- data/spec/unit/scrum/creator_spec.rb +8 -8
- data/spec/unit/scrum/prioritizer_spec.rb +19 -19
- data/spec/unit/scrum/priority_name_spec.rb +16 -16
- data/spec/unit/scrum/sprint_board_spec.rb +3 -3
- data/spec/unit/scrum/sprint_cleaner_spec.rb +15 -15
- data/spec/unit/scrum/sprint_planning_board_spec.rb +2 -2
- data/spec/unit/scrum_board_spec.rb +56 -56
- data/spec/unit/settings_spec.rb +23 -23
- data/spec/unit/spec_helper.rb +3 -3
- data/spec/unit/support/test_data_operations.rb +4 -0
- data/spec/unit/support/update_webmock_data +9 -11
- data/spec/unit/support/vcr.rb +3 -3
- data/spec/unit/support/webmocks.rb +21 -12
- data/spec/unit/trello_wrapper_spec.rb +40 -29
- data/trollolo.gemspec +3 -3
- metadata +8 -5
data/CHANGELOG.md
CHANGED
@@ -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
|
data/CONTRIBUTING.md
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
+
# CONTRIBUTING GUIDE
|
2
|
+
|
1
3
|
Contributions are welcome! :smile:
|
2
4
|
|
3
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
33
|
+
### To run rubocop
|
32
34
|
|
33
35
|
To run Rubocop displaying cop names in offense messages:
|
34
36
|
|
data/Gemfile
CHANGED
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
|
-
|
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
|
4
|
-
system
|
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 :
|
10
|
-
system
|
9
|
+
task build: ['man_pages:build'] do
|
10
|
+
system 'gem build trollolo.gemspec'
|
11
11
|
end
|
12
12
|
end
|
data/bin/trollolo
CHANGED
@@ -18,12 +18,12 @@
|
|
18
18
|
|
19
19
|
require_relative '../lib/trollolo'
|
20
20
|
|
21
|
-
config_path = ENV[
|
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[
|
26
|
+
ENV['THOR_DEBUG'] = '1'
|
27
27
|
begin
|
28
28
|
Cli.check_unknown_options!
|
29
29
|
Cli.start
|
data/lib/backup.rb
CHANGED
@@ -4,9 +4,9 @@ class Backup
|
|
4
4
|
|
5
5
|
attr_accessor :directory
|
6
6
|
|
7
|
-
def initialize
|
7
|
+
def initialize(settings)
|
8
8
|
@settings = settings
|
9
|
-
@directory = File.expand_path(
|
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,
|
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,
|
34
|
+
board = JSON.parse(File.read(File.join(backup_path, 'board.json')))
|
35
35
|
|
36
|
-
out.puts board[
|
36
|
+
out.puts board['name']
|
37
37
|
|
38
38
|
lists = {}
|
39
|
-
board[
|
40
|
-
lists[list[
|
39
|
+
board['lists'].each do |list|
|
40
|
+
lists[list['id']] = []
|
41
41
|
end
|
42
|
-
board[
|
43
|
-
if lists[card[
|
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[
|
46
|
+
board['lists'].each do |list|
|
49
47
|
out.puts " #{list['name']}"
|
50
|
-
lists[list[
|
51
|
-
out.puts
|
52
|
-
if options[
|
53
|
-
|
54
|
-
out.puts
|
55
|
-
out.puts
|
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[
|
59
|
-
out.puts
|
60
|
-
checklist[
|
61
|
-
out.puts
|
62
|
-
checklist_item[
|
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
|
data/lib/burndown_chart.rb
CHANGED
@@ -24,53 +24,53 @@ class BurndownChart
|
|
24
24
|
@settings = settings
|
25
25
|
|
26
26
|
@data = {
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
'meta' => {
|
28
|
+
'board_id' => nil,
|
29
|
+
'sprint' => 1,
|
30
|
+
'total_days' => 10,
|
31
|
+
'weekend_lines' => [ 3.5, 8.5 ]
|
32
32
|
},
|
33
|
-
|
33
|
+
'days' => []
|
34
34
|
}
|
35
35
|
end
|
36
36
|
|
37
37
|
def sprint
|
38
|
-
@data[
|
38
|
+
@data['meta']['sprint']
|
39
39
|
end
|
40
40
|
|
41
|
-
def sprint=
|
42
|
-
@data[
|
41
|
+
def sprint=(s)
|
42
|
+
@data['meta']['sprint'] = s
|
43
43
|
end
|
44
44
|
|
45
45
|
def board_id
|
46
|
-
@data[
|
46
|
+
@data['meta']['board_id']
|
47
47
|
end
|
48
48
|
|
49
|
-
def board_id=
|
50
|
-
@data[
|
49
|
+
def board_id=(id)
|
50
|
+
@data['meta']['board_id'] = id
|
51
51
|
end
|
52
52
|
|
53
53
|
def days
|
54
|
-
@data[
|
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[
|
61
|
-
@data[
|
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[
|
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[
|
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
|
85
|
+
def read_data(filename)
|
86
86
|
@data = YAML.load_file filename
|
87
|
-
not_done_columns = @data[
|
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
|
94
|
-
@data[
|
95
|
-
[
|
96
|
-
if day[key] && day[key][
|
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,
|
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
|
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,
|
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
|
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,
|
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 =
|
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
|
184
|
+
raise TrolloloError, "'#{burndown_data_path}' not found"
|
191
185
|
end
|
192
|
-
|
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(
|
202
|
+
BurndownChart.plot(sprint, options)
|
208
203
|
end
|
209
204
|
|
210
|
-
if options.
|
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
|
-
|
221
|
-
trello.
|
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] || (
|
228
|
-
@data[
|
229
|
-
@data[
|
230
|
-
@data[
|
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
|
|
data/lib/burndown_data.rb
CHANGED
@@ -52,37 +52,37 @@ class BurndownData
|
|
52
52
|
|
53
53
|
def to_hash
|
54
54
|
base = {
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
61
|
+
'tasks' => {
|
62
|
+
'total' => tasks.total,
|
63
|
+
'open' => tasks.open
|
64
64
|
},
|
65
|
-
|
66
|
-
|
65
|
+
'story_points_extra' => {
|
66
|
+
'done' => extra_story_points.done
|
67
67
|
},
|
68
|
-
|
69
|
-
|
68
|
+
'tasks_extra' => {
|
69
|
+
'done' => extra_tasks.done
|
70
70
|
}
|
71
71
|
}
|
72
72
|
if fast_lane_cards.total > 0
|
73
|
-
base[
|
74
|
-
|
75
|
-
|
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[
|
80
|
-
|
81
|
-
|
79
|
+
base['unplanned_story_points'] = {
|
80
|
+
'total' => unplanned_story_points.total,
|
81
|
+
'open' => unplanned_story_points.open
|
82
82
|
}
|
83
|
-
base[
|
84
|
-
|
85
|
-
|
83
|
+
base['unplanned_tasks'] = {
|
84
|
+
'total' => unplanned_tasks.total,
|
85
|
+
'open' => unplanned_tasks.open
|
86
86
|
}
|
87
87
|
end
|
88
88
|
base
|