shiftzilla 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|