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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 97d7153e749c49c28648b85cd0e2a9704d938b0a462b36d08d506458943d0441
|
4
|
+
data.tar.gz: 85af213bfdf7b6ae5c8854dbef2e5f6bd99233edc3f91a292397cf4de5e1f494
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2d00e6716bdffab9b6266021b95aafd6f5d38f809500a12a9f46bdbf9a3225ab5ab6d17cfa0ec61be8a2d138b7e527e7eddfbdb31779f346cc29c562050f6770
|
7
|
+
data.tar.gz: ac4499fd1bcf2f048672b528ab422494386f4a2ec18fb2cf0d6e9327d7f5af0ecff3b7bcc7f397b3f7d10ed41104c3207e60777cace213ba71c397ca81b1fedc
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Shiftzilla
|
2
|
+
a.k.a "The tool that we made because Bugzilla lacks any meaingful aggreation reporting."
|
3
|
+
|
4
|
+
This is a specialized tool for aggregating Bugzilla records in a way that is useful for some development teams. In order to use it:
|
5
|
+
|
6
|
+
1. This utility depends on a python-based tool called [python-bugzilla](https://pypi.python.org/pypi/python-bugzilla).
|
7
|
+
* Install it so that the `bugzilla` executable is in your $PATH
|
8
|
+
* Configure it by running `bugzilla login`
|
9
|
+
2. Next grab this utility from RubyGems:
|
10
|
+
* gem install shiftzilla
|
11
|
+
* Run any command (like `shiftzilla summary`) to have the utility set up your local $HOME/.shiftzilla directory
|
12
|
+
3. Edit $HOME/.shiftzilla/shiftzilla_cfg.yml to reflect the right organizational info for your teams and groups, plus the saved reports in Bugzilla that you want to draw data from. The utlity expects three tables:
|
13
|
+
* One for _all_ team bugs
|
14
|
+
* One for bugs filtered by the release that you are tracking
|
15
|
+
* One for bugs identified as test blockers by your QE team
|
16
|
+
|
17
|
+
With all of this done, you can start to run reports (or even set up cron jobs around them):
|
18
|
+
* `shiftzilla load` polls bugzilla and stores info in a local SQLite3 database
|
19
|
+
* `shiftzilla summary` gives you an overview report in your terminal
|
20
|
+
* `shiftzilla build` generates an overall and team-by-team reports that it will push to a web server as static web pages
|
data/bin/shiftzilla
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'shiftzilla/engine'
|
4
|
+
require 'shiftzilla/helpers'
|
5
|
+
require 'shiftzilla/version'
|
6
|
+
require 'trollop'
|
7
|
+
|
8
|
+
include Shiftzilla::Engine
|
9
|
+
include Shiftzilla::Helpers
|
10
|
+
|
11
|
+
SUB_COMMANDS = %w{help version load build purge triage}
|
12
|
+
Trollop::options do
|
13
|
+
version Shiftzilla::VERSION
|
14
|
+
banner <<-EOF
|
15
|
+
Usage:
|
16
|
+
#$0 <command>
|
17
|
+
|
18
|
+
Commands:
|
19
|
+
load
|
20
|
+
Queries Bugzilla and loads records into the local DB
|
21
|
+
build
|
22
|
+
Generates reports based local DB contents
|
23
|
+
purge
|
24
|
+
Removes today's records from the local DB
|
25
|
+
triage
|
26
|
+
Generate a report of teams that need to do bug triage
|
27
|
+
|
28
|
+
Options:
|
29
|
+
EOF
|
30
|
+
stop_on SUB_COMMANDS
|
31
|
+
end
|
32
|
+
|
33
|
+
cmd = ARGV.shift
|
34
|
+
cmd_opts = case cmd
|
35
|
+
when 'load'
|
36
|
+
Trollop::options do
|
37
|
+
banner <<-EOF
|
38
|
+
Usage:
|
39
|
+
#$0 load <options>
|
40
|
+
|
41
|
+
Description:
|
42
|
+
Runs all of the queries in the 'Queries' portion of the
|
43
|
+
config file, and stores info about each retrieved Bugzilla record
|
44
|
+
in a local database
|
45
|
+
|
46
|
+
Options:
|
47
|
+
EOF
|
48
|
+
end
|
49
|
+
when 'build'
|
50
|
+
Trollop::options do
|
51
|
+
banner <<-EOF
|
52
|
+
Usage:
|
53
|
+
#$0 build <options>
|
54
|
+
|
55
|
+
Description:
|
56
|
+
Performs aggregations and generates reports based on those,
|
57
|
+
which are rsynced to a target location.
|
58
|
+
|
59
|
+
Options:
|
60
|
+
EOF
|
61
|
+
opt :local_preview, "Don't publish, just show the report locally", :default => false
|
62
|
+
opt :quiet, "For cron use; don't print anything to STDOUT.", :default => false
|
63
|
+
end
|
64
|
+
when 'purge'
|
65
|
+
Trollop::options do
|
66
|
+
banner <<-EOF
|
67
|
+
Usage:
|
68
|
+
#$0 purge <options>
|
69
|
+
|
70
|
+
Description:
|
71
|
+
Purges today's data from the local database
|
72
|
+
|
73
|
+
Options:
|
74
|
+
EOF
|
75
|
+
end
|
76
|
+
when 'triage'
|
77
|
+
Trollop::options do
|
78
|
+
banner <<-EOF
|
79
|
+
Usage:
|
80
|
+
#$0 triage <options>
|
81
|
+
|
82
|
+
Description:
|
83
|
+
Generate a report of bugs with no target release,
|
84
|
+
ranked by team / component and age.
|
85
|
+
|
86
|
+
Options:
|
87
|
+
EOF
|
88
|
+
end
|
89
|
+
when 'help'
|
90
|
+
Trollop::educate
|
91
|
+
when 'version'
|
92
|
+
puts Shiftzilla::VERSION
|
93
|
+
exit 0
|
94
|
+
end
|
95
|
+
|
96
|
+
# Check for the config info
|
97
|
+
check_config
|
98
|
+
|
99
|
+
case cmd
|
100
|
+
when 'load'
|
101
|
+
load_records
|
102
|
+
when 'build'
|
103
|
+
build_reports(cmd_opts)
|
104
|
+
when 'purge'
|
105
|
+
purge_records
|
106
|
+
when 'triage'
|
107
|
+
triage_report
|
108
|
+
end
|
109
|
+
|
110
|
+
exit
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Shiftzilla
|
2
|
+
class Bug
|
3
|
+
attr_reader :id, :first_seen, :last_seen, :test_blocker, :ops_blocker, :owner, :component, :pm_score, :cust_cases, :tgt_release
|
4
|
+
|
5
|
+
def initialize(bzid,binfo)
|
6
|
+
@id = bzid
|
7
|
+
@first_seen = binfo[:snapdate]
|
8
|
+
@last_seen = binfo[:snapdate]
|
9
|
+
@test_blocker = binfo[:test_blocker]
|
10
|
+
@ops_blocker = binfo[:ops_blocker]
|
11
|
+
@owner = binfo[:owner]
|
12
|
+
@summary = binfo[:summary]
|
13
|
+
@component = binfo[:component]
|
14
|
+
@pm_score = binfo[:pm_score]
|
15
|
+
@cust_cases = binfo[:cust_cases]
|
16
|
+
@tgt_release = binfo[:tgt_release]
|
17
|
+
end
|
18
|
+
|
19
|
+
def update(binfo)
|
20
|
+
@last_seen = binfo[:snapdate]
|
21
|
+
@test_blocker = binfo[:test_blocker]
|
22
|
+
@ops_blocker = binfo[:ops_blocker]
|
23
|
+
@owner = binfo[:owner]
|
24
|
+
@summary = binfo[:summary]
|
25
|
+
@component = binfo[:component]
|
26
|
+
@pm_score = binfo[:pm_score]
|
27
|
+
@cust_cases = binfo[:cust_cases]
|
28
|
+
@tgt_release = binfo[:tgt_release]
|
29
|
+
end
|
30
|
+
|
31
|
+
def summary
|
32
|
+
@summary[0..30].gsub(/\s\w+\s*$/, '...')
|
33
|
+
end
|
34
|
+
|
35
|
+
def age
|
36
|
+
(@last_seen - @first_seen).to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
def semver
|
40
|
+
parts = @tgt_release.split('.')
|
41
|
+
if parts.length == 1
|
42
|
+
return @tgt_release
|
43
|
+
end
|
44
|
+
semver = ''
|
45
|
+
first_part = true
|
46
|
+
parts.each do |part|
|
47
|
+
unless is_number?(part)
|
48
|
+
semver += part
|
49
|
+
else
|
50
|
+
semver += ("%09d" % part).to_s
|
51
|
+
end
|
52
|
+
# A version like '3.z' gets a middle 0 for sort purposes.
|
53
|
+
if first_part and parts.length == 2
|
54
|
+
semver += ("%09d" % 0).to_s
|
55
|
+
end
|
56
|
+
first_part = false
|
57
|
+
end
|
58
|
+
return semver
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def is_number?(val)
|
64
|
+
val.to_f.to_s == val.to_s || val.to_i.to_s == val.to_s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'shiftzilla/group'
|
3
|
+
require 'shiftzilla/release'
|
4
|
+
require 'shiftzilla/source'
|
5
|
+
require 'shiftzilla/team'
|
6
|
+
|
7
|
+
module Shiftzilla
|
8
|
+
class Config
|
9
|
+
attr_reader :teams, :groups, :sources, :releases, :ssh
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@teams = []
|
13
|
+
@groups = []
|
14
|
+
@sources = []
|
15
|
+
group_map = {}
|
16
|
+
cfg_file['Groups'].each do |group|
|
17
|
+
gobj = Shiftzilla::Group.new(group)
|
18
|
+
@groups << gobj
|
19
|
+
group_map[gobj.id] = gobj
|
20
|
+
end
|
21
|
+
cfg_file['Teams'].each do |team|
|
22
|
+
@teams << Shiftzilla::Team.new(team,group_map)
|
23
|
+
end
|
24
|
+
cfg_file['Sources'].each do |sid,sinfo|
|
25
|
+
@sources << Shiftzilla::Source.new(sid,sinfo)
|
26
|
+
end
|
27
|
+
# Always track a release for bugs with no target release
|
28
|
+
@releases = [Shiftzilla::Release.new({ 'name' => '"---"', 'targets' => ['---'] },true)]
|
29
|
+
cfg_file['Releases'].each do |release|
|
30
|
+
@releases << Shiftzilla::Release.new(release)
|
31
|
+
end
|
32
|
+
@releases << Shiftzilla::Release.new({ 'name' => 'All', 'targets' => [] },true)
|
33
|
+
@ssh = {
|
34
|
+
:host => cfg_file['SSH']['host'],
|
35
|
+
:path => cfg_file['SSH']['path'],
|
36
|
+
:url => cfg_file['SSH']['url'],
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def earliest_milestone
|
41
|
+
milestone_boundaries[:earliest]
|
42
|
+
end
|
43
|
+
|
44
|
+
def latest_milestone
|
45
|
+
milestone_boundaries[:latest]
|
46
|
+
end
|
47
|
+
|
48
|
+
def team(tname)
|
49
|
+
@teams.select{ |t| t.name == tname }[0]
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_ad_hoc_team(tinfo)
|
53
|
+
@teams << Shiftzilla::Team.new(tinfo,{},true)
|
54
|
+
end
|
55
|
+
|
56
|
+
def release(rname)
|
57
|
+
@releases.select{ |r| r.name == rname }[0]
|
58
|
+
end
|
59
|
+
|
60
|
+
def release_by_target(tgt)
|
61
|
+
return target_map[tgt]
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def target_map
|
67
|
+
@target_map ||= begin
|
68
|
+
tmap = {}
|
69
|
+
@releases.each do |release|
|
70
|
+
release.targets.each do |target|
|
71
|
+
tmap[target] = release
|
72
|
+
end
|
73
|
+
end
|
74
|
+
tmap
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def milestone_boundaries
|
79
|
+
@milestone_boundaries ||= begin
|
80
|
+
boundaries = { :earliest => Date.today, :latest => (Date.today - 1800) }
|
81
|
+
@releases.each do |release|
|
82
|
+
next unless release.uses_milestones?
|
83
|
+
ms = release.milestones
|
84
|
+
[ms.start,ms.feature_complete,ms.code_freeze,ms.ga].each do |m|
|
85
|
+
next if m.date.nil?
|
86
|
+
if m.date < boundaries[:earliest]
|
87
|
+
boundaries[:earliest] = m.date
|
88
|
+
end
|
89
|
+
if m.date > boundaries[:latest]
|
90
|
+
boundaries[:latest] = m.date
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
boundaries
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'highline/import'
|
2
|
+
require 'shiftzilla/config'
|
3
|
+
require 'shiftzilla/helpers'
|
4
|
+
require 'shiftzilla/org_data'
|
5
|
+
require 'terminal-table'
|
6
|
+
require 'trollop'
|
7
|
+
|
8
|
+
include Shiftzilla::Helpers
|
9
|
+
|
10
|
+
module Shiftzilla
|
11
|
+
module Engine
|
12
|
+
def check_config
|
13
|
+
if not File.directory?(SZA_DIR)
|
14
|
+
choose do |menu|
|
15
|
+
menu.header = 'You don\'t have a Shiftzilla config directory at $HOME/.shiftzilla. Should I create one?'
|
16
|
+
menu.prompt = 'Choice?'
|
17
|
+
menu.choice(:yes) {
|
18
|
+
say('Okay. Creating config directory.')
|
19
|
+
Dir.mkdir(SZA_DIR)
|
20
|
+
Dir.mkdir(ARCH_DIR)
|
21
|
+
}
|
22
|
+
menu.choice(:no) {
|
23
|
+
say('Okay. Exiting Shiftzilla.')
|
24
|
+
exit
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
if not File.directory?(ARCH_DIR)
|
29
|
+
puts 'You don\'t have an archive directory at $HOME/.shiftzilla/archive. Creating it.'
|
30
|
+
Dir.mkdir(ARCH_DIR)
|
31
|
+
end
|
32
|
+
if not File.exists?(CFG_FILE)
|
33
|
+
choose do |menu|
|
34
|
+
menu.header = "\nYou don't have a shiftzilla_cfg.yml file in $HOME/.shiftzilla. Should I create one?"
|
35
|
+
menu.prompt = 'Choice?'
|
36
|
+
menu.choice(:yes) {
|
37
|
+
say('Okay. Creating shiftzilla_cfg.yml')
|
38
|
+
FileUtils.cp(CFG_TMPL,CFG_FILE)
|
39
|
+
}
|
40
|
+
menu.choice(:no) {
|
41
|
+
say('Okay. Exiting Shiftzilla.')
|
42
|
+
exit
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
if not File.exists?(DB_FPATH)
|
47
|
+
choose do |menu|
|
48
|
+
menu.header = "\nYou don't have a shiftzilla.sqlite file in $HOME/.shiftzilla.\nI can create it for you, but it is very important for you to\nconfigure Shiftzilla by puttng the proper settings in\n$HOME/.shiftzilla/shiftzilla_cfg.yml first. Do you want me to proceed with creating the database?"
|
49
|
+
menu.prompt = 'Choice?'
|
50
|
+
menu.choice(:yes) {
|
51
|
+
say('Okay. Creating shiftzilla.sqlite')
|
52
|
+
sql_tmpl = File.read(SQL_TMPL)
|
53
|
+
tgt_rel_clause = release_clause(releases)
|
54
|
+
sql_tmpl.gsub! '$RELEASE_CLAUSE', tgt_rel_clause
|
55
|
+
dbh.execute_batch(sql_tmpl)
|
56
|
+
dbh.close
|
57
|
+
exit
|
58
|
+
}
|
59
|
+
menu.choice(:no) {
|
60
|
+
say('Okay. Exiting Shiftzilla.')
|
61
|
+
exit
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def load_records
|
68
|
+
sources.each do |s|
|
69
|
+
if s.has_records_for_today?
|
70
|
+
puts "Skipping query for #{s.id}; it already has records for today."
|
71
|
+
else
|
72
|
+
backup_db
|
73
|
+
puts "Querying bugzilla for #{s.id}"
|
74
|
+
added_count = s.load_records
|
75
|
+
puts "Added #{added_count} records to #{s.table}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def purge_records
|
81
|
+
sources.each do |s|
|
82
|
+
s.purge_records
|
83
|
+
end
|
84
|
+
puts "Purged #{sources.length} tables."
|
85
|
+
end
|
86
|
+
|
87
|
+
def triage_report
|
88
|
+
org_data = Shiftzilla::OrgData.new(shiftzilla_config)
|
89
|
+
org_data.populate_releases
|
90
|
+
teams = org_data.get_ordered_teams
|
91
|
+
no_tgt_rel = shiftzilla_config.releases[0]
|
92
|
+
|
93
|
+
teams.each do |tname|
|
94
|
+
next if tname == '_overall'
|
95
|
+
rdata = org_data.get_release_data(tname,no_tgt_rel)
|
96
|
+
next if rdata.nil? or rdata.snaps.empty?
|
97
|
+
recipients = {}
|
98
|
+
team = shiftzilla_config.team(tname)
|
99
|
+
unless team.nil? or team.ad_hoc?
|
100
|
+
recipients[team.lead] = 1
|
101
|
+
recipients[team.group.lead] = 1
|
102
|
+
end
|
103
|
+
bzids = rdata.snaps.has_key?(latest_snapshot) ? rdata.snaps[latest_snapshot].bug_ids : []
|
104
|
+
next if bzids.length == 0
|
105
|
+
bugs = rdata.bugs
|
106
|
+
table = Terminal::Table.new do |t|
|
107
|
+
t << ['URL','Age','Component','Owner','Summary']
|
108
|
+
t << :separator
|
109
|
+
bzids.sort_by{ |b| [bugs[b].first_seen,bugs[b].component] }.each do |bzid|
|
110
|
+
bug = bugs[bzid]
|
111
|
+
if team.nil?
|
112
|
+
recipients[bug.owner] = 1
|
113
|
+
end
|
114
|
+
t << [
|
115
|
+
bug_url(bzid),
|
116
|
+
bug.age,
|
117
|
+
bug.component,
|
118
|
+
bug.owner,
|
119
|
+
bug.summary
|
120
|
+
]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
puts "#{tname}#{team.nil? ? ' Component' : ' Team'} - #{bzids.length} bugs with no Target Release"
|
124
|
+
puts "To: #{recipients.keys.sort.join(',')}"
|
125
|
+
puts "#{table}\n\n"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def build_reports(options)
|
130
|
+
org_data = Shiftzilla::OrgData.new(shiftzilla_config)
|
131
|
+
org_data.populate_releases
|
132
|
+
org_data.build_series
|
133
|
+
org_data.generate_reports
|
134
|
+
if options[:local_preview]
|
135
|
+
org_data.show_local_reports
|
136
|
+
else
|
137
|
+
org_data.publish_reports(ssh)
|
138
|
+
system("rm -rf #{org_data.tmp_dir}")
|
139
|
+
unless options[:quiet]
|
140
|
+
system("open #{ssh[:url]}")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def shiftzilla_config
|
148
|
+
@shiftzilla_config ||= Shiftzilla::Config.new
|
149
|
+
end
|
150
|
+
|
151
|
+
def teams
|
152
|
+
@teams ||= shiftzilla_config.teams
|
153
|
+
end
|
154
|
+
|
155
|
+
def groups
|
156
|
+
@groups ||= shiftzilla_config.groups
|
157
|
+
end
|
158
|
+
|
159
|
+
def sources
|
160
|
+
@sources ||= shiftzilla_config.sources
|
161
|
+
end
|
162
|
+
|
163
|
+
def releases
|
164
|
+
@releases ||= shiftzilla_config.releases
|
165
|
+
end
|
166
|
+
|
167
|
+
def ssh
|
168
|
+
@ssh ||= shiftzilla_config.ssh
|
169
|
+
end
|
170
|
+
|
171
|
+
def component_team_map
|
172
|
+
@component_team_map ||= begin
|
173
|
+
ctm = {}
|
174
|
+
teams.each do |team|
|
175
|
+
team.components.each do |component|
|
176
|
+
ctm[component] = team
|
177
|
+
end
|
178
|
+
end
|
179
|
+
ctm
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'haml'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'sqlite3'
|
6
|
+
require 'tmpdir'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
module Shiftzilla
|
10
|
+
module Helpers
|
11
|
+
BZ_URL = 'https://bugzilla.redhat.com/show_bug.cgi?id='
|
12
|
+
SZA_DIR = File.join(ENV['HOME'],'.shiftzilla')
|
13
|
+
ARCH_DIR = File.join(SZA_DIR,'archive')
|
14
|
+
CFG_FILE = File.join(SZA_DIR,'shiftzilla_cfg.yml')
|
15
|
+
DB_FNAME = 'shiftzilla.sqlite'
|
16
|
+
DB_FPATH = File.join(SZA_DIR,DB_FNAME)
|
17
|
+
THIS_PATH = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
18
|
+
HAML_TMPL = File.expand_path(File.join(File.dirname(THIS_PATH), '../../template.haml'))
|
19
|
+
CFG_TMPL = File.expand_path(File.join(File.dirname(THIS_PATH), '../../shiftzilla_cfg.yml.tmpl'))
|
20
|
+
SQL_TMPL = File.expand_path(File.join(File.dirname(THIS_PATH), '../../shiftzilla.sql.tmpl'))
|
21
|
+
VENDOR_DIR = File.expand_path(File.join(File.dirname(THIS_PATH), '../../vendor'))
|
22
|
+
|
23
|
+
GRAPH_DIMENSIONS = '800x400'
|
24
|
+
GRAPH_THEME = {
|
25
|
+
:colors => [
|
26
|
+
'#268bd2', # Blue
|
27
|
+
'#cb4b16', # Orange
|
28
|
+
'#859900', # Green
|
29
|
+
'#2aa198', # Cyan
|
30
|
+
'#d33682', # Magenta
|
31
|
+
'#6c71c4', # Violet
|
32
|
+
'#b58900', # Yellow
|
33
|
+
'#dc322f', # Red
|
34
|
+
],
|
35
|
+
:marker_color => '#93a1a1', # Base1
|
36
|
+
:font_color => '#586e75', # Base01
|
37
|
+
:background_colors => '#fdf6e3', # Base3
|
38
|
+
:background_image => nil,
|
39
|
+
}
|
40
|
+
|
41
|
+
def tmp_dir
|
42
|
+
@tmp_dir ||= Dir.mktmpdir('shiftzilla-reports-')
|
43
|
+
end
|
44
|
+
|
45
|
+
def cfg_file
|
46
|
+
@cfg_file ||= YAML.load_file(CFG_FILE)
|
47
|
+
end
|
48
|
+
|
49
|
+
def dbh
|
50
|
+
@dbh ||= SQLite3::Database.new(DB_FPATH)
|
51
|
+
end
|
52
|
+
|
53
|
+
def haml_engine
|
54
|
+
@haml_engine ||= Haml::Engine.new(File.read(HAML_TMPL))
|
55
|
+
end
|
56
|
+
|
57
|
+
def new_graph(labels,max_y)
|
58
|
+
g = Gruff::Line.new(GRAPH_DIMENSIONS)
|
59
|
+
g.theme = GRAPH_THEME
|
60
|
+
g.line_width = 2
|
61
|
+
g.labels = labels
|
62
|
+
g.y_axis_increment = set_axis_increment(max_y)
|
63
|
+
g.hide_dots = true
|
64
|
+
return g
|
65
|
+
end
|
66
|
+
|
67
|
+
def backup_db
|
68
|
+
unless db_backed_up
|
69
|
+
today = Date.today.strftime('%Y-%m-%d')
|
70
|
+
tpath = File.join(ARCH_DIR,today)
|
71
|
+
unless Dir.exists?(tpath)
|
72
|
+
Dir.mkdir(tpath)
|
73
|
+
end
|
74
|
+
apath = ''
|
75
|
+
copy_idx = 0
|
76
|
+
loop do
|
77
|
+
copynum = "%02d" % copy_idx
|
78
|
+
apath = File.join(tpath,"#{copynum}-#{DB_FNAME}")
|
79
|
+
break unless File.exists?(apath)
|
80
|
+
copy_idx += 1
|
81
|
+
end
|
82
|
+
FileUtils.cp DB_FPATH, apath
|
83
|
+
puts "Backed up the database."
|
84
|
+
@db_backed_up = true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def timestamp
|
89
|
+
DateTime.now.to_s
|
90
|
+
end
|
91
|
+
|
92
|
+
def bug_url(bug_id)
|
93
|
+
return "#{BZ_URL}#{bug_id}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def set_axis_increment(initial_value)
|
97
|
+
case
|
98
|
+
when initial_value < 10
|
99
|
+
return 1
|
100
|
+
when initial_value < 20
|
101
|
+
return 2
|
102
|
+
when initial_value < 50
|
103
|
+
return 5
|
104
|
+
when initial_value < 100
|
105
|
+
return 10
|
106
|
+
when initial_value < 200
|
107
|
+
return 20
|
108
|
+
when initial_value < 400
|
109
|
+
return 25
|
110
|
+
else
|
111
|
+
return 100
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def all_bugs_query(snapshot)
|
116
|
+
return "SELECT AB.'Bug ID', AB.'Component', AB.'Target Release', AB.'Assignee', AB.'Status', AB.'Summary', AB.'Keywords', AB.'PM Score', AB.'External Bugs' FROM ALL_BUGS AB WHERE AB.'Snapshot' = date('#{snapshot}')"
|
117
|
+
end
|
118
|
+
|
119
|
+
def component_bugs_query(components,snapshot)
|
120
|
+
rclause = list_clause(components)
|
121
|
+
rfilter = rclause == '' ? '' : " AND AB.'Component' #{rclause}"
|
122
|
+
return "SELECT AB.'Bug ID', AB.'Component', AB.'Target Release', AB.'Assignee', AB.'Status', AB.'Summary', AB.'Keywords', AB.'PM Score', AB.'External Bugs' FROM ALL_BUGS AB WHERE AB.'Snapshot' = date('#{snapshot}')#{rfilter} ORDER BY AB.'Target Release' DESC"
|
123
|
+
end
|
124
|
+
|
125
|
+
def component_bugs_count(components,snapshot)
|
126
|
+
rclause = list_clause(components)
|
127
|
+
rfilter = rclause == '' ? '' : " AND AB.'Component' #{rclause}"
|
128
|
+
return "SELECT count(*) FROM ALL_BUGS AB WHERE AB.'Snapshot' = date('#{snapshot}')#{rfilter}"
|
129
|
+
end
|
130
|
+
|
131
|
+
def all_snapshots
|
132
|
+
@all_snapshots ||= begin
|
133
|
+
all_snapshots = []
|
134
|
+
dbh.execute('SELECT DISTINCT Snapshot FROM ALL_BUGS ORDER BY Snapshot ASC') do |row|
|
135
|
+
all_snapshots << row[0]
|
136
|
+
end
|
137
|
+
all_snapshots
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def latest_snapshot
|
142
|
+
all_snapshots[-1]
|
143
|
+
end
|
144
|
+
|
145
|
+
def field_map
|
146
|
+
{
|
147
|
+
:assigned_to => 'Assignee',
|
148
|
+
:component => 'Component',
|
149
|
+
:id => 'Bug ID',
|
150
|
+
:keywords => 'Keywords',
|
151
|
+
:cf_pm_score => 'PM Score',
|
152
|
+
:status => 'Status',
|
153
|
+
:summary => 'Summary',
|
154
|
+
:target_release => 'Target Release',
|
155
|
+
:external_bugs => 'External Bugs',
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def list_clause(list)
|
162
|
+
return '' if list.length == 0
|
163
|
+
if list.length > 1
|
164
|
+
stmt = list.map{ |t| "'#{t}'" }.join(',')
|
165
|
+
return "IN (#{stmt})"
|
166
|
+
end
|
167
|
+
return "== '#{list[0]}'"
|
168
|
+
end
|
169
|
+
|
170
|
+
def db_backed_up
|
171
|
+
@db_backed_up ||= false
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Shiftzilla
|
2
|
+
class Milestone
|
3
|
+
def initialize(mtxt)
|
4
|
+
@date = nil
|
5
|
+
@stamp = ''
|
6
|
+
unless (mtxt.nil? or mtxt == '')
|
7
|
+
@date = Date.parse(mtxt)
|
8
|
+
@stamp = mtxt
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def date
|
13
|
+
@date
|
14
|
+
end
|
15
|
+
|
16
|
+
def stamp
|
17
|
+
@stamp
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|