trollolo 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +5 -1
- data/CHANGELOG.md +29 -0
- data/Gemfile +7 -2
- data/README.md +19 -0
- data/bin/trollolo +1 -1
- data/lib/array.rb +6 -0
- data/lib/backup.rb +67 -0
- data/lib/burndown_chart.rb +96 -67
- data/lib/burndown_data.rb +62 -123
- data/lib/card.rb +74 -30
- data/lib/cli.rb +131 -9
- data/lib/column.rb +61 -0
- data/lib/result.rb +0 -0
- data/lib/scrum_board.rb +104 -0
- data/lib/settings.rb +9 -4
- data/lib/trello_wrapper.rb +62 -0
- data/lib/trollolo.rb +10 -7
- data/lib/version.rb +1 -1
- data/scripts/.gitignore +1 -0
- data/scripts/burndowndata.py +113 -0
- data/scripts/create_burndown.py +111 -146
- data/scripts/graph.py +116 -0
- data/scripts/plot.py +131 -0
- data/spec/data/board.json +63 -0
- data/spec/data/burndown-data.yaml +3 -0
- data/spec/data/burndown_dir/burndown-data-01.yaml +1 -1
- data/spec/data/burndown_dir/burndown-data-02.yaml +1 -1
- data/spec/data/card.json +61 -0
- data/spec/data/full-board.json +1626 -0
- data/spec/data/lists.json +25 -25
- data/spec/data/trollolorc +5 -0
- data/spec/{command_line_spec.rb → integration/command_line_spec.rb} +1 -4
- data/spec/integration/create_burndown_spec.rb +57 -0
- data/spec/integration/integration_spec_helper.rb +10 -0
- data/spec/integration/support/aruba_hook.rb +11 -0
- data/spec/integration/support/custom_matchers.rb +13 -0
- data/spec/{wrapper → integration/wrapper}/credentials_input_wrapper +2 -2
- data/spec/{wrapper → integration/wrapper}/empty_config_trollolo_wrapper +2 -2
- data/spec/integration/wrapper/trollolo_wrapper +10 -0
- data/spec/unit/backup_spec.rb +107 -0
- data/spec/unit/burndown_chart_spec.rb +396 -0
- data/spec/unit/burndown_data_spec.rb +118 -0
- data/spec/unit/card_spec.rb +79 -0
- data/spec/unit/cli_spec.rb +38 -0
- data/spec/unit/retrieve_data_spec.rb +54 -0
- data/spec/unit/scrum_board_spec.rb +18 -0
- data/spec/{settings_spec.rb → unit/settings_spec.rb} +1 -1
- data/spec/{spec_helper.rb → unit/spec_helper.rb} +4 -12
- data/spec/unit/support/test_data_operations.rb +7 -0
- data/spec/unit/support/update_webmock_data +17 -0
- data/spec/unit/support/webmocks.rb +52 -0
- data/spec/unit/trello_wrapper_spec.rb +47 -0
- data/trollolo.gemspec +10 -11
- metadata +54 -37
- data/lib/trello.rb +0 -66
- data/spec/burndown_chart_spec.rb +0 -307
- data/spec/burndown_data_spec.rb +0 -125
- data/spec/card_spec.rb +0 -15
- data/spec/cli_spec.rb +0 -18
- data/spec/data/cards.json +0 -1002
- data/spec/trello_spec.rb +0 -32
- data/spec/wrapper/trollolo_wrapper +0 -11
data/lib/settings.rb
CHANGED
@@ -17,8 +17,9 @@
|
|
17
17
|
|
18
18
|
class Settings
|
19
19
|
|
20
|
-
attr_accessor :verbose, :raw
|
21
|
-
|
20
|
+
attr_accessor :developer_public_key, :member_token, :verbose, :raw,
|
21
|
+
:not_done_columns, :todo_column, :done_column_name_regex,
|
22
|
+
:todo_column_name_regex
|
22
23
|
|
23
24
|
def initialize config_file_path
|
24
25
|
@config_file_path = config_file_path
|
@@ -26,8 +27,12 @@ class Settings
|
|
26
27
|
@config = YAML.load_file(config_file_path)
|
27
28
|
|
28
29
|
if @config
|
29
|
-
@developer_public_key
|
30
|
-
@member_token
|
30
|
+
@developer_public_key = @config["developer_public_key"]
|
31
|
+
@member_token = @config["member_token"]
|
32
|
+
@not_done_columns = @config["not_done_columns"].freeze || ["Sprint Backlog", "Doing"]
|
33
|
+
@todo_column = @config["todo_column"].freeze
|
34
|
+
@done_column_name_regex = @config["done_column_name_regex"].freeze || /\ADone Sprint (\d+)\Z/
|
35
|
+
@todo_column_name_regex = @config["todo_column_name_regex"].freeze || /\ATo Do\Z/
|
31
36
|
else
|
32
37
|
raise "Couldn't read config data from '#{config_file_path}'"
|
33
38
|
end
|
@@ -0,0 +1,62 @@
|
|
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
|
+
require 'trello'
|
18
|
+
|
19
|
+
class TrelloWrapper
|
20
|
+
|
21
|
+
attr_accessor :board
|
22
|
+
|
23
|
+
def initialize(settings)
|
24
|
+
@settings = settings
|
25
|
+
init_trello
|
26
|
+
end
|
27
|
+
|
28
|
+
def client
|
29
|
+
Trello::Client.new(
|
30
|
+
developer_public_key: @settings.developer_public_key,
|
31
|
+
member_token: @settings.member_token
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def board(board_id)
|
36
|
+
return @board if @board
|
37
|
+
|
38
|
+
@board = ScrumBoard.new(retrieve_board_data(board_id), @settings)
|
39
|
+
end
|
40
|
+
|
41
|
+
def retrieve_board_data(board_id)
|
42
|
+
JSON.parse(client.get("/boards/#{board_id}?lists=open&cards=open&card_checklists=all"))
|
43
|
+
end
|
44
|
+
|
45
|
+
def backup(board_id)
|
46
|
+
client.get("/boards/#{board_id}?lists=open&cards=open&card_checklists=all")
|
47
|
+
end
|
48
|
+
|
49
|
+
def organization(org_id)
|
50
|
+
Trello::Organization.find(org_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def init_trello
|
56
|
+
Trello.configure do |config|
|
57
|
+
config.developer_public_key = @settings.developer_public_key
|
58
|
+
config.member_token = @settings.member_token
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/lib/trollolo.rb
CHANGED
@@ -15,20 +15,23 @@
|
|
15
15
|
# To contact SUSE about this file by physical or electronic mail,
|
16
16
|
# you may find current contact information at www.suse.com
|
17
17
|
|
18
|
-
require
|
19
|
-
require
|
20
|
-
require
|
21
|
-
require
|
22
|
-
require "yaml"
|
23
|
-
require "erb"
|
18
|
+
require 'thor'
|
19
|
+
require 'json'
|
20
|
+
require 'yaml'
|
21
|
+
require 'erb'
|
24
22
|
|
23
|
+
require_relative 'array'
|
25
24
|
require_relative 'version'
|
26
25
|
require_relative 'cli'
|
27
26
|
require_relative 'settings'
|
28
|
-
require_relative '
|
27
|
+
require_relative 'column'
|
29
28
|
require_relative 'card'
|
29
|
+
require_relative 'scrum_board'
|
30
|
+
require_relative 'result'
|
31
|
+
require_relative 'trello_wrapper'
|
30
32
|
require_relative 'burndown_chart'
|
31
33
|
require_relative 'burndown_data'
|
34
|
+
require_relative 'backup'
|
32
35
|
|
33
36
|
class TrolloloError < StandardError
|
34
37
|
end
|
data/lib/version.rb
CHANGED
data/scripts/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.pyc
|
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
import yaml
|
3
|
+
|
4
|
+
|
5
|
+
class BurndownData:
|
6
|
+
"Store burndown data parsed from YAML file"
|
7
|
+
|
8
|
+
def __init__(self, args):
|
9
|
+
self.args = args
|
10
|
+
burndown = self.readYAML(self.args.sprint)
|
11
|
+
self.getSprintData(burndown)
|
12
|
+
self.calculateMaxStoryPoints()
|
13
|
+
self.setBonusTasksDayOne(burndown)
|
14
|
+
self.setExtraDays()
|
15
|
+
self.calculateYRange(self.max_story_points, self.bonus_tasks_done, self.bonus_story_points_done)
|
16
|
+
self.setScaleFactor(self.total_tasks[0], self.max_story_points)
|
17
|
+
|
18
|
+
def readYAML(self, sprint_number):
|
19
|
+
with open('burndown-data-' + sprint_number + '.yaml', 'r') as f:
|
20
|
+
burndown = yaml.load(f)
|
21
|
+
return burndown
|
22
|
+
|
23
|
+
def getSprintData(self, burndown):
|
24
|
+
self.sprint_number = burndown["meta"]["sprint"]
|
25
|
+
self.weekend_lines = burndown["meta"]["weekend_lines"]
|
26
|
+
self.total_days = burndown["meta"]["total_days"]
|
27
|
+
self.extra_day = 0
|
28
|
+
self.current_day = 1
|
29
|
+
self.days = []
|
30
|
+
self.tasks_extra_days = []
|
31
|
+
self.story_points_extra_days = []
|
32
|
+
self.open_story_points = []
|
33
|
+
self.total_story_points = []
|
34
|
+
self.bonus_story_points_done = []
|
35
|
+
self.open_tasks = []
|
36
|
+
self.total_tasks = []
|
37
|
+
self.bonus_tasks_done = []
|
38
|
+
self.x_fast_lane = []
|
39
|
+
self.y_fast_lane = []
|
40
|
+
self.total_fast_lane = []
|
41
|
+
self.max_story_points = 0
|
42
|
+
|
43
|
+
for day in burndown["days"]:
|
44
|
+
self.days.append(self.current_day)
|
45
|
+
self.open_story_points.append(day["story_points"]["open"])
|
46
|
+
self.total_story_points.append(day["story_points"]["total"])
|
47
|
+
self.open_tasks.append(day["tasks"]["open"])
|
48
|
+
self.total_tasks.append(day["tasks"]["total"])
|
49
|
+
|
50
|
+
if "tasks_extra" in day:
|
51
|
+
self.tasks_extra_days.append(self.current_day)
|
52
|
+
tasks = -day["tasks_extra"]["done"]
|
53
|
+
self.bonus_tasks_done.append(tasks)
|
54
|
+
|
55
|
+
if "story_points_extra" in day:
|
56
|
+
self.story_points_extra_days.append(self.current_day)
|
57
|
+
points = -day["story_points_extra"]["done"]
|
58
|
+
self.bonus_story_points_done.append(points)
|
59
|
+
|
60
|
+
if day.has_key("fast_lane"):
|
61
|
+
self.x_fast_lane.append(self.current_day)
|
62
|
+
self.y_fast_lane.append(day["fast_lane"]["open"])
|
63
|
+
self.total_fast_lane.append(day["fast_lane"]["total"])
|
64
|
+
|
65
|
+
self.current_day += 1
|
66
|
+
return
|
67
|
+
|
68
|
+
def calculateMaxStoryPoints(self):
|
69
|
+
for sp in self.total_story_points:
|
70
|
+
self.max_story_points = max(self.max_story_points, sp)
|
71
|
+
return
|
72
|
+
|
73
|
+
def setBonusTasksDayOne(self, burndown):
|
74
|
+
if burndown["days"][0].has_key("tasks_extra"):
|
75
|
+
self.bonus_tasks_day_one = burndown["days"][0]["tasks_extra"]["done"]
|
76
|
+
else:
|
77
|
+
self.bonus_tasks_day_one = 0
|
78
|
+
return
|
79
|
+
|
80
|
+
def setExtraDays(self):
|
81
|
+
if len(self.story_points_extra_days) > 0:
|
82
|
+
self.story_points_extra_days = [self.story_points_extra_days[0] - 1] + self.story_points_extra_days
|
83
|
+
self.bonus_story_points_done = [0] + self.bonus_story_points_done
|
84
|
+
if len(self.tasks_extra_days) > 0:
|
85
|
+
if not self.args.no_tasks and not self.bonus_tasks_day_one:
|
86
|
+
self.tasks_extra_days = [self.tasks_extra_days[0] - 1] + self.tasks_extra_days
|
87
|
+
self.bonus_tasks_done = [0] + self.bonus_tasks_done
|
88
|
+
self.extra_day = 1
|
89
|
+
return
|
90
|
+
|
91
|
+
def calculateYRange(self, max_story_points, bonus_tasks_done, bonus_story_points_done):
|
92
|
+
self.ymax = max_story_points + 3
|
93
|
+
|
94
|
+
if len(bonus_tasks_done) > 0:
|
95
|
+
ymin_bonus_tasks = min(bonus_tasks_done) -3
|
96
|
+
else:
|
97
|
+
ymin_bonus_tasks = 0
|
98
|
+
|
99
|
+
ymin_bonus_story_points = 0
|
100
|
+
|
101
|
+
if len(bonus_story_points_done) > 0:
|
102
|
+
ymin_bonus_story_points = min(bonus_story_points_done) -3
|
103
|
+
|
104
|
+
if ymin_bonus_tasks == 0 and ymin_bonus_story_points == 0:
|
105
|
+
self.ymin = -3
|
106
|
+
else:
|
107
|
+
self.ymin = min(ymin_bonus_tasks, ymin_bonus_story_points)
|
108
|
+
return
|
109
|
+
|
110
|
+
def setScaleFactor(self, total_tasks, max_story_points):
|
111
|
+
self.scalefactor = float(total_tasks) / float(max_story_points)
|
112
|
+
return
|
113
|
+
|
data/scripts/create_burndown.py
CHANGED
@@ -1,149 +1,114 @@
|
|
1
|
-
#!/usr/bin/python
|
1
|
+
#!/usr/bin/env python
|
2
|
+
import matplotlib
|
3
|
+
import imp
|
4
|
+
try:
|
5
|
+
imp.find_module('TkAgg')
|
6
|
+
mac_backend_available = True
|
7
|
+
except ImportError:
|
8
|
+
mac_backend_available = False
|
9
|
+
|
10
|
+
if mac_backend_available:
|
11
|
+
matplotlib.use('TkAgg')
|
12
|
+
|
2
13
|
import matplotlib.pyplot as plt
|
3
|
-
import
|
4
|
-
import
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
if
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
plt.axis([0, total_days + 1, ymin*scalefactor, ymax * scalefactor])
|
101
|
-
plt.plot(x_days, y_open_tasks, 'go-', linewidth=2)
|
102
|
-
if x_days_extra:
|
103
|
-
plt.plot(x_days_extra, y_tasks_done_extra, 'go-', linewidth=2)
|
104
|
-
|
105
|
-
# Calculation of new tasks
|
106
|
-
if len(total_tasks) > 1:
|
107
|
-
new_tasks = [0]
|
108
|
-
for i in range(1, len(total_tasks)):
|
109
|
-
new_tasks.append(total_tasks[i] - total_tasks[i - 1])
|
110
|
-
effective_new_tasks_days = []
|
111
|
-
effective_new_tasks = []
|
112
|
-
for i in range(len(new_tasks)):
|
113
|
-
if new_tasks[i] != 0:
|
114
|
-
effective_new_tasks_days.append(i - 0.25 + 1)
|
115
|
-
effective_new_tasks.append(new_tasks[i])
|
116
|
-
if len(effective_new_tasks) > 0:
|
117
|
-
plt.bar(effective_new_tasks_days, effective_new_tasks, .2, color='green')
|
118
|
-
|
119
|
-
# Calculation of new story points
|
120
|
-
if len(total_story_points) > 1:
|
121
|
-
new_story_points = [0]
|
122
|
-
for i in range(1, len(total_story_points)):
|
123
|
-
new_story_points.append(total_story_points[i] - total_story_points[i - 1])
|
124
|
-
effective_new_story_points_days = []
|
125
|
-
effective_new_story_points = []
|
126
|
-
for i in range(len(new_story_points)):
|
127
|
-
if new_story_points[i] != 0:
|
128
|
-
effective_new_story_points_days.append(i + 0.05 + 1)
|
129
|
-
effective_new_story_points.append(new_story_points[i])
|
130
|
-
if len(effective_new_story_points) > 0:
|
131
|
-
plt.bar(effective_new_story_points_days, effective_new_story_points, .2, color='black')
|
132
|
-
|
133
|
-
# Draw arrow showing already done tasks at begin of sprint
|
134
|
-
tasks_done = burndown["days"][0]["tasks"]["total"] - burndown["days"][0]["tasks"]["open"]
|
135
|
-
|
136
|
-
if tasks_done > 5:
|
137
|
-
plt.annotate("",
|
138
|
-
xy=(x_days[0], scalefactor * y_open_story_points[0] - 0.5 ), xycoords='data',
|
139
|
-
xytext=(x_days[0], y_open_tasks[0] + 0.5), textcoords='data',
|
140
|
-
arrowprops=dict(arrowstyle="<|-|>", connectionstyle="arc3", color='green')
|
141
|
-
)
|
142
|
-
|
143
|
-
plt.text(0.7, y_open_story_points[0], str(tasks_done) + " tasks done",
|
144
|
-
rotation='vertical', verticalalignment='center', color='green'
|
145
|
-
)
|
14
|
+
import argparse
|
15
|
+
import os
|
16
|
+
|
17
|
+
import burndowndata
|
18
|
+
import plot
|
19
|
+
import graph
|
20
|
+
|
21
|
+
|
22
|
+
def parseCommandLine():
|
23
|
+
epilog = "Look at https://github.com/openSUSE/trollolo for details"
|
24
|
+
description = "Generates Scrum Burndown Chart from YAML file"
|
25
|
+
|
26
|
+
parser = argparse.ArgumentParser(epilog=epilog, description=description)
|
27
|
+
parser.add_argument('sprint', metavar='NUM', help='Sprint Number')
|
28
|
+
parser.add_argument('--output', help='Location of data to process')
|
29
|
+
parser.add_argument('--no-tasks', action='store_true', help='Disable Tasks line in the chart', default=False)
|
30
|
+
parser.add_argument('--with-fast-lane', action='store_true', help='Draw line for Fast Lane cards', default=False)
|
31
|
+
parser.add_argument('--verbose', action='store_true', help='Verbose Output', default=False)
|
32
|
+
parser.add_argument('--no-head', action='store_true', help='Run in headless mode', default=False)
|
33
|
+
args = parser.parse_args()
|
34
|
+
|
35
|
+
if args.output:
|
36
|
+
os.chdir(args.output)
|
37
|
+
|
38
|
+
if args.verbose:
|
39
|
+
print args
|
40
|
+
|
41
|
+
return args
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
### MAIN ###
|
46
|
+
|
47
|
+
# parseCommandLine() needs to be called at the beginning to retrieve the
|
48
|
+
# command line parameters or provide help on these. It returns a dict
|
49
|
+
# containing the state of all parameters. Currently the following
|
50
|
+
# parameters are available:
|
51
|
+
#
|
52
|
+
# Mandatory parameters:
|
53
|
+
# NUM The sprint number for which the burndown chart should be generated
|
54
|
+
#
|
55
|
+
# Optional parameters:
|
56
|
+
# --file Specify the location of the YAML file containing the sprint data,
|
57
|
+
# if not provided, the YAML file is expected to be in the current
|
58
|
+
# working directory
|
59
|
+
#
|
60
|
+
# --no-tasks Disable drawing the tasks graph in the chart
|
61
|
+
#
|
62
|
+
# --with-fast-lane Enable drawing the graph for fast lane cards in the chart
|
63
|
+
#
|
64
|
+
# --no-head Disable showing the graph, to be used for automation
|
65
|
+
#
|
66
|
+
# --verbose Verbose output
|
67
|
+
#
|
68
|
+
args = parseCommandLine()
|
69
|
+
|
70
|
+
# Create burndown data object
|
71
|
+
data = burndowndata.BurndownData(args)
|
72
|
+
|
73
|
+
# Configure plot parameters
|
74
|
+
plot = plot.Plot(data)
|
75
|
+
|
76
|
+
title = "Sprint" + str(data.sprint_number)
|
77
|
+
title_fontsize = 'large'
|
78
|
+
plot.setTitle(title, title_fontsize)
|
79
|
+
plot.setXAxisLabel("Days")
|
80
|
+
|
81
|
+
plot.drawDiagonal("grey")
|
82
|
+
plot.drawWaterLine("blue", ":")
|
83
|
+
plot.drawWeekendLines("grey", ":")
|
84
|
+
|
85
|
+
# Plot all graphs
|
86
|
+
graph_story_points = graph.Graph(plot.storyPoints())
|
87
|
+
|
88
|
+
y_label = "Story Points"
|
89
|
+
color = "black"
|
90
|
+
marker = "o"
|
91
|
+
linestyle = "solid"
|
92
|
+
linewidth = 2
|
93
|
+
|
94
|
+
graph_story_points.draw(y_label, color, marker, linestyle, linewidth, plot)
|
95
|
+
|
96
|
+
if not args.no_tasks:
|
97
|
+
graph_tasks = graph.Graph(plot.tasks())
|
98
|
+
|
99
|
+
y_label = "Tasks"
|
100
|
+
color = "green"
|
101
|
+
|
102
|
+
graph_tasks.draw(y_label, color, marker, linestyle, linewidth, plot)
|
103
|
+
|
104
|
+
if args.with_fast_lane:
|
105
|
+
graph_fast_lane = graph.Graph(plot.fastLane())
|
106
|
+
|
107
|
+
y_label = "Fast Lane"
|
108
|
+
color = "red"
|
109
|
+
|
110
|
+
graph_fast_lane.draw(y_label, color, marker, linestyle, linewidth, plot)
|
146
111
|
|
147
112
|
# Save the burndown chart
|
148
|
-
|
149
|
-
|
113
|
+
plot.saveImage(args)
|
114
|
+
|