trollolo 0.0.3 → 0.0.4
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 +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
|
+
|