trollolo 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'codeclimate-test-reporter'
7
+ gem 'rspec', "~>3"
8
+ gem 'aruba'
9
+ gem 'webmock'
10
+ gem 'given_filesystem'
11
+ end
@@ -0,0 +1,92 @@
1
+ # Trollolo
2
+
3
+ [![Build Status](https://travis-ci.org/openSUSE/trollolo.svg?branch=master)](https://travis-ci.org/openSUSE/trollolo)
4
+ [![Code Climate](https://codeclimate.com/github/openSUSE/trollolo/badges/gpa.svg)](https://codeclimate.com/github/openSUSE/trollolo)
5
+ [![Test Coverage](https://codeclimate.com/github/openSUSE/trollolo/badges/coverage.svg)](https://codeclimate.com/github/openSUSE/trollolo)
6
+
7
+ Command line tool to extract data from Trello, in particular for creating
8
+ burndown charts.
9
+
10
+ ## Functionality
11
+
12
+ A detailed description of the functionality of the tool can be found in the
13
+ [man page](http://github.com/openSUSE/trollolo/blob/master/man/trollolo.1.md).
14
+
15
+ ## Expectations
16
+
17
+ For expectations how the board has to be structured to make the burndown chart
18
+ functions work see the Trollolo man page. There is an
19
+ [example Trello board](https://trello.com/b/CRdddpdy/trollolo-testing-board)
20
+ which demonstrates the expected structure.
21
+
22
+ ## Configuration
23
+
24
+ Trollolo reads a configuration file `.trollolorc` in the home directory of the
25
+ user running the command line tool. It reads the data required to authenticate
26
+ with the Trello server from it. It's two values (the example shows random data):
27
+
28
+ ```yaml
29
+ developer_public_key: 87349873487ef8732487234
30
+ member_token: 87345897238957a29835789b2374580927f3589072398579820345
31
+ ```
32
+
33
+ These values have to be set with the personal access data for the Trello API
34
+ and the id of the board, which is processed.
35
+
36
+ For creating a developer key go to the
37
+ [Developer API Keys](https://trello.com/1/appKey/generate) page on Trello. It's
38
+ the key in the first box.
39
+
40
+ For creating a member token go follow the
41
+ [instructions](https://trello.com/docs/gettingstarted/index.html#getting-a-token-from-a-user)
42
+ in the Trello API documentation.
43
+
44
+ The board id is the cryptic string in the URL of your board.
45
+
46
+ ## Creating burndown charts
47
+
48
+ Trollolo implements a simple work flow for creating burndown charts from the
49
+ data on a Trello board. It fetches the data from Trello, stores and processes
50
+ it locally, and generates charts which can then be uploaded as graphics to
51
+ Trello again.
52
+
53
+ At the moment it only needs read-only access to the Trello board from which it
54
+ reads the data. In the future it would be great, if it could also write back
55
+ the generated data and results to make it even more automatic.
56
+
57
+ The work flow goes as follows:
58
+
59
+ Create an initial working directory for the burndown chart generation:
60
+
61
+ trollolo burndown-init --board-id=MYBOARDID --output=WORKING_DIR
62
+
63
+ This will create a directory WORKING_DIR and put an initial data file there,
64
+ which contains the meta data. The file is called `burndown-data-1.yaml`. You
65
+ might want to keep this file in a git repository for safe storage and history.
66
+
67
+ After each daily go to the working directory and call:
68
+
69
+ trollolo burndown
70
+
71
+ This will get the current data from the Trello board and update the data file
72
+ with the data from the current day. If there already was some data in the file
73
+ for the same day it will be overridden.
74
+
75
+ When the sprint is over and you want to start with the next sprint, go to the
76
+ working directory and call:
77
+
78
+ trollolo burndown --new-sprint
79
+
80
+ This will create a new data file for the next sprint number and populate it
81
+ with initial data taken from the Trello board. You are ready to go for the
82
+ sprint now and can continue with calling `trollolo burndown` after each daily.
83
+
84
+ To generate the actual burndown chart, go to the working directory and call:
85
+
86
+ trollolo plot SPRINT_NUMBER
87
+
88
+ This will take the data from the file `burndown-data-SPRINT_NUMBER.yaml` and
89
+ create a nice chart from it. It will show the chart and also create a file
90
+ `burndown-SPRINT_NUMBER.png` you can upload as cover graphics to a card on your
91
+ Trello board.
92
+
@@ -0,0 +1,12 @@
1
+ namespace :man_pages do
2
+ task :build do
3
+ puts " Building man pages"
4
+ system "ronn man/*.md"
5
+ end
6
+ end
7
+
8
+ namespace :gem do
9
+ task :build => ["man_pages:build"] do
10
+ system "gem build trollolo.gemspec"
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (c) 2013-2014 SUSE LLC
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of version 3 of the GNU General Public License as
6
+ # published by the Free Software Foundation.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program; if not, contact SUSE LLC.
15
+ #
16
+ # To contact SUSE about this file by physical or electronic mail,
17
+ # you may find current contact information at www.suse.com
18
+
19
+ require_relative '../lib/trollolo'
20
+
21
+ config_path = ENV["TROLLOLO_CONFIG_PATH"] || File.expand_path("~/.trollolorc")
22
+
23
+ Cli.settings = Settings.new(config_path)
24
+
25
+ # Set debug flag, so thor throws exceptions on error
26
+ ENV["THOR_DEBUG"] = "1"
27
+ begin
28
+ Cli.check_unknown_options!
29
+ result = Cli.start ARGV
30
+ rescue Thor::Error => e
31
+ STDERR.puts e
32
+ exit 1
33
+ end
@@ -0,0 +1,158 @@
1
+ # Copyright (c) 2013-2014 SUSE LLC
2
+ #
3
+ # This program is free software; you can redistribute it and/or
4
+ # modify it under the terms of version 3 of the GNU General Public License as
5
+ # published by the Free Software Foundation.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program; if not, contact SUSE LLC.
14
+ #
15
+ # To contact SUSE about this file by physical or electronic mail,
16
+ # you may find current contact information at www.suse.com
17
+
18
+ class BurndownChart
19
+
20
+ attr_accessor :data
21
+
22
+ def initialize(settings)
23
+ @settings = settings
24
+ @burndown_data = BurndownData.new settings
25
+
26
+ @data = {
27
+ "meta" => {
28
+ "board_id" => nil,
29
+ "sprint" => 1,
30
+ "total_days" => 10,
31
+ "weekend_lines" => [ 3.5, 8.5 ]
32
+ },
33
+ "days" => []
34
+ }
35
+ end
36
+
37
+ def sprint
38
+ @data["meta"]["sprint"]
39
+ end
40
+
41
+ def sprint= s
42
+ @data["meta"]["sprint"] = s
43
+ end
44
+
45
+ def board_id
46
+ @data["meta"]["board_id"]
47
+ end
48
+
49
+ def board_id= id
50
+ @data["meta"]["board_id"] = id
51
+ end
52
+
53
+ def days
54
+ @data["days"]
55
+ end
56
+
57
+ def add_data(burndown_data, date)
58
+ new_entry = {
59
+ "date" => date.to_s,
60
+ "story_points" => {
61
+ "total" => burndown_data.story_points.total,
62
+ "open" => burndown_data.story_points.open
63
+ },
64
+ "tasks" => {
65
+ "total" => burndown_data.tasks.total,
66
+ "open" => burndown_data.tasks.open
67
+ },
68
+ "story_points_extra" => {
69
+ "done" => burndown_data.extra_story_points.done
70
+ },
71
+ "tasks_extra" => {
72
+ "done" => burndown_data.extra_tasks.done
73
+ }
74
+ }
75
+ new_days = Array.new
76
+ replaced_entry = false
77
+ @data["days"].each do |entry|
78
+ if entry["date"] == date.to_s
79
+ new_days.push(new_entry)
80
+ replaced_entry = true
81
+ else
82
+ new_days.push(entry)
83
+ end
84
+ end
85
+ if !replaced_entry
86
+ new_days.push(new_entry)
87
+ end
88
+ @data["days"] = new_days
89
+ end
90
+
91
+ def read_data filename
92
+ @data = YAML.load_file filename
93
+ end
94
+
95
+ def write_data filename
96
+ @data["days"].each do |day|
97
+ [ "story_points_extra", "tasks_extra" ].each do |key|
98
+ if day[key] && day[key]["done"] == 0
99
+ day.delete key
100
+ end
101
+ end
102
+ end
103
+
104
+ File.open( filename, "w" ) do |file|
105
+ file.write @data.to_yaml
106
+ end
107
+ end
108
+
109
+ def burndown_data_filename
110
+ "burndown-data-#{sprint.to_s.rjust(2,"0")}.yaml"
111
+ end
112
+
113
+ def setup(burndown_dir, board_id)
114
+ self.board_id = board_id
115
+ FileUtils.mkdir_p burndown_dir
116
+ write_data File.join(burndown_dir, burndown_data_filename)
117
+ end
118
+
119
+ def update(burndown_dir)
120
+ Dir.glob("#{burndown_dir}/burndown-data-*.yaml").each do |file|
121
+ file =~ /burndown-data-(.*).yaml/
122
+ current_sprint = $1.to_i
123
+ if current_sprint > sprint
124
+ self.sprint = current_sprint
125
+ end
126
+ end
127
+ burndown_data_path = File.join(burndown_dir, burndown_data_filename)
128
+ begin
129
+ read_data burndown_data_path
130
+ @burndown_data.board_id = board_id
131
+ @burndown_data.fetch
132
+ add_data(@burndown_data, Date.today)
133
+ write_data burndown_data_path
134
+ puts "Updated data for sprint #{self.sprint}"
135
+ rescue Errno::ENOENT
136
+ raise TrolloloError.new( "'#{burndown_data_path}' not found" )
137
+ end
138
+ end
139
+
140
+ def create_next_sprint(burndown_dir)
141
+ Dir.glob("#{burndown_dir}/burndown-data-*.yaml").each do |file|
142
+ file =~ /burndown-data-(.*).yaml/
143
+ current_sprint = $1.to_i
144
+ if current_sprint > sprint
145
+ self.sprint = current_sprint
146
+ end
147
+ end
148
+ burndown_data_path = File.join(burndown_dir, burndown_data_filename)
149
+ begin
150
+ read_data burndown_data_path
151
+ self.sprint = self.sprint + 1
152
+ @data["days"] = []
153
+ write_data File.join(burndown_dir, burndown_data_filename)
154
+ rescue Errno::ENOENT
155
+ raise TrolloloError.new( "'#{burndown_data_path}' not found" )
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,172 @@
1
+ # Copyright (c) 2013-2014 SUSE LLC
2
+ #
3
+ # This program is free software; you can redistribute it and/or
4
+ # modify it under the terms of version 3 of the GNU General Public License as
5
+ # published by the Free Software Foundation.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program; if not, contact SUSE LLC.
14
+ #
15
+ # To contact SUSE about this file by physical or electronic mail,
16
+ # you may find current contact information at www.suse.com
17
+
18
+ # This class represents the current state of burndown data on the Trello board.
19
+ # It encapsulates getting the data from Trello. It does not keep any history
20
+ # or interaction with the files used to generate burndown charts.
21
+ class BurndownData
22
+
23
+ attr_accessor :story_points, :tasks, :extra_story_points, :extra_tasks
24
+ attr_accessor :board_id
25
+
26
+ class Result
27
+ attr_accessor :open, :done
28
+
29
+ def initialize
30
+ @open = 0
31
+ @done = 0
32
+ end
33
+
34
+ def total
35
+ @open + @done
36
+ end
37
+ end
38
+
39
+ def initialize settings
40
+ @settings = settings
41
+
42
+ @story_points = Result.new
43
+ @tasks = Result.new
44
+
45
+ @extra_story_points = Result.new
46
+ @extra_tasks = Result.new
47
+ end
48
+
49
+ def trello
50
+ Trello.new(board_id: @board_id, developer_public_key: @settings.developer_public_key, member_token: @settings.member_token)
51
+ end
52
+
53
+ def fetch_todo_list_id
54
+ lists = trello.lists
55
+ lists.each do |l|
56
+ if l["name"] =~ /^Sprint Backlog$/
57
+ return l["id"]
58
+ end
59
+ end
60
+
61
+ raise "Unable to find sprint backlog column on sprint board"
62
+ end
63
+
64
+ def fetch_doing_list_id
65
+ lists = trello.lists
66
+ lists.each do |l|
67
+ if l["name"] =~ /^Doing$/
68
+ return l["id"]
69
+ end
70
+ end
71
+
72
+ raise "Unable to find doing column on sprint board"
73
+ end
74
+
75
+ def fetch_done_list_id
76
+ lists = trello.lists
77
+ last_sprint = nil
78
+ lists.each do |l|
79
+ if l["name"] =~ /^Done Sprint (.*)$/
80
+ sprint = $1.to_i
81
+ if !last_sprint || sprint > last_sprint[:number]
82
+ last_sprint = { :number => sprint, :id => l["id"] }
83
+ end
84
+ end
85
+ end
86
+
87
+ id = last_sprint[:id]
88
+ if !id
89
+ raise "Unable to find done column on sprint board"
90
+ end
91
+ id
92
+ end
93
+
94
+ def fetch
95
+ cards = trello.cards
96
+
97
+ todo_list_id = fetch_todo_list_id
98
+ doing_list_id = fetch_doing_list_id
99
+ done_list_id = fetch_done_list_id
100
+
101
+ if @settings.verbose
102
+ puts "Todo list: #{todo_list_id}"
103
+ puts "Doing list: #{doing_list_id}"
104
+ puts "Done list: #{done_list_id}"
105
+ end
106
+
107
+ sp_total = 0
108
+ sp_done = 0
109
+
110
+ extra_sp_total = 0
111
+ extra_sp_done = 0
112
+
113
+ tasks_total = 0
114
+ tasks_done = 0
115
+
116
+ extra_tasks_total = 0
117
+ extra_tasks_done = 0
118
+
119
+ cards.each do |c|
120
+ card = Card.parse c
121
+
122
+ list_id = c["idList"]
123
+
124
+ if list_id == todo_list_id || list_id == doing_list_id
125
+ if card.has_sp?
126
+ if card.extra?
127
+ extra_sp_total += card.sp
128
+ else
129
+ sp_total += card.sp
130
+ end
131
+ end
132
+ if card.extra?
133
+ extra_tasks_total += card.tasks
134
+ extra_tasks_done += card.tasks_done
135
+ else
136
+ tasks_total += card.tasks
137
+ tasks_done += card.tasks_done
138
+ end
139
+ elsif list_id == done_list_id
140
+ if card.has_sp?
141
+ if card.extra?
142
+ extra_sp_total += card.sp
143
+ extra_sp_done += card.sp
144
+ else
145
+ sp_total += card.sp
146
+ sp_done += card.sp
147
+ end
148
+ end
149
+ if card.extra?
150
+ extra_tasks_total += card.tasks
151
+ extra_tasks_done += card.tasks_done
152
+ else
153
+ tasks_total += card.tasks
154
+ tasks_done += card.tasks_done
155
+ end
156
+ end
157
+ end
158
+
159
+ @story_points.done = sp_done
160
+ @story_points.open = sp_total - sp_done
161
+
162
+ @tasks.done = tasks_done
163
+ @tasks.open = tasks_total - tasks_done
164
+
165
+ @extra_story_points.done = extra_sp_done
166
+ @extra_story_points.open = extra_sp_total - extra_sp_done
167
+
168
+ @extra_tasks.done = extra_tasks_done
169
+ @extra_tasks.open = extra_tasks_total - extra_tasks_done
170
+ end
171
+
172
+ end