trollolo 0.0.8 → 0.0.9
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/.gitignore +0 -1
- data/.travis.yml +2 -2
- data/CHANGELOG.md +15 -0
- data/Gemfile +2 -1
- data/README.md +7 -3
- data/lib/card.rb +22 -0
- data/lib/cli.rb +47 -3
- data/lib/prioritizer.rb +34 -0
- data/lib/sprint_cleanup.rb +54 -0
- data/lib/trello_wrapper.rb +8 -0
- data/lib/trollolo.rb +2 -1
- data/lib/version.rb +1 -1
- data/scripts/burndowndata.py +7 -21
- data/spec/data/create_burndown_helper/burndown-01.png +0 -0
- data/spec/data/create_burndown_helper/burndown-23.png +0 -0
- data/spec/data/create_burndown_helper/burndown-31.png +0 -0
- data/spec/data/create_burndown_helper/burndown-42.png +0 -0
- data/spec/data/create_burndown_helper/burndown-56.png +0 -0
- data/spec/data/create_burndown_helper/burndown-data-01.yaml +23 -0
- data/spec/data/vcr/sprint_cleanup.yml +11021 -0
- data/spec/integration/create_burndown_spec.rb +4 -0
- data/spec/unit/card_spec.rb +40 -0
- data/spec/unit/prioritizer_spec.rb +47 -0
- data/spec/unit/sprint_cleanup_spec.rb +18 -0
- data/spec/unit/support/vcr.rb +52 -0
- data/spec/unit/trello_wrapper_spec.rb +1 -2
- metadata +12 -3
- data/lib/result.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1b0bc626175dfcdffe5dffa8942ec7469797ec4
|
4
|
+
data.tar.gz: a8feeb3163223a858044f9d2c9be8bb66e350699
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf9922a6a5d5ec348be19bde09f391115f6022e1fb0e2a2fc5b5f0c83cae7ee1b3de95037d3964b0fcbb54db1fb16c106a0f85df118f0928aed9f8f1424b7286
|
7
|
+
data.tar.gz: f5345fe994f6db7343aece171c5e57782264f493c454e742aba148f6e58c8d111eb714986ec3f2b2aa99d8bfd3c968b4644f2b16adb67a060b1ece658effa1f7
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# Trollolo Changelog
|
2
2
|
|
3
|
+
## Version 0.0.9
|
4
|
+
|
5
|
+
* Add `sprint-cleanup` command to move cards back from the sprint board to the
|
6
|
+
planning board. It takes all cards from the "Sprint backlog" and "Doing"
|
7
|
+
columns on the sprint board, moves them to the "Ready" column on the planning
|
8
|
+
board and removes all members and the "under the waterline" label.
|
9
|
+
* Add `set-priorities` command to add priorities to the title of all cards of a
|
10
|
+
given column. The priorities are added as a prefix of the form "Pnn", where
|
11
|
+
"nn" is the number of the card in the column. This is useful, if you use the
|
12
|
+
order of cards as priorities and want to move them around to different columns
|
13
|
+
without losing this information.
|
14
|
+
* Consistently use hyphens in command names, get rid of underscores.
|
15
|
+
* Fix calculation of unplanned tasks on day one
|
16
|
+
* Fix scaling tasks
|
17
|
+
|
3
18
|
## Version 0.0.8
|
4
19
|
|
5
20
|
* Burndown chart reflects unplanned work
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -40,16 +40,20 @@ member_token: 87345897238957a29835789b2374580927f3589072398579820345
|
|
40
40
|
```
|
41
41
|
|
42
42
|
These values have to be set with the personal access data for the Trello API
|
43
|
-
and the
|
43
|
+
and the personal access token of the application, which has to be generated.
|
44
44
|
|
45
45
|
For creating a developer key go to the
|
46
46
|
[Developer API Keys](https://trello.com/1/appKey/generate) page on Trello. It's
|
47
47
|
the key in the first box.
|
48
48
|
|
49
|
-
For creating a member token use the following URL, replacing
|
49
|
+
For creating a member token use the following URL, replacing `...` by
|
50
50
|
the key you obtained in the first step.
|
51
51
|
|
52
|
-
https://trello.com/1/authorize?key
|
52
|
+
https://trello.com/1/authorize?key=...&name=trollolo&expiration=never&response_type=token
|
53
|
+
|
54
|
+
To create a member token for the `set-priority` command use this URL instead:
|
55
|
+
|
56
|
+
https://trello.com/1/connect?key=...&name=trollolo&response_type=token&scope=read,write
|
53
57
|
|
54
58
|
The board id is the cryptic string in the URL of your board.
|
55
59
|
|
data/lib/card.rb
CHANGED
@@ -18,6 +18,7 @@
|
|
18
18
|
class Card
|
19
19
|
# Assuming we have card titles as follows '(8) This is the card name'
|
20
20
|
ESTIMATED_REGEX = /\(([\d.]+)\)/
|
21
|
+
PRIORITY_REGEX = /^(?:\([\d.]+\) )?P(\d+): /
|
21
22
|
SPRINT_NUMBER_REGEX = /\ASprint (\d+)/
|
22
23
|
|
23
24
|
def initialize(board_data, card_id)
|
@@ -42,6 +43,19 @@ class Card
|
|
42
43
|
name.match(ESTIMATED_REGEX).captures.first.to_f
|
43
44
|
end
|
44
45
|
|
46
|
+
def priority
|
47
|
+
return unless m = name.match(PRIORITY_REGEX)
|
48
|
+
m.captures.first.to_i
|
49
|
+
end
|
50
|
+
|
51
|
+
def priority=(n)
|
52
|
+
if priority
|
53
|
+
@card_data["name"].sub!(/P\d+: /, "P#{n}: ")
|
54
|
+
else
|
55
|
+
@card_data["name"] = "P#{n}: #{name}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
45
59
|
def done_tasks
|
46
60
|
count = 0
|
47
61
|
@card_data["checklists"].each do |checklist|
|
@@ -123,4 +137,12 @@ class Card
|
|
123
137
|
def name
|
124
138
|
@card_data["name"]
|
125
139
|
end
|
140
|
+
|
141
|
+
def name=(str)
|
142
|
+
@card_data["name"] = str
|
143
|
+
end
|
144
|
+
|
145
|
+
def id
|
146
|
+
@card_data["id"]
|
147
|
+
end
|
126
148
|
end
|
data/lib/cli.rb
CHANGED
@@ -206,7 +206,7 @@ EOT
|
|
206
206
|
b.backup(options["board-id"])
|
207
207
|
end
|
208
208
|
|
209
|
-
desc "
|
209
|
+
desc "list-backups", "List all backups"
|
210
210
|
def list_backups
|
211
211
|
b = Backup.new @@settings
|
212
212
|
b.list.each do |backup|
|
@@ -214,7 +214,7 @@ EOT
|
|
214
214
|
end
|
215
215
|
end
|
216
216
|
|
217
|
-
desc "
|
217
|
+
desc "show-backup", "Show backup of board"
|
218
218
|
option "board-id", :desc => "Id of Trello board", :required => true
|
219
219
|
option "show-descriptions", :desc => "Show descriptions of cards", :required => false, :type => :boolean
|
220
220
|
def show_backup
|
@@ -257,7 +257,7 @@ EOT
|
|
257
257
|
trello.set_description(options["card-id"], STDIN.read)
|
258
258
|
end
|
259
259
|
|
260
|
-
desc "
|
260
|
+
desc "organization-members", "Show organization members"
|
261
261
|
option "org-name", :desc => "Name of organization", :required => true
|
262
262
|
def organization_members
|
263
263
|
process_global_options options
|
@@ -293,6 +293,50 @@ EOT
|
|
293
293
|
trello.make_cover(options["card-id"], filename)
|
294
294
|
end
|
295
295
|
|
296
|
+
desc "set-priorities", "Set priority text into card titles"
|
297
|
+
long_desc <<EOT
|
298
|
+
Add 'P<n>: ' to the beginning of every cards title, replace where
|
299
|
+
already present. n is the current position of the list on the card.
|
300
|
+
EOT
|
301
|
+
option "board-id", :desc => "Id of the board", :required => true
|
302
|
+
option "list-name", :desc => "Name of the list", :required => true
|
303
|
+
def set_priorities
|
304
|
+
process_global_options options
|
305
|
+
require_trello_credentials
|
306
|
+
|
307
|
+
p = Prioritizer.new(@@settings)
|
308
|
+
p.prioritize(options["board-id"], options["list-name"])
|
309
|
+
end
|
310
|
+
|
311
|
+
desc "list-member-boards", "List name and id of all boards"
|
312
|
+
option "member-id", :desc => "Id of the member", :required => true
|
313
|
+
def list_member_boards
|
314
|
+
process_global_options options
|
315
|
+
require_trello_credentials
|
316
|
+
|
317
|
+
trello = TrelloWrapper.new(@@settings)
|
318
|
+
trello.get_member_boards(options["member-id"]).sort_by { |board|
|
319
|
+
board["name"]
|
320
|
+
}.each { |board|
|
321
|
+
puts "#{board["name"]} - #{board["id"]}"
|
322
|
+
}
|
323
|
+
end
|
324
|
+
|
325
|
+
desc "sprint-cleanup", "Move remaining cards to backlog"
|
326
|
+
long_desc <<EOT
|
327
|
+
After the sprint, move remaining cards from 'Sprint Backlog' and 'Doing'
|
328
|
+
back to the planning board into the 'Ready' list.
|
329
|
+
EOT
|
330
|
+
option "board-id", :desc => "Id of the board", :required => true
|
331
|
+
option "target-board-id", :desc => "Id of the target board", :required => true
|
332
|
+
def sprint_cleanup
|
333
|
+
process_global_options options
|
334
|
+
require_trello_credentials
|
335
|
+
|
336
|
+
s = SprintCleanup.new(@@settings)
|
337
|
+
s.cleanup(options["board-id"], options["target-board-id"])
|
338
|
+
end
|
339
|
+
|
296
340
|
private
|
297
341
|
|
298
342
|
def process_global_options options
|
data/lib/prioritizer.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
class Prioritizer
|
2
|
+
|
3
|
+
def initialize(settings)
|
4
|
+
@settings = settings
|
5
|
+
end
|
6
|
+
|
7
|
+
def prioritize(board_id, list_name)
|
8
|
+
list = find_list(board_id, list_name)
|
9
|
+
fail "list not found on board" unless list
|
10
|
+
update_priorities(list)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def trello
|
16
|
+
@trello ||= TrelloWrapper.new(@settings)
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_priorities(list)
|
20
|
+
n = 1
|
21
|
+
list.cards.each do |card|
|
22
|
+
next if card.name =~ /waterline/i
|
23
|
+
card.priority = n
|
24
|
+
trello.set_name(card.id, card.name)
|
25
|
+
puts %(set priority to #{n} for "#{card.name}")
|
26
|
+
n += 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_list(board_id, list_name)
|
31
|
+
board = trello.board(board_id)
|
32
|
+
board.columns.find { |list| list.name == list_name }
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class SprintCleanup
|
2
|
+
SOURCE_LISTS = ["Sprint Backlog", "Doing"]
|
3
|
+
TARGET_LIST = "Ready"
|
4
|
+
|
5
|
+
def initialize(settings)
|
6
|
+
@settings = settings
|
7
|
+
init_trello
|
8
|
+
end
|
9
|
+
|
10
|
+
def cleanup(board_id, target_board_id)
|
11
|
+
@board = Trello::Board.find(board_id)
|
12
|
+
@target_board = Trello::Board.find(target_board_id)
|
13
|
+
|
14
|
+
SOURCE_LISTS.each do |list_name|
|
15
|
+
move_cards(@board.lists.find { |l| l.name == list_name })
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def init_trello
|
22
|
+
Trello.configure do |config|
|
23
|
+
config.developer_public_key = @settings.developer_public_key
|
24
|
+
config.member_token = @settings.member_token
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def target_list
|
29
|
+
@target_list ||= @target_board.lists.find { |l| l.name == TARGET_LIST }
|
30
|
+
end
|
31
|
+
|
32
|
+
def sticky?(card)
|
33
|
+
card.labels.any? { |l| l.name == "Sticky" }
|
34
|
+
end
|
35
|
+
|
36
|
+
def waterline_label(card)
|
37
|
+
card.labels.find { |label| label.name =~ /waterline/i }
|
38
|
+
end
|
39
|
+
|
40
|
+
def remove_waterline_label(card)
|
41
|
+
label = waterline_label(card)
|
42
|
+
card.remove_label(label) if label
|
43
|
+
end
|
44
|
+
|
45
|
+
def move_cards(source_list)
|
46
|
+
source_list.cards.each do |card|
|
47
|
+
next if sticky?(card)
|
48
|
+
puts %(moving card "#{card.name}" to list "#{target_list.name}")
|
49
|
+
card.members.each { |member| card.remove_member(member) }
|
50
|
+
remove_waterline_label(card)
|
51
|
+
card.move_to_board(@target_board, target_list)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/trello_wrapper.rb
CHANGED
@@ -76,10 +76,18 @@ class TrelloWrapper
|
|
76
76
|
card.desc
|
77
77
|
end
|
78
78
|
|
79
|
+
def get_member_boards(member_id)
|
80
|
+
JSON.parse(client.get("/members/#{member_id}/boards"))
|
81
|
+
end
|
82
|
+
|
79
83
|
def set_description(card_id, description)
|
80
84
|
client.put("/cards/#{card_id}/desc?value=#{description}")
|
81
85
|
end
|
82
86
|
|
87
|
+
def set_name(card_id, name)
|
88
|
+
client.put("/cards/#{card_id}/name?value=#{name}")
|
89
|
+
end
|
90
|
+
|
83
91
|
private
|
84
92
|
|
85
93
|
def init_trello
|
data/lib/trollolo.rb
CHANGED
@@ -27,12 +27,13 @@ require_relative 'settings'
|
|
27
27
|
require_relative 'column'
|
28
28
|
require_relative 'card'
|
29
29
|
require_relative 'scrum_board'
|
30
|
-
require_relative 'result'
|
31
30
|
require_relative 'trello_wrapper'
|
32
31
|
require_relative 'burndown_chart'
|
33
32
|
require_relative 'burndown_data'
|
34
33
|
require_relative 'backup'
|
35
34
|
require_relative 'checklist'
|
35
|
+
require_relative 'prioritizer'
|
36
|
+
require_relative 'sprint_cleanup'
|
36
37
|
|
37
38
|
class TrolloloError < StandardError
|
38
39
|
end
|
data/lib/version.rb
CHANGED
data/scripts/burndowndata.py
CHANGED
@@ -9,14 +9,12 @@ class BurndownData:
|
|
9
9
|
self.args = args
|
10
10
|
burndown = self.readYAML(self.args.sprint)
|
11
11
|
self.getSprintData(burndown)
|
12
|
-
self.calculateMaxStoryPoints()
|
13
|
-
self.calculateMaxTasks()
|
14
12
|
self.setBonusTasksDayOne(burndown)
|
15
13
|
self.setUnplannedTasksDayOne(burndown)
|
16
14
|
self.setExtraDays()
|
17
15
|
self.setUnplannedDays()
|
18
|
-
self.calculateYRange(self.
|
19
|
-
self.setScaleFactor(self.
|
16
|
+
self.calculateYRange(self.total_story_points[0], self.bonus_tasks_done, self.bonus_story_points_done, self.unplanned_tasks_done, self.unplanned_story_points_done)
|
17
|
+
self.setScaleFactor(self.total_tasks[0], self.total_story_points[0])
|
20
18
|
|
21
19
|
def readYAML(self, sprint_number):
|
22
20
|
with open('burndown-data-' + sprint_number + '.yaml', 'r') as f:
|
@@ -49,8 +47,6 @@ class BurndownData:
|
|
49
47
|
self.y_fast_lane = []
|
50
48
|
self.total_fast_lane = []
|
51
49
|
self.total_unplanned_fast_lane = []
|
52
|
-
self.max_story_points = 0
|
53
|
-
self.max_tasks = 0
|
54
50
|
|
55
51
|
for day in burndown["days"]:
|
56
52
|
self.days.append(self.current_day)
|
@@ -94,16 +90,6 @@ class BurndownData:
|
|
94
90
|
self.current_day += 1
|
95
91
|
return
|
96
92
|
|
97
|
-
def calculateMaxStoryPoints(self):
|
98
|
-
for sp in self.total_story_points:
|
99
|
-
self.max_story_points = max(self.max_story_points, sp)
|
100
|
-
return
|
101
|
-
|
102
|
-
def calculateMaxTasks(self):
|
103
|
-
for t in self.total_tasks:
|
104
|
-
self.max_tasks = max(self.max_tasks, t)
|
105
|
-
return
|
106
|
-
|
107
93
|
def setBonusTasksDayOne(self, burndown):
|
108
94
|
if burndown["days"][0].has_key("tasks_extra"):
|
109
95
|
self.bonus_tasks_day_one = burndown["days"][0]["tasks_extra"]["done"]
|
@@ -113,7 +99,7 @@ class BurndownData:
|
|
113
99
|
|
114
100
|
def setUnplannedTasksDayOne(self, burndown):
|
115
101
|
if burndown["days"][0].has_key("unplanned_tasks"):
|
116
|
-
self.unplanned_tasks_day_one = burndown["days"][0]["unplanned_tasks"]["
|
102
|
+
self.unplanned_tasks_day_one = burndown["days"][0]["unplanned_tasks"]["total"] - burndown["days"][0]["unplanned_tasks"]["open"]
|
117
103
|
else:
|
118
104
|
self.unplanned_tasks_day_one = 0
|
119
105
|
return
|
@@ -140,8 +126,8 @@ class BurndownData:
|
|
140
126
|
self.unplanned_day = 1
|
141
127
|
return
|
142
128
|
|
143
|
-
def calculateYRange(self,
|
144
|
-
self.ymax =
|
129
|
+
def calculateYRange(self, total_story_points, bonus_tasks_done, bonus_story_points_done, unplanned_tasks_done, unplanned_story_points_done):
|
130
|
+
self.ymax = total_story_points + 3
|
145
131
|
|
146
132
|
if len(bonus_tasks_done) > 0:
|
147
133
|
ymin_bonus_tasks = min(bonus_tasks_done) -3
|
@@ -168,7 +154,7 @@ class BurndownData:
|
|
168
154
|
self.ymin = -3
|
169
155
|
return
|
170
156
|
|
171
|
-
def setScaleFactor(self,
|
172
|
-
self.scalefactor = float(
|
157
|
+
def setScaleFactor(self, total_tasks, total_story_points):
|
158
|
+
self.scalefactor = float(total_tasks) / float(total_story_points)
|
173
159
|
return
|
174
160
|
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,23 @@
|
|
1
|
+
---
|
2
|
+
meta:
|
3
|
+
board_id: CRdddpdy
|
4
|
+
sprint: 1
|
5
|
+
total_days: 10
|
6
|
+
weekend_lines:
|
7
|
+
- 3.5
|
8
|
+
- 8.5
|
9
|
+
days:
|
10
|
+
- date: '2016-03-17'
|
11
|
+
updated_at: '2016-03-17T18:31:46+01:00'
|
12
|
+
story_points:
|
13
|
+
total: 16.0
|
14
|
+
open: 13.0
|
15
|
+
tasks:
|
16
|
+
total: 13
|
17
|
+
open: 9
|
18
|
+
unplanned_story_points:
|
19
|
+
total: 3.0
|
20
|
+
open: 1.0
|
21
|
+
unplanned_tasks:
|
22
|
+
total: 2
|
23
|
+
open: 1
|