shiftzilla 0.2.6
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 +7 -0
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/README.md +20 -0
- data/bin/shiftzilla +110 -0
- data/lib/shiftzilla/bug.rb +67 -0
- data/lib/shiftzilla/config.rb +98 -0
- data/lib/shiftzilla/engine.rb +183 -0
- data/lib/shiftzilla/group.rb +10 -0
- data/lib/shiftzilla/helpers.rb +174 -0
- data/lib/shiftzilla/milestone.rb +20 -0
- data/lib/shiftzilla/milestones.rb +28 -0
- data/lib/shiftzilla/org_data.rb +332 -0
- data/lib/shiftzilla/release.rb +34 -0
- data/lib/shiftzilla/release_data.rb +185 -0
- data/lib/shiftzilla/snap_data.rb +31 -0
- data/lib/shiftzilla/source.rb +51 -0
- data/lib/shiftzilla/team.rb +17 -0
- data/lib/shiftzilla/team_data.rb +35 -0
- data/lib/shiftzilla/version.rb +3 -0
- data/shiftzilla.gemspec +30 -0
- data/shiftzilla.sql.tmpl +14 -0
- data/shiftzilla_cfg.yml.tmpl +42 -0
- data/template.haml +186 -0
- data/vendor/flot/jquery.flot.min.js +8 -0
- metadata +218 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'shiftzilla/milestone'
|
2
|
+
|
3
|
+
module Shiftzilla
|
4
|
+
class Milestones
|
5
|
+
def initialize(msrc)
|
6
|
+
@milestones = {}
|
7
|
+
msrc.each do |key,val|
|
8
|
+
@milestones[key.to_sym] = Shiftzilla::Milestone.new(val)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
@milestones[:start]
|
14
|
+
end
|
15
|
+
|
16
|
+
def feature_complete
|
17
|
+
@milestones[:feature_complete]
|
18
|
+
end
|
19
|
+
|
20
|
+
def code_freeze
|
21
|
+
@milestones[:code_freeze]
|
22
|
+
end
|
23
|
+
|
24
|
+
def ga
|
25
|
+
@milestones[:ga]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,332 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'shiftzilla/bug'
|
3
|
+
require 'shiftzilla/helpers'
|
4
|
+
require 'shiftzilla/team_data'
|
5
|
+
|
6
|
+
include Shiftzilla::Helpers
|
7
|
+
|
8
|
+
module Shiftzilla
|
9
|
+
class OrgData
|
10
|
+
attr_reader :tmp_dir
|
11
|
+
|
12
|
+
def initialize(config)
|
13
|
+
@config = config
|
14
|
+
@teams = config.teams
|
15
|
+
@releases = config.releases
|
16
|
+
@tmp_dir = Shiftzilla::Helpers.tmp_dir
|
17
|
+
@org_data = { '_overall' => Shiftzilla::TeamData.new('_overall') }
|
18
|
+
end
|
19
|
+
|
20
|
+
def populate_releases
|
21
|
+
all_snapshots.each do |snapshot|
|
22
|
+
snapdate = Date.parse(snapshot)
|
23
|
+
next if snapdate < @config.earliest_milestone
|
24
|
+
break if snapdate > @config.latest_milestone
|
25
|
+
break if snapdate > Date.today
|
26
|
+
|
27
|
+
dbh.execute(all_bugs_query(snapshot)) do |row|
|
28
|
+
bzid = row[0].strip
|
29
|
+
comp = row[1].strip
|
30
|
+
tgtr = row[2].strip
|
31
|
+
owns = row[3].strip
|
32
|
+
stat = row[4].strip
|
33
|
+
summ = row[5].strip
|
34
|
+
keyw = row[6].nil? ? '' : row[6].strip
|
35
|
+
pmsc = row[7].nil? ? '0' : row[7].strip
|
36
|
+
cust = row[8].nil? ? 0 : row[8]
|
37
|
+
|
38
|
+
# Package up bug data
|
39
|
+
binfo = {
|
40
|
+
:snapdate => snapdate,
|
41
|
+
:test_blocker => keyw.include?('TestBlocker'),
|
42
|
+
:ops_blocker => keyw.include?('OpsBlocker'),
|
43
|
+
:owner => owns,
|
44
|
+
:summary => summ,
|
45
|
+
:component => comp,
|
46
|
+
:pm_score => pmsc,
|
47
|
+
:cust_cases => (cust == 1),
|
48
|
+
:tgt_release => tgtr,
|
49
|
+
}
|
50
|
+
|
51
|
+
tgt_release = @config.release_by_target(tgtr)
|
52
|
+
all_release = @config.release('All')
|
53
|
+
|
54
|
+
# If this component isn't mapped to a team, stub out a fake team.
|
55
|
+
tname = comp_map.has_key?(comp) ? comp_map[comp] : "(?) #{comp}"
|
56
|
+
unless @org_data.has_key?(tname)
|
57
|
+
@config.add_ad_hoc_team({ 'name' => tname, 'components' => [comp] })
|
58
|
+
@org_data[tname] = Shiftzilla::TeamData.new(tname)
|
59
|
+
end
|
60
|
+
|
61
|
+
team_rdata = tgt_release.nil? ? nil : @org_data[tname].get_release_data(tgt_release)
|
62
|
+
team_adata = @org_data[tname].get_release_data(all_release)
|
63
|
+
over_rdata = tgt_release.nil? ? nil : @org_data['_overall'].get_release_data(tgt_release)
|
64
|
+
over_adata = @org_data['_overall'].get_release_data(all_release)
|
65
|
+
|
66
|
+
# Do some bean counting
|
67
|
+
[over_rdata,team_rdata,over_adata,team_adata].each do |group|
|
68
|
+
next if group.nil?
|
69
|
+
snapdata = group.get_snapdata(snapshot)
|
70
|
+
if group.first_snap.nil?
|
71
|
+
group.first_snap = snapshot
|
72
|
+
group.first_snapdate = snapdate
|
73
|
+
end
|
74
|
+
group.latest_snap = snapshot
|
75
|
+
group.latest_snapdate = snapdate
|
76
|
+
|
77
|
+
bug = group.add_or_update_bug(bzid,binfo)
|
78
|
+
|
79
|
+
# Add info to the snapshot
|
80
|
+
snapdata.bug_ids << bzid
|
81
|
+
if bug.test_blocker or bug.ops_blocker
|
82
|
+
snapdata.tb_ids << bzid
|
83
|
+
end
|
84
|
+
if bug.cust_cases
|
85
|
+
snapdata.cc_ids << bzid
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Determine new and closed bug counts by comparing the current
|
91
|
+
# snapshot to the previous snapshot
|
92
|
+
@org_data.each do |tname,tdata|
|
93
|
+
@releases.each do |release|
|
94
|
+
next unless tdata.has_release_data?(release)
|
95
|
+
|
96
|
+
rdata = tdata.get_release_data(release)
|
97
|
+
next unless rdata.has_snapdata?(snapshot)
|
98
|
+
|
99
|
+
if rdata.prev_snap.nil?
|
100
|
+
rdata.prev_snap = snapshot
|
101
|
+
next
|
102
|
+
end
|
103
|
+
|
104
|
+
currdata = rdata.get_snapdata(snapshot)
|
105
|
+
prevdata = rdata.get_snapdata(rdata.prev_snap)
|
106
|
+
|
107
|
+
prev_bzids = prevdata.bug_ids
|
108
|
+
curr_bzids = currdata.bug_ids
|
109
|
+
prev_tbids = prevdata.tb_ids
|
110
|
+
curr_tbids = currdata.tb_ids
|
111
|
+
prev_ccids = prevdata.cc_ids
|
112
|
+
curr_ccids = currdata.cc_ids
|
113
|
+
currdata.closed_bugs = prev_bzids.select{ |bzid| not curr_bzids.include?(bzid) }.length
|
114
|
+
currdata.new_bugs = curr_bzids.select{ |bzid| not prev_bzids.include?(bzid) }.length
|
115
|
+
currdata.closed_tb = prev_tbids.select{ |tbid| not curr_tbids.include?(tbid) }.length
|
116
|
+
currdata.new_tb = curr_tbids.select{ |tbid| not prev_tbids.include?(tbid) }.length
|
117
|
+
currdata.closed_cc = prev_ccids.select{ |ccid| not curr_ccids.include?(ccid) }.length
|
118
|
+
currdata.new_cc = curr_ccids.select{ |ccid| not prev_ccids.include?(ccid) }.length
|
119
|
+
|
120
|
+
rdata.prev_snap = snapshot
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
@all_teams = @teams.map{ |t| t.name }.concat(@org_data.keys).uniq
|
125
|
+
@ordered_teams = ['_overall'].concat(@all_teams.select{ |t| t != '_overall'}.sort)
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_ordered_teams
|
129
|
+
@ordered_teams
|
130
|
+
end
|
131
|
+
|
132
|
+
def get_release_data(team_name,release)
|
133
|
+
if @org_data.has_key?(team_name) and @org_data[team_name].has_release_data?(release)
|
134
|
+
return @org_data[team_name].get_release_data(release)
|
135
|
+
end
|
136
|
+
return nil
|
137
|
+
end
|
138
|
+
|
139
|
+
def build_series
|
140
|
+
@team_files = []
|
141
|
+
@ordered_teams.each do |tname|
|
142
|
+
unless @org_data.has_key?(tname)
|
143
|
+
@org_data[tname] = Shiftzilla::TeamData.new(tname)
|
144
|
+
end
|
145
|
+
tdata = @org_data[tname]
|
146
|
+
tinfo = @config.team(tname)
|
147
|
+
|
148
|
+
@team_files << {
|
149
|
+
:tname => tdata.title,
|
150
|
+
:file => tdata.file,
|
151
|
+
:releases => {},
|
152
|
+
}
|
153
|
+
|
154
|
+
@releases.each do |release|
|
155
|
+
rname = release.name
|
156
|
+
bug_total = 0
|
157
|
+
unless tdata.has_release_data?(release)
|
158
|
+
@team_files[-1][:releases][release.name] = bug_total
|
159
|
+
next
|
160
|
+
end
|
161
|
+
rdata = tdata.get_release_data(release)
|
162
|
+
if rdata.snaps.has_key?(latest_snapshot)
|
163
|
+
bug_total = rdata.snaps[latest_snapshot].total_bugs
|
164
|
+
end
|
165
|
+
@team_files[-1][:releases][release.name] = bug_total
|
166
|
+
|
167
|
+
next if rdata.first_snap.nil? and not release.uses_milestones?
|
168
|
+
next if release.built_in?
|
169
|
+
|
170
|
+
rdata.populate_series
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def generate_reports
|
176
|
+
build_time = timestamp
|
177
|
+
all_release = @config.release('All')
|
178
|
+
@ordered_teams.each do |tname|
|
179
|
+
tinfo = @config.team(tname)
|
180
|
+
tdata = @org_data[tname]
|
181
|
+
|
182
|
+
team_pinfo = {
|
183
|
+
:build_time => build_time,
|
184
|
+
:tname => tdata.title,
|
185
|
+
:tinfo => tinfo,
|
186
|
+
:tdata => tdata,
|
187
|
+
:team_files => @team_files,
|
188
|
+
:bug_url => BZ_URL,
|
189
|
+
:chart_order => ((1..(@releases.length - 1)).to_a << 0),
|
190
|
+
:releases => [],
|
191
|
+
:latest_snapshot => latest_snapshot,
|
192
|
+
:all_bugs => [],
|
193
|
+
}
|
194
|
+
|
195
|
+
@releases.each do |release|
|
196
|
+
rname = release.name
|
197
|
+
rdata = tdata.has_release_data?(release) ? tdata.get_release_data(release) : nil
|
198
|
+
snapdata = nil
|
199
|
+
if not rdata.nil? and rdata.snaps.has_key?(latest_snapshot)
|
200
|
+
snapdata = rdata.snaps[latest_snapshot]
|
201
|
+
else
|
202
|
+
snapdata = Shiftzilla::SnapData.new(latest_snapshot)
|
203
|
+
end
|
204
|
+
|
205
|
+
release_info = {
|
206
|
+
:release => release,
|
207
|
+
:snapdata => snapdata,
|
208
|
+
:no_rdata => (rdata.nil? ? true : false),
|
209
|
+
:bug_avg_age => (rdata.nil? ? 0 : rdata.bug_avg_age),
|
210
|
+
:tb_avg_age => (rdata.nil? ? 0 : rdata.tb_avg_age),
|
211
|
+
}
|
212
|
+
unless rdata.nil?
|
213
|
+
release_info[:charts] = {
|
214
|
+
:burndown => chartify('Bug Burndown',rdata.series[:date],[
|
215
|
+
{
|
216
|
+
:label => 'Ideal Trend',
|
217
|
+
:data => rdata.series[:ideal],
|
218
|
+
},
|
219
|
+
{
|
220
|
+
:label => 'Total',
|
221
|
+
:data => rdata.series[:total_bugs],
|
222
|
+
},
|
223
|
+
{
|
224
|
+
:label => 'w/ Customer Cases',
|
225
|
+
:data => rdata.series[:total_cc],
|
226
|
+
},
|
227
|
+
]),
|
228
|
+
:new_closed => chartify('New vs. Closed',rdata.series[:date],[
|
229
|
+
{
|
230
|
+
:label => 'New',
|
231
|
+
:data => rdata.series[:new_bugs],
|
232
|
+
},
|
233
|
+
{
|
234
|
+
:label => 'Closed',
|
235
|
+
:data => rdata.series[:closed_bugs],
|
236
|
+
},
|
237
|
+
]),
|
238
|
+
:blockers => chartify('Test / Ops Blockers',rdata.series[:date],[
|
239
|
+
{
|
240
|
+
:label => 'Total',
|
241
|
+
:data => rdata.series[:total_tb],
|
242
|
+
},
|
243
|
+
{
|
244
|
+
:label => 'New',
|
245
|
+
:data => rdata.series[:new_tb],
|
246
|
+
},
|
247
|
+
{
|
248
|
+
:label => 'Closed',
|
249
|
+
:data => rdata.series[:closed_tb],
|
250
|
+
},
|
251
|
+
]),
|
252
|
+
}
|
253
|
+
end
|
254
|
+
team_pinfo[:releases] << release_info
|
255
|
+
|
256
|
+
if rname == 'All'
|
257
|
+
team_pinfo[:all_bugs] = snapdata.bug_ids.map{ |id| rdata.bugs[id] }
|
258
|
+
end
|
259
|
+
end
|
260
|
+
team_page = haml_engine.render(Object.new,team_pinfo)
|
261
|
+
File.write(File.join(@tmp_dir,tdata.file), team_page)
|
262
|
+
end
|
263
|
+
# Copy flot library to build area
|
264
|
+
jsdir = File.join(@tmp_dir,'js')
|
265
|
+
Dir.mkdir(jsdir)
|
266
|
+
FileUtils.cp(File.join(VENDOR_DIR,'flot','jquery.flot.min.js'),jsdir)
|
267
|
+
end
|
268
|
+
|
269
|
+
def show_local_reports
|
270
|
+
system("open file://#{@tmp_dir}/index.html")
|
271
|
+
end
|
272
|
+
|
273
|
+
def publish_reports(ssh)
|
274
|
+
system("ssh #{ssh[:host]} 'rm -rf #{ssh[:path]}/*'")
|
275
|
+
system("rsync -avPq #{@tmp_dir}/* #{ssh[:host]}:#{ssh[:path]}/")
|
276
|
+
end
|
277
|
+
|
278
|
+
private
|
279
|
+
|
280
|
+
def comp_map
|
281
|
+
@comp_map ||= begin
|
282
|
+
comp_map = {}
|
283
|
+
@teams.each do |team|
|
284
|
+
team.components.each do |comp|
|
285
|
+
comp_map[comp] = team.name
|
286
|
+
end
|
287
|
+
end
|
288
|
+
comp_map
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def chartify(title,dates,series)
|
293
|
+
modulo = label_modulo(dates.length)
|
294
|
+
tlist = []
|
295
|
+
dates.each_with_index do |val,idx|
|
296
|
+
next unless (idx + 1) % modulo == 0
|
297
|
+
tlist << [idx,Date.parse(val).strftime('%m/%d')]
|
298
|
+
end
|
299
|
+
data = []
|
300
|
+
series.each do |s|
|
301
|
+
dlist = []
|
302
|
+
s[:data].each_with_index do |val,idx|
|
303
|
+
dlist <<[idx,val]
|
304
|
+
end
|
305
|
+
data << { :label => s[:label], :data => dlist }
|
306
|
+
end
|
307
|
+
options = {
|
308
|
+
:xaxis => {
|
309
|
+
:ticks => tlist,
|
310
|
+
},
|
311
|
+
:yaxis => {
|
312
|
+
:min => 0,
|
313
|
+
:labelWidth => 50,
|
314
|
+
},
|
315
|
+
:tickSize => 5,
|
316
|
+
}
|
317
|
+
return { :title => title, :data => data.to_json, :options => options.to_json }
|
318
|
+
end
|
319
|
+
|
320
|
+
def label_modulo(date_count)
|
321
|
+
if date_count < 15
|
322
|
+
return 1
|
323
|
+
elsif date_count < 30
|
324
|
+
return 2
|
325
|
+
elsif date_count < 60
|
326
|
+
return 5
|
327
|
+
else
|
328
|
+
return 10
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'shiftzilla/milestones'
|
2
|
+
|
3
|
+
module Shiftzilla
|
4
|
+
class Release
|
5
|
+
attr_reader :name, :targets, :milestones, :default, :token
|
6
|
+
|
7
|
+
def initialize(release,builtin=false)
|
8
|
+
@name = release['name']
|
9
|
+
@token = @name.tr(' .', '_')
|
10
|
+
@targets = release['targets']
|
11
|
+
@default = release.has_key?('default') ? release['default'] : false
|
12
|
+
@builtin = builtin
|
13
|
+
@milestones = nil
|
14
|
+
if release.has_key?('milestones')
|
15
|
+
@milestones = Shiftzilla::Milestones.new(release['milestones'])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def uses_milestones?
|
20
|
+
return @milestones.nil? ? false : true
|
21
|
+
end
|
22
|
+
|
23
|
+
def built_in?
|
24
|
+
@builtin
|
25
|
+
end
|
26
|
+
|
27
|
+
def no_tgt_rel?
|
28
|
+
if @targets.length == 1 and @targets[0] == '---'
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'shiftzilla/bug'
|
2
|
+
require 'shiftzilla/snap_data'
|
3
|
+
|
4
|
+
module Shiftzilla
|
5
|
+
class ReleaseData
|
6
|
+
attr_accessor :snaps, :bugs, :prev_snap, :latest_snap, :latest_snapdate, :first_snap, :first_snapdate, :labels, :series
|
7
|
+
|
8
|
+
def initialize(release)
|
9
|
+
@release = release
|
10
|
+
@snaps = {}
|
11
|
+
@bugs = {}
|
12
|
+
@prev_snap = nil
|
13
|
+
@latest_snap = nil
|
14
|
+
@latest_snapdate = nil
|
15
|
+
@first_snap = nil
|
16
|
+
@first_snapdate = nil
|
17
|
+
@labels = {}
|
18
|
+
@series = {
|
19
|
+
:date => [],
|
20
|
+
:ideal => [],
|
21
|
+
:total_bugs => [],
|
22
|
+
:new_bugs => [],
|
23
|
+
:closed_bugs => [],
|
24
|
+
:total_tb => [],
|
25
|
+
:new_tb => [],
|
26
|
+
:closed_tb => [],
|
27
|
+
:total_cc => [],
|
28
|
+
:new_cc => [],
|
29
|
+
:closed_cc => [],
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def max_total
|
34
|
+
pick_max([@snaps.values.map{ |s| s.total_bugs }])
|
35
|
+
end
|
36
|
+
|
37
|
+
def max_new_closed
|
38
|
+
pick_max([@snaps.values.map{ |s| s.new_bugs },
|
39
|
+
@snaps.values.map{ |s| s.closed_bugs }
|
40
|
+
])
|
41
|
+
end
|
42
|
+
|
43
|
+
def max_tb
|
44
|
+
pick_max([@snaps.values.map{ |s| s.total_tb },
|
45
|
+
@snaps.values.map{ |s| s.new_tb },
|
46
|
+
@snaps.values.map{ |s| s.closed_tb }
|
47
|
+
])
|
48
|
+
end
|
49
|
+
|
50
|
+
def max_cc
|
51
|
+
pick_max([@snaps.values.map{ |s| s.total_cc },
|
52
|
+
@snaps.values.map{ |s| s.new_cc },
|
53
|
+
@snaps.values.map{ |s| s.closed_cc }
|
54
|
+
])
|
55
|
+
end
|
56
|
+
|
57
|
+
def populate_series
|
58
|
+
return if first_snap.nil?
|
59
|
+
|
60
|
+
# Set up the 'ideal' series
|
61
|
+
ideal_slope = max_total.to_f / burndown_span.to_f
|
62
|
+
ideal_total = max_total
|
63
|
+
|
64
|
+
range_idx = 0
|
65
|
+
series_range.each do |date|
|
66
|
+
next if date.saturday? or date.sunday?
|
67
|
+
snapshot = date.strftime('%Y-%m-%d')
|
68
|
+
|
69
|
+
@series[:date] << snapshot
|
70
|
+
@series[:ideal] << ideal_total
|
71
|
+
ideal_total = ideal_total < ideal_slope ? 0 : ideal_total - ideal_slope
|
72
|
+
|
73
|
+
snapdata = @snaps.has_key?(snapshot) ? @snaps[snapshot] : nil
|
74
|
+
if date < first_snapdate
|
75
|
+
snapdata = @snaps[first_snap]
|
76
|
+
end
|
77
|
+
|
78
|
+
['bugs','tb','cc'].each do |set|
|
79
|
+
['total','new','closed'].each do |count|
|
80
|
+
set_key = "#{count}_#{set}".to_sym
|
81
|
+
@series[set_key] << (snapdata.nil? ? nil : snapdata.send(set_key))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
if range_idx % label_modulo == 0
|
86
|
+
@labels[range_idx] = date.strftime('%m/%d')
|
87
|
+
end
|
88
|
+
range_idx += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def series_range
|
93
|
+
if @release.uses_milestones?
|
94
|
+
return (@release.milestones.start.date..@release.milestones.ga.date)
|
95
|
+
elsif not @first_snapdate.nil? and not @latest_snapdate.nil?
|
96
|
+
return (@first_snapdate..@latest_snapdate)
|
97
|
+
end
|
98
|
+
return nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def series_span
|
102
|
+
if @release.uses_milestones?
|
103
|
+
return business_days_between(@release.milestones.start.date,@release.milestones.ga.date)
|
104
|
+
elsif not @first_snapdate.nil? and not @latest_snapdate.nil?
|
105
|
+
return business_days_between(@first_snapdate,@latest_snapdate)
|
106
|
+
end
|
107
|
+
return nil
|
108
|
+
end
|
109
|
+
|
110
|
+
def burndown_span
|
111
|
+
if @release.uses_milestones?
|
112
|
+
return business_days_between(@release.milestones.start.date,@release.milestones.code_freeze.date) - 2
|
113
|
+
elsif not @first_snapdate.nil? and not @latest_snapdate.nil?
|
114
|
+
return business_days_between(@first_snapdate,@latest_snapdate)
|
115
|
+
end
|
116
|
+
return nil
|
117
|
+
end
|
118
|
+
|
119
|
+
def bug_avg_age(blockers_only=false)
|
120
|
+
bug_list = blockers_only ? @bugs.values.select{ |b| b.test_blocker } : @bugs.values
|
121
|
+
bug_count = bug_list.length
|
122
|
+
age_total = bug_list.map{ |b| b.age }.sum
|
123
|
+
return bug_count == 0 ? 0 : (age_total / bug_count).round(1).to_s
|
124
|
+
end
|
125
|
+
|
126
|
+
def tb_avg_age
|
127
|
+
bug_avg_age(true)
|
128
|
+
end
|
129
|
+
|
130
|
+
def has_snapdata?(snapshot)
|
131
|
+
@snaps.has_key?(snapshot)
|
132
|
+
end
|
133
|
+
|
134
|
+
def get_snapdata(snapshot)
|
135
|
+
unless @snaps.has_key?(snapshot)
|
136
|
+
@snaps[snapshot] = Shiftzilla::SnapData.new(snapshot)
|
137
|
+
end
|
138
|
+
@snaps[snapshot]
|
139
|
+
end
|
140
|
+
|
141
|
+
def add_or_update_bug(bzid,binfo)
|
142
|
+
unless @bugs.has_key?(bzid)
|
143
|
+
@bugs[bzid] = Shiftzilla::Bug.new(bzid,binfo)
|
144
|
+
else
|
145
|
+
@bugs[bzid].update(binfo)
|
146
|
+
end
|
147
|
+
@bugs[bzid]
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
# Hat tip to https://stackoverflow.com/a/24753003 for this:
|
153
|
+
def business_days_between(start_date, end_date)
|
154
|
+
days_between = (end_date - start_date).to_i
|
155
|
+
return 0 unless days_between > 0
|
156
|
+
whole_weeks, extra_days = days_between.divmod(7)
|
157
|
+
unless extra_days.zero?
|
158
|
+
extra_days -= if (start_date + 1).wday <= end_date.wday
|
159
|
+
[(start_date + 1).sunday?, end_date.saturday?].count(true)
|
160
|
+
else
|
161
|
+
2
|
162
|
+
end
|
163
|
+
end
|
164
|
+
(whole_weeks * 5) + extra_days
|
165
|
+
end
|
166
|
+
|
167
|
+
def pick_max(series_list)
|
168
|
+
max_val = 0
|
169
|
+
series_list.each do |s|
|
170
|
+
smax = s.select{ |v| not v.nil? }.max
|
171
|
+
next if smax.nil?
|
172
|
+
next unless smax > max_val
|
173
|
+
max_val = smax
|
174
|
+
end
|
175
|
+
return max_val
|
176
|
+
end
|
177
|
+
|
178
|
+
def label_modulo
|
179
|
+
if series_span < 50
|
180
|
+
return 5
|
181
|
+
end
|
182
|
+
return 10
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Shiftzilla
|
2
|
+
class SnapData
|
3
|
+
attr_reader :id
|
4
|
+
attr_accessor :bug_ids, :tb_ids, :cc_ids, :new_bugs, :closed_bugs, :new_tb, :closed_tb, :new_cc, :closed_cc
|
5
|
+
|
6
|
+
def initialize(snapshot)
|
7
|
+
@id = snapshot
|
8
|
+
@bug_ids = []
|
9
|
+
@tb_ids = []
|
10
|
+
@cc_ids = []
|
11
|
+
@new_bugs = 0
|
12
|
+
@closed_bugs = 0
|
13
|
+
@new_tb = 0
|
14
|
+
@closed_tb = 0
|
15
|
+
@new_cc = 0
|
16
|
+
@closed_cc = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_bugs
|
20
|
+
@bug_ids.length
|
21
|
+
end
|
22
|
+
|
23
|
+
def total_tb
|
24
|
+
@tb_ids.length
|
25
|
+
end
|
26
|
+
|
27
|
+
def total_cc
|
28
|
+
@cc_ids.length
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'shiftzilla/helpers'
|
2
|
+
|
3
|
+
module Shiftzilla
|
4
|
+
class Source
|
5
|
+
attr_reader :id, :table
|
6
|
+
def initialize(qid,qinfo)
|
7
|
+
@id = qid.to_sym
|
8
|
+
@search = qinfo['search']
|
9
|
+
@sharer = qinfo['sharer']
|
10
|
+
@table = qinfo['table']
|
11
|
+
@external_sub = qinfo['external_sub']
|
12
|
+
@fields = qinfo['fields'].map{ |f| f.to_sym }
|
13
|
+
@external_bugs_idx = @fields.index(:external_bugs)
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_records_for_today?
|
17
|
+
count = 0
|
18
|
+
dbh.execute("SELECT count(*) FROM #{@table} WHERE Snapshot = date('now')") do |row|
|
19
|
+
count = row[0].to_i
|
20
|
+
end
|
21
|
+
return count > 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_records
|
25
|
+
output_format = @fields.map{ |fld| "%{#{fld.to_s}}" }.join("\x1F")
|
26
|
+
table_fields = @fields.map{ |fld| "\"#{field_map[fld]}\"" }.join(',')
|
27
|
+
insert_frame = @fields.map{ |fld| '?' }.join(', ')
|
28
|
+
bz_command = "bugzilla query --savedsearch #{@search} --savedsearch-sharer-id=#{@sharer} --outputformat='#{output_format}'"
|
29
|
+
bz_csv = `#{bz_command}`
|
30
|
+
row_count = 0
|
31
|
+
bz_csv.split("\n").each do |row|
|
32
|
+
values = row.split("\x1F")
|
33
|
+
if not @external_bugs_idx.nil?
|
34
|
+
if not @external_sub.nil? and values[@external_bugs_idx].include?(@external_sub)
|
35
|
+
values[@external_bugs_idx] = 1
|
36
|
+
else
|
37
|
+
values[@external_bugs_idx] = 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
dbh.execute("INSERT INTO #{@table} (#{table_fields}) VALUES (#{insert_frame})", values)
|
41
|
+
row_count += 1
|
42
|
+
end
|
43
|
+
dbh.execute("UPDATE #{@table} SET Snapshot = date('now') WHERE Snapshot ISNULL")
|
44
|
+
return row_count
|
45
|
+
end
|
46
|
+
|
47
|
+
def purge_records
|
48
|
+
dbh.execute("DELETE FROM #{@table} WHERE Snapshot == date('now') OR Snapshot ISNULL")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Shiftzilla
|
2
|
+
class Team
|
3
|
+
attr_reader :name, :lead, :group, :components
|
4
|
+
|
5
|
+
def initialize(tinfo,group_map,ad_hoc=false)
|
6
|
+
@name = tinfo['name']
|
7
|
+
@lead = ad_hoc ? nil : tinfo['lead']
|
8
|
+
@group = ad_hoc ? nil : group_map[tinfo['group']]
|
9
|
+
@components = tinfo.has_key?('components') ? tinfo['components'] : []
|
10
|
+
@ad_hoc = ad_hoc
|
11
|
+
end
|
12
|
+
|
13
|
+
def ad_hoc?
|
14
|
+
@ad_hoc
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|