trollolo 0.0.3

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