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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e965a5dd1f21efe913566b48fdd34366c96a281d
4
- data.tar.gz: 3ceddeef3b6156896c240274603422f456b4a774
3
+ metadata.gz: a1b0bc626175dfcdffe5dffa8942ec7469797ec4
4
+ data.tar.gz: a8feeb3163223a858044f9d2c9be8bb66e350699
5
5
  SHA512:
6
- metadata.gz: a4a388af4ab07d713d63fcdc1b1ef70601de633e6755a462988893f9cd42cfbf4401fe02a24af1869ef1124f1e09291a711a0f9abb311e0c01e33933b9b07beb
7
- data.tar.gz: dc7bd9ccb6dcca2f136163929e68b6842e5cfd1f88da59f5450547a8af4d5a238826b9a1050b880942956b3cc67da2b1789ad568340082b09c3be3932d56b2fd
6
+ metadata.gz: cf9922a6a5d5ec348be19bde09f391115f6022e1fb0e2a2fc5b5f0c83cae7ee1b3de95037d3964b0fcbb54db1fb16c106a0f85df118f0928aed9f8f1424b7286
7
+ data.tar.gz: f5345fe994f6db7343aece171c5e57782264f493c454e742aba148f6e58c8d111eb714986ec3f2b2aa99d8bfd3c968b4644f2b16adb67a060b1ece658effa1f7
data/.gitignore CHANGED
@@ -2,4 +2,3 @@
2
2
  Gemfile.lock
3
3
  tmp
4
4
  coverage
5
- *.png
data/.travis.yml CHANGED
@@ -1,11 +1,11 @@
1
1
  language: ruby
2
2
  cache: bundler
3
3
  before_install:
4
- - gem update --system
5
4
  - gem --version
5
+ - gem list bundler
6
6
  rvm:
7
- - "2.1.7"
8
7
  - "2.2.3"
8
+ - "2.3.1"
9
9
  script: bundle exec rspec spec/unit
10
10
  sudo: false
11
11
 
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
@@ -4,9 +4,10 @@ gemspec
4
4
 
5
5
  group :test do
6
6
  gem 'codeclimate-test-reporter'
7
- gem 'rspec', '~> 3'
7
+ gem 'rspec', '~> 3.5'
8
8
  gem 'cli_tester'
9
9
  gem 'webmock'
10
+ gem 'vcr'
10
11
  gem 'given_filesystem'
11
12
  gem 'byebug'
12
13
  gem 'awesome_print'
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 id of the board, which is processed.
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 `applicationkey` by
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=applicationkey&name=trollolo&expiration=never&response_type=token
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 "list_backups", "List all backups"
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 "show_backup", "Show backup of board"
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 "organization_members", "Show organization members"
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
@@ -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
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Trollolo
2
2
 
3
- VERSION = "0.0.8"
3
+ VERSION = "0.0.9"
4
4
 
5
5
  end
@@ -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.max_story_points, self.bonus_tasks_done, self.bonus_story_points_done, self.unplanned_tasks_done, self.unplanned_story_points_done)
19
- self.setScaleFactor(self.max_tasks, self.max_story_points)
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"]["done"]
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, max_story_points, bonus_tasks_done, bonus_story_points_done, unplanned_tasks_done, unplanned_story_points_done):
144
- self.ymax = max_story_points + 3
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, max_tasks, max_story_points):
172
- self.scalefactor = float(max_tasks) / float(max_story_points)
157
+ def setScaleFactor(self, total_tasks, total_story_points):
158
+ self.scalefactor = float(total_tasks) / float(total_story_points)
173
159
  return
174
160
 
@@ -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