trollolo 0.0.8 → 0.0.9

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
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