ruby-bugzilla 0.3.2 → 0.3.3
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.
- data/README.rdoc +1 -1
- data/bin/bzconsole +25 -11
- data/bin/bzconsole~ +750 -0
- data/lib/bugzilla.rb +15 -12
- data/lib/bugzilla.rb~ +788 -0
- data/lib/ruby-bugzilla/rhbugzilla.rb +1 -1
- data/lib/ruby-bugzilla/rhbugzilla.rb~ +60 -0
- metadata +59 -30
- data/Rakefile +0 -55
- data/spec/ruby-bugzilla_spec.rb +0 -7
- data/spec/spec_helper.rb +0 -9
data/README.rdoc
CHANGED
data/bin/bzconsole
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#! /usr/bin/env ruby
|
|
2
2
|
# bzconsole.rb
|
|
3
|
-
# Copyright (C) 2010 Red Hat, Inc.
|
|
3
|
+
# Copyright (C) 2010-2012 Red Hat, Inc.
|
|
4
4
|
#
|
|
5
5
|
# Authors:
|
|
6
6
|
# Akira TAGOH <tagoh@redhat.com>
|
|
@@ -29,16 +29,15 @@ require 'gruff'
|
|
|
29
29
|
require 'uri'
|
|
30
30
|
require 'highline/import'
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
$KCODE = 'u'
|
|
34
|
-
|
|
35
32
|
begin
|
|
36
33
|
require 'bugzilla'
|
|
37
34
|
rescue LoadError
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
$:.push(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
36
|
+
require 'bugzilla'
|
|
40
37
|
end
|
|
41
38
|
|
|
39
|
+
$KCODE = 'u'
|
|
40
|
+
|
|
42
41
|
module BzConsole
|
|
43
42
|
|
|
44
43
|
class CommandTemplate
|
|
@@ -94,6 +93,16 @@ module BzConsole
|
|
|
94
93
|
File.open(fname, "w") {|f| f.chmod(0600); f.write(conf.to_yaml)}
|
|
95
94
|
end # def save_config
|
|
96
95
|
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def get_proxy(info)
|
|
99
|
+
uri = info[:Proxy] || ENV["http_proxy"]
|
|
100
|
+
proxy_uri = uri.nil? ? nil : URI.parse(uri)
|
|
101
|
+
proxy_host = proxy_uri.nil? ? nil : proxy_uri.host
|
|
102
|
+
proxy_port = proxy_uri.nil? ? nil : proxy_uri.port
|
|
103
|
+
return proxy_host, proxy_port
|
|
104
|
+
end
|
|
105
|
+
|
|
97
106
|
end # class CommandTemplate
|
|
98
107
|
|
|
99
108
|
class Setup < CommandTemplate
|
|
@@ -200,8 +209,9 @@ module BzConsole
|
|
|
200
209
|
path = uri.path.empty? ? nil : uri.path
|
|
201
210
|
login = info[:User].nil? ? ask("Bugzilla ID: ") : info[:User]
|
|
202
211
|
pass = info[:Password].nil? ? ask("Bugzilla password: ") {|q| q.echo = false} : info[:Password]
|
|
212
|
+
proxy_host, proxy_port = get_proxy(info)
|
|
203
213
|
|
|
204
|
-
xmlrpc = Bugzilla::XMLRPC.new(host, port, path)
|
|
214
|
+
xmlrpc = Bugzilla::XMLRPC.new(host, port, path, proxy_host, proxy_port)
|
|
205
215
|
user = Bugzilla::User.new(xmlrpc)
|
|
206
216
|
begin
|
|
207
217
|
result = user.login({'login'=>login, 'password'=>pass, 'remember'=>true})
|
|
@@ -298,10 +308,11 @@ module BzConsole
|
|
|
298
308
|
path = uri.path.empty? ? nil : uri.path
|
|
299
309
|
login = opts[:command][:anonymous] == true ? nil : info[:User]
|
|
300
310
|
pass = opts[:command][:anonymous] == true ? nil : info[:Password]
|
|
311
|
+
proxy_host, proxy_port = get_proxy(info)
|
|
301
312
|
|
|
302
313
|
@plugin.run(:pre, host, :getbug, opts)
|
|
303
314
|
|
|
304
|
-
xmlrpc = Bugzilla::XMLRPC.new(host, port, path)
|
|
315
|
+
xmlrpc = Bugzilla::XMLRPC.new(host, port, path, proxy_host, proxy_port)
|
|
305
316
|
user = Bugzilla::User.new(xmlrpc)
|
|
306
317
|
user.session(login, pass) do
|
|
307
318
|
bug = Bugzilla::Bug.new(xmlrpc)
|
|
@@ -410,10 +421,11 @@ module BzConsole
|
|
|
410
421
|
path = uri.path.empty? ? nil : uri.path
|
|
411
422
|
login = opts[:command][:anonymous] == true ? nil : info[:User]
|
|
412
423
|
pass = opts[:command][:anonymous] == true ? nil : info[:Password]
|
|
424
|
+
proxy_host, proxy_port = get_proxy(info)
|
|
413
425
|
|
|
414
426
|
@plugin.run(:pre, host, :search, opts[:command][:query])
|
|
415
427
|
|
|
416
|
-
xmlrpc = Bugzilla::XMLRPC.new(host, port, path)
|
|
428
|
+
xmlrpc = Bugzilla::XMLRPC.new(host, port, path, proxy_host, proxy_port)
|
|
417
429
|
user = Bugzilla::User.new(xmlrpc)
|
|
418
430
|
user.session(login, pass) do
|
|
419
431
|
bug = Bugzilla::Bug.new(xmlrpc)
|
|
@@ -487,10 +499,11 @@ module BzConsole
|
|
|
487
499
|
path = uri.path.empty? ? nil : uri.path
|
|
488
500
|
login = opts[:command][:anonymous] == true ? nil : info[:User]
|
|
489
501
|
pass = opts[:command][:anonymous] == true ? nil : info[:Password]
|
|
502
|
+
proxy_host, proxy_port = get_proxy(info)
|
|
490
503
|
|
|
491
504
|
@plugin.run(:pre, host, :show, opts)
|
|
492
505
|
|
|
493
|
-
xmlrpc = Bugzilla::XMLRPC.new(host, port, path)
|
|
506
|
+
xmlrpc = Bugzilla::XMLRPC.new(host, port, path, proxy_host, proxy_port)
|
|
494
507
|
user = Bugzilla::User.new(xmlrpc)
|
|
495
508
|
user.session(login, pass) do
|
|
496
509
|
if opts[:command][:show][:mode] == :field then
|
|
@@ -651,8 +664,9 @@ module BzConsole
|
|
|
651
664
|
path = uri.path.empty? ? nil : uri.path
|
|
652
665
|
login = opts[:command][:anonymous] == true ? nil : info[:User]
|
|
653
666
|
pass = opts[:command][:anonymous] == true ? nil : info[:Password]
|
|
667
|
+
proxy_host, proxy_port = get_proxy(info)
|
|
654
668
|
|
|
655
|
-
xmlrpc = Bugzilla::XMLRPC.new(host, port, path)
|
|
669
|
+
xmlrpc = Bugzilla::XMLRPC.new(host, port, path, proxy_host, proxy_port)
|
|
656
670
|
user = Bugzilla::User.new(xmlrpc)
|
|
657
671
|
user.session(login, pass) do
|
|
658
672
|
bug = Bugzilla::Bug.new(xmlrpc)
|
data/bin/bzconsole~
ADDED
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
#! /usr/bin/env ruby
|
|
2
|
+
# bzconsole.rb
|
|
3
|
+
# Copyright (C) 2010 Red Hat, Inc.
|
|
4
|
+
#
|
|
5
|
+
# Authors:
|
|
6
|
+
# Akira TAGOH <tagoh@redhat.com>
|
|
7
|
+
#
|
|
8
|
+
# This library is free software; you can redistribute it and/or
|
|
9
|
+
# modify it under the terms of the GNU Lesser General Public
|
|
10
|
+
# License as published by the Free Software Foundation; either
|
|
11
|
+
# version 2 of the License, or (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# This library is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
16
|
+
# Lesser General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU Lesser General Public
|
|
19
|
+
# License along with this library; if not, write to the
|
|
20
|
+
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
21
|
+
# Boston, MA 02111-1307, USA.
|
|
22
|
+
|
|
23
|
+
require 'optparse'
|
|
24
|
+
require 'time'
|
|
25
|
+
require 'yaml'
|
|
26
|
+
require 'rubygems'
|
|
27
|
+
require 'pp'
|
|
28
|
+
require 'gruff'
|
|
29
|
+
require 'uri'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
$KCODE = 'u'
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
require 'bugzilla'
|
|
36
|
+
rescue LoadError
|
|
37
|
+
require File.join(File.dirname(__FILE__), "..", "lib", "bugzilla.rb")
|
|
38
|
+
$:.push(File.join(File.dirname(__FILE__), "..", "lib"))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
module BzConsole
|
|
42
|
+
|
|
43
|
+
class CommandTemplate
|
|
44
|
+
|
|
45
|
+
def initialize(plugin)
|
|
46
|
+
@n_args = 0
|
|
47
|
+
@defaultyamlfile = File.join(ENV['HOME'], ".bzconsole.yml")
|
|
48
|
+
@plugin = plugin
|
|
49
|
+
end # def initialize
|
|
50
|
+
|
|
51
|
+
attr_reader :n_args
|
|
52
|
+
|
|
53
|
+
def parse(parser, argv, opts)
|
|
54
|
+
raise RuntimeError, sprintf("No implementation for %s", self.class) if self.class =~ /CommandTemplate/
|
|
55
|
+
|
|
56
|
+
parser.on('-h', '--help', 'show this message') {|x| opts[:help] = true}
|
|
57
|
+
|
|
58
|
+
read_config(opts)
|
|
59
|
+
@plugin.run(:parser, nil, parser, argv, opts)
|
|
60
|
+
|
|
61
|
+
parser.order(argv)
|
|
62
|
+
end # def parse
|
|
63
|
+
|
|
64
|
+
def read_config(opts)
|
|
65
|
+
fname = opts[:config].nil? ? @defaultyamlfile : opts[:config]
|
|
66
|
+
begin
|
|
67
|
+
conf = YAML.load(File.open(fname).read)
|
|
68
|
+
rescue Errno::ENOENT
|
|
69
|
+
conf = {}
|
|
70
|
+
end
|
|
71
|
+
conf.each do |k, v|
|
|
72
|
+
if v.include?(:Plugin) then
|
|
73
|
+
load(v[:Plugin])
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
conf
|
|
77
|
+
end # def read_config
|
|
78
|
+
|
|
79
|
+
def do(argv)
|
|
80
|
+
raise RuntimeError, sprintf("No implementation for %s", self.class)
|
|
81
|
+
end # def do
|
|
82
|
+
|
|
83
|
+
end # class CommandTemplate
|
|
84
|
+
|
|
85
|
+
class Setup < CommandTemplate
|
|
86
|
+
|
|
87
|
+
def initialize(plugin)
|
|
88
|
+
super
|
|
89
|
+
|
|
90
|
+
@n_args = 0
|
|
91
|
+
end # def initialize
|
|
92
|
+
|
|
93
|
+
def parse(parser, argv, opts)
|
|
94
|
+
parser.banner = sprintf("Usage: %s [global options] setup", File.basename(__FILE__))
|
|
95
|
+
parser.separator ""
|
|
96
|
+
parser.separator "Command options:"
|
|
97
|
+
|
|
98
|
+
super
|
|
99
|
+
end # def parse
|
|
100
|
+
|
|
101
|
+
def do(argv, opts)
|
|
102
|
+
template = {
|
|
103
|
+
"rhbz"=>{
|
|
104
|
+
:Name=>"Red Hat Bugzilla",
|
|
105
|
+
:URL=>"https://bugzilla.redhat.com",
|
|
106
|
+
:User=>"account@example.com",
|
|
107
|
+
:Password=>"blahblahblah",
|
|
108
|
+
:ProductAliases=>{
|
|
109
|
+
'RHEL3'=>'Red Hat Enterprise Linux 3',
|
|
110
|
+
'RHEL4'=>'Red Hat Enterprise Linux 4',
|
|
111
|
+
'RHEL5'=>'Red Hat Enterprise Linux 5',
|
|
112
|
+
'RHEL6'=>'Red Hat Enterprise Linux 6'
|
|
113
|
+
},
|
|
114
|
+
:Plugin=>"ruby-bugzilla/rhbugzilla.rb"
|
|
115
|
+
},
|
|
116
|
+
"gnbz"=>{
|
|
117
|
+
:Name=>"GNOME Bugzilla",
|
|
118
|
+
:URL=>"https://bugzilla.gnome.org",
|
|
119
|
+
:User=>"account@example.com",
|
|
120
|
+
:Password=>"blahblahblah",
|
|
121
|
+
},
|
|
122
|
+
"fdobz"=>{
|
|
123
|
+
:Name=>"FreeDesktop Bugzilla",
|
|
124
|
+
:URL=>"https://bugs.freedesktop.org",
|
|
125
|
+
:User=>"account@example.com",
|
|
126
|
+
:Password=>"blahblahblah",
|
|
127
|
+
},
|
|
128
|
+
"mzbz"=>{
|
|
129
|
+
:Name=>"Mozilla Bugzilla",
|
|
130
|
+
:URL=>"https://bugzilla.mozilla.org",
|
|
131
|
+
:User=>"account@example.com",
|
|
132
|
+
:Password=>"blahblahblah",
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
template.each do |k, v|
|
|
137
|
+
@plugin.run(:pre, v[:URL].sub(/\Ahttps?:\/\//, '').sub(/\/\Z/, ''), :setup, v)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
fname = opts[:config].nil? ? @defaultyamlfile : opts[:config]
|
|
141
|
+
if File.exist?(fname) then
|
|
142
|
+
raise RuntimeError, ".bzconsole.yml already exists. please remove it first to proceed to the next step."
|
|
143
|
+
end
|
|
144
|
+
File.open(fname, File::CREAT|File::WRONLY, 0600) do |f|
|
|
145
|
+
f.write(template.to_yaml)
|
|
146
|
+
end
|
|
147
|
+
printf("%s has been created. please modify your account information before operating.\n", fname)
|
|
148
|
+
end # def do
|
|
149
|
+
|
|
150
|
+
end # class Setup
|
|
151
|
+
|
|
152
|
+
class Getbug < CommandTemplate
|
|
153
|
+
|
|
154
|
+
def initialize(plugin)
|
|
155
|
+
super
|
|
156
|
+
|
|
157
|
+
@n_args = 1
|
|
158
|
+
end # def initialize
|
|
159
|
+
|
|
160
|
+
def parse(parser, argv, opts)
|
|
161
|
+
parser.banner = sprintf("Usage: %s [global options] getbug [command options] <prefix:bug number> ...", File.basename(__FILE__))
|
|
162
|
+
parser.separator ""
|
|
163
|
+
parser.separator "Command options:"
|
|
164
|
+
parser.on('-s', '--summary', 'Displays bugs summary only') {opts[:summary] = true}
|
|
165
|
+
parser.on('-d', '--details', 'Displays detailed bugs information') {opts[:details] = true}
|
|
166
|
+
parser.on('-a', '--all', 'Displays the whole data in bugs') {opts[:all] = true}
|
|
167
|
+
parser.on('--anonymous', 'Access to Bugzilla anonymously') {opts[:anonymous] = true}
|
|
168
|
+
|
|
169
|
+
super
|
|
170
|
+
end # def parse
|
|
171
|
+
|
|
172
|
+
def do(argv, opts)
|
|
173
|
+
real_do(argv, opts) do |result|
|
|
174
|
+
if opts[:command][:summary] == true then
|
|
175
|
+
printf("Bug#%s, %s, %s[%s, %s] %s\n",
|
|
176
|
+
result['id'],
|
|
177
|
+
result['product'],
|
|
178
|
+
result['component'],
|
|
179
|
+
result['status'],
|
|
180
|
+
result['severity'],
|
|
181
|
+
result['summary'])
|
|
182
|
+
elsif opts[:command][:details] == true then
|
|
183
|
+
printf("Bug#%s, %s, %s, %s[%s, %s, %s, %s] %s\n",
|
|
184
|
+
result['id'],
|
|
185
|
+
result['product'],
|
|
186
|
+
result['assigned_to'],
|
|
187
|
+
result['component'],
|
|
188
|
+
result['status'],
|
|
189
|
+
result['resolution'],
|
|
190
|
+
result['priority'],
|
|
191
|
+
result['severity'],
|
|
192
|
+
result['summary'])
|
|
193
|
+
else
|
|
194
|
+
printf("Bug#%s - %s\n", result['id'], result['summary'])
|
|
195
|
+
printf("Status:\t\t%s%s\n", result['status'], !result['resolution'].empty? ? sprintf("[%s]", result['resolution']) : "")
|
|
196
|
+
printf("Product:\t%s\n", result['product'])
|
|
197
|
+
printf("Component:\t%s\n", result['component'])
|
|
198
|
+
printf("Assinged To:\t%s\n", result['assigned_to'])
|
|
199
|
+
printf("Priority:\t%s\n", result['priority'])
|
|
200
|
+
printf("Severity:\t%s\n", result['severity'])
|
|
201
|
+
printf("Comments:\t%d\n\n", result['comments'].length)
|
|
202
|
+
i = 0
|
|
203
|
+
result['comments'].each do |c|
|
|
204
|
+
printf("Comment#%d%s %s %s\n", i, c['is_private'] == true ? "[private]" : "", c['author'], c['time'].to_time)
|
|
205
|
+
printf("\n %s\n\n", c['text'].split("\n").join("\n "))
|
|
206
|
+
i += 1
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end # def do
|
|
211
|
+
|
|
212
|
+
private
|
|
213
|
+
|
|
214
|
+
def real_do(argv, opts)
|
|
215
|
+
conf = read_config(opts)
|
|
216
|
+
conf.freeze
|
|
217
|
+
argv.each do |bugn|
|
|
218
|
+
bugn =~ /(.*):(.*)/
|
|
219
|
+
prefix = $1
|
|
220
|
+
nnn = $2
|
|
221
|
+
if prefix.nil? then
|
|
222
|
+
raise ArgumentError, sprintf("No prefix specified for Bug#%s", bugn)
|
|
223
|
+
end
|
|
224
|
+
unless conf.include?(prefix) then
|
|
225
|
+
raise RuntimeError, sprintf("No host information for %s", prefix)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
info = conf[prefix]
|
|
229
|
+
uri = URI.parse(info[:URL])
|
|
230
|
+
host = uri.host
|
|
231
|
+
port = uri.port
|
|
232
|
+
path = uri.path.empty? ? nil : uri.path
|
|
233
|
+
login = opts[:command][:anonymous] == true ? nil : info[:User]
|
|
234
|
+
pass = opts[:command][:anonymous] == true ? nil : info[:Password]
|
|
235
|
+
|
|
236
|
+
@plugin.run(:pre, host, :getbug, opts)
|
|
237
|
+
|
|
238
|
+
xmlrpc = Bugzilla::XMLRPC.new(host, port, path)
|
|
239
|
+
user = Bugzilla::User.new(xmlrpc)
|
|
240
|
+
user.session(login, pass) do
|
|
241
|
+
bug = Bugzilla::Bug.new(xmlrpc)
|
|
242
|
+
|
|
243
|
+
result = nil
|
|
244
|
+
if opts[:command][:summary] == true then
|
|
245
|
+
result = bug.get_bugs(nnn.split(','))
|
|
246
|
+
elsif opts[:command][:details] == true then
|
|
247
|
+
result = bug.get_bugs(nnn.split(','), ::Bugzilla::Bug::FIELDS_DETAILS)
|
|
248
|
+
else
|
|
249
|
+
result = bug.get_bugs(nnn.split(','), nil)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
@plugin.run(:post, host, :getbug, result)
|
|
253
|
+
|
|
254
|
+
result.each do |r|
|
|
255
|
+
yield r
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end # def real_do
|
|
260
|
+
|
|
261
|
+
end # class Getbug
|
|
262
|
+
|
|
263
|
+
class Search < CommandTemplate
|
|
264
|
+
|
|
265
|
+
def initialize(plugin)
|
|
266
|
+
super
|
|
267
|
+
|
|
268
|
+
@n_args = 1
|
|
269
|
+
end # def initialize
|
|
270
|
+
|
|
271
|
+
def parse(parser, argv, opts)
|
|
272
|
+
opts[:query] ||= {}
|
|
273
|
+
parser.banner = sprintf("Usage: %s [global options] search [command options] <prefix> ...", File.basename(__FILE__))
|
|
274
|
+
parser.separator ""
|
|
275
|
+
parser.separator "Search options:"
|
|
276
|
+
parser.on('--alias=ALIASES', 'filter out the result by the alias') {|v| opts[:query][:alias] ||= []; opts[:query][:alias].push(*v.split(','))}
|
|
277
|
+
parser.on('-a', '--assignee=ASSIGNEES', 'filter out the result by the specific assignees') {|v| opts[:query][:assigned_to] ||= []; opts[:query][:assigned_to].push(*v.split(','))}
|
|
278
|
+
parser.on('--bug=BUGS', 'filter out the result by the specific bug number') {|v| opts[:query][:id] ||= []; opts[:query][:id].push(*v.split(','))}
|
|
279
|
+
parser.on('-c', '--component=COMPONENTS', 'filter out the result by the specific components') {|v| opts[:query][:component] ||= []; opts[:query][:component].push(*v.split(','))}
|
|
280
|
+
parser.on('--create-time=TIME', 'Searches for bugs that were created at this time or later') {|v| opts[:query][:creation_time] = Time.parse(v)}
|
|
281
|
+
parser.on('--creator=CREATER', 'filter out the result by the specific user who reported bugs') {|v| opts[:query][:creator] ||= []; opts[:query][:creator].push(*v.split(','))}
|
|
282
|
+
parser.on('--last-change-time=TIME', 'Searches for bugs that were modified at this time or later') {|v| opts[:query][:last_change_time] = Time.parse(v)}
|
|
283
|
+
parser.on('--op-sys=NAMES', 'filter out the result by the operating system') {|v| opts[:query][:op_sys] ||= []; opts[:query][:op_sys].push(*v.split(','))}
|
|
284
|
+
parser.on('--platform=PLATFORMS', 'filter out the result by the platform') {|v| opts[:query][:platform] ||= []; opts[:query][:platform].push(*v.split(','))}
|
|
285
|
+
parser.on('--priority=PRIORITY', 'filter out the result by the priority') {|v| opts[:query][:priority] ||= []; opts[:query][:priority].push(*v.split(','))}
|
|
286
|
+
parser.on('-p', '--product=PRODUCTS', 'filter out the result by the specific products') {|v| opts[:query][:product] ||= []; opts[:query][:product].push(*v.split(','))}
|
|
287
|
+
parser.on('--resolution=RESOLUSIONS', 'filter out the result by the resolusions') {|v| opts[:query][:resolution] ||= []; opts[:query][:resolusion].push(*v.split(','))}
|
|
288
|
+
parser.on('--severity=SEVERITY', 'filter out the result by the severity') {|v| opts[:query][:severity] ||= []; opts[:query][:severity].push(*v.split(','))}
|
|
289
|
+
parser.on('-s', '--status=STATUSES', 'filter out the result by the status') {|v| opts[:query][:status] ||= []; opts[:query][:status].push(*v.split(','))}
|
|
290
|
+
parser.on('--summary=SUMMARY', 'filter out the result by the summary') {|v| opts[:query][:summary] ||= []; opts[:query][:summary] << v}
|
|
291
|
+
parser.on('--milestone=MILESTONE', 'filter out the result by the target milestone') {|v| opts[:query][:target_milestone] ||= []; opts[:query][:target_milestone].push(*v.split(','))}
|
|
292
|
+
parser.on('--whiteboard=STRING', 'filter out the result by the specific words in the status whiteboard') {|v| opts[:query][:whiteboard] ||= []; opts[:query][:whiteboard] << v}
|
|
293
|
+
parser.separator ""
|
|
294
|
+
parser.separator "Command options:"
|
|
295
|
+
parser.on('--short-list', 'Displays bugs summary only') {opts[:summary] = true}
|
|
296
|
+
parser.on('--detailed-list', 'Displays detailed bugs information') {opts[:details] = true}
|
|
297
|
+
parser.on('--anonymous', 'Access to Bugzilla anonymously') {opts[:anonymous] = true}
|
|
298
|
+
|
|
299
|
+
super
|
|
300
|
+
end # def parse
|
|
301
|
+
|
|
302
|
+
def do(argv, opts)
|
|
303
|
+
c = 0
|
|
304
|
+
real_do(argv, opts) do |result|
|
|
305
|
+
if opts[:command][:summary] == true then
|
|
306
|
+
printf("Bug#%s, %s, %s[%s, %s] %s\n",
|
|
307
|
+
result['id'] || result['bug_id'],
|
|
308
|
+
result['product'],
|
|
309
|
+
result['component'],
|
|
310
|
+
result['status'],
|
|
311
|
+
result['severity'],
|
|
312
|
+
result['summary'])
|
|
313
|
+
elsif opts[:command][:details] == true then
|
|
314
|
+
printf("Bug#%s, %s, %s, %s[%s, %s, %s, %s] %s\n",
|
|
315
|
+
result['id'],
|
|
316
|
+
result['product'],
|
|
317
|
+
result['assigned_to'],
|
|
318
|
+
result['component'],
|
|
319
|
+
result['status'],
|
|
320
|
+
result['resolution'],
|
|
321
|
+
result['priority'],
|
|
322
|
+
result['severity'],
|
|
323
|
+
result['summary'])
|
|
324
|
+
end
|
|
325
|
+
c += 1
|
|
326
|
+
end
|
|
327
|
+
printf("\n%d bug(s) found\n", c)
|
|
328
|
+
end # def do
|
|
329
|
+
|
|
330
|
+
private
|
|
331
|
+
|
|
332
|
+
def real_do(argv, opts)
|
|
333
|
+
conf = read_config(opts)
|
|
334
|
+
conf.freeze
|
|
335
|
+
argv.each do |prefix|
|
|
336
|
+
unless conf.include?(prefix) then
|
|
337
|
+
raise RuntimeError, sprintf("No host information for %s", prefix)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
info = conf[prefix]
|
|
341
|
+
uri = URI.parse(info[:URL])
|
|
342
|
+
host = uri.host
|
|
343
|
+
port = uri.port
|
|
344
|
+
path = uri.path.empty? ? nil : uri.path
|
|
345
|
+
login = opts[:command][:anonymous] == true ? nil : info[:User]
|
|
346
|
+
pass = opts[:command][:anonymous] == true ? nil : info[:Password]
|
|
347
|
+
|
|
348
|
+
@plugin.run(:pre, host, :search, opts[:command][:query])
|
|
349
|
+
|
|
350
|
+
xmlrpc = Bugzilla::XMLRPC.new(host, port, path)
|
|
351
|
+
user = Bugzilla::User.new(xmlrpc)
|
|
352
|
+
user.session(login, pass) do
|
|
353
|
+
bug = Bugzilla::Bug.new(xmlrpc)
|
|
354
|
+
|
|
355
|
+
opts[:command][:query][:product].map! {|x| info.include?(:ProductAliases) && info[:ProductAliases].include?(x) ? info[:ProductAliases][x] : x} if opts[:command][:query].include?(:product)
|
|
356
|
+
|
|
357
|
+
result = bug.search(opts[:command][:query])
|
|
358
|
+
|
|
359
|
+
@plugin.run(:post, host, :search, result)
|
|
360
|
+
|
|
361
|
+
if result.include?('bugs') then
|
|
362
|
+
result['bugs'].each do |r|
|
|
363
|
+
yield r
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end # def real_do
|
|
369
|
+
|
|
370
|
+
end # class Search
|
|
371
|
+
|
|
372
|
+
class Show < CommandTemplate
|
|
373
|
+
|
|
374
|
+
def initialize(plugin)
|
|
375
|
+
super
|
|
376
|
+
|
|
377
|
+
@n_args = 1
|
|
378
|
+
end # def initialize
|
|
379
|
+
|
|
380
|
+
def parse(parser, argv, opts)
|
|
381
|
+
opts[:show] ||= {}
|
|
382
|
+
opts[:show][:mode] = :component
|
|
383
|
+
opts[:show][:field] = []
|
|
384
|
+
parser.banner = sprintf("Usage: %s [global options] show [command options] <prefix> ...", File.basename(__FILE__))
|
|
385
|
+
parser.separator ""
|
|
386
|
+
parser.separator "Command options:"
|
|
387
|
+
parser.on('-f', '--field', 'Displays available field informations') {|v| opts[:show][:mode] = :field}
|
|
388
|
+
parser.on('--field-name=NAME', 'Displays NAME field information') {|v| opts[:show][:field] << v.split(',')}
|
|
389
|
+
parser.on('-p', '--product', 'Displays available product names') {|v| opts[:show][:mode] = :product}
|
|
390
|
+
parser.on('-c', '--component', 'Displays available component names (default)') {|v| opts[:show][:mode] = :component}
|
|
391
|
+
parser.on('--anonymous', 'Access to Bugzilla anonymously') {opts[:anonymous] = true}
|
|
392
|
+
|
|
393
|
+
super
|
|
394
|
+
end # def parse
|
|
395
|
+
|
|
396
|
+
def do(argv, opts)
|
|
397
|
+
real_do(argv, opts) do |*result|
|
|
398
|
+
if opts[:command][:show][:mode] == :product then
|
|
399
|
+
printf("%s\n", result[0])
|
|
400
|
+
elsif opts[:command][:show][:mode] == :component then
|
|
401
|
+
printf("%s:\n", result[0])
|
|
402
|
+
printf(" %s\n", result[1].join("\n "))
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
end # def do
|
|
406
|
+
|
|
407
|
+
private
|
|
408
|
+
|
|
409
|
+
def real_do(argv, opts)
|
|
410
|
+
conf = read_config(opts)
|
|
411
|
+
conf.freeze
|
|
412
|
+
argv.each do |prefix|
|
|
413
|
+
unless conf.include?(prefix) then
|
|
414
|
+
raise RuntimeError, sprintf("No host information for %s", prefix)
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
info = conf[prefix]
|
|
418
|
+
uri = URI.parse(info[:URL])
|
|
419
|
+
host = uri.host
|
|
420
|
+
port = uri.port
|
|
421
|
+
path = uri.path.empty? ? nil : uri.path
|
|
422
|
+
login = opts[:command][:anonymous] == true ? nil : info[:User]
|
|
423
|
+
pass = opts[:command][:anonymous] == true ? nil : info[:Password]
|
|
424
|
+
|
|
425
|
+
@plugin.run(:pre, host, :show, opts)
|
|
426
|
+
|
|
427
|
+
xmlrpc = Bugzilla::XMLRPC.new(host, port, path)
|
|
428
|
+
user = Bugzilla::User.new(xmlrpc)
|
|
429
|
+
user.session(login, pass) do
|
|
430
|
+
if opts[:command][:show][:mode] == :field then
|
|
431
|
+
bug = Bugzilla::Bug.new(xmlrpc)
|
|
432
|
+
|
|
433
|
+
result = bug.fields(opts[:command][:show][:field].flatten)
|
|
434
|
+
|
|
435
|
+
@plugin.run(:post, host, :show, result)
|
|
436
|
+
|
|
437
|
+
pp result
|
|
438
|
+
else
|
|
439
|
+
product = Bugzilla::Product.new(xmlrpc)
|
|
440
|
+
|
|
441
|
+
result = product.selectable_products
|
|
442
|
+
|
|
443
|
+
@plugin.run(:post, host, :show, result)
|
|
444
|
+
|
|
445
|
+
products = result.keys.sort
|
|
446
|
+
products.each do |p|
|
|
447
|
+
if opts[:command][:show][:mode] == :product then
|
|
448
|
+
yield p
|
|
449
|
+
elsif opts[:command][:show][:mode] == :component then
|
|
450
|
+
yield p, result[p][0].sort
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end # def real_do
|
|
457
|
+
|
|
458
|
+
end # class Show
|
|
459
|
+
|
|
460
|
+
class Metrics < CommandTemplate
|
|
461
|
+
|
|
462
|
+
def initialize(plugin)
|
|
463
|
+
super
|
|
464
|
+
|
|
465
|
+
@n_args = 1
|
|
466
|
+
end # def initialize
|
|
467
|
+
|
|
468
|
+
def parse(parser, argv, opts)
|
|
469
|
+
opts[:metrics] = {}
|
|
470
|
+
opts[:query] = {}
|
|
471
|
+
opts[:metrics][:output] = 'bz-metrics.png'
|
|
472
|
+
opts[:metrics][:x_coordinate] = :monthly
|
|
473
|
+
|
|
474
|
+
parser.banner = sprintf("Usage: %s [global options] metrics [command options] <prefix> ...", File.basename(__FILE__))
|
|
475
|
+
parser.separator ""
|
|
476
|
+
parser.separator "Search options:"
|
|
477
|
+
parser.on('--alias=ALIASES', 'filter out the result by the alias') {|v| opts[:query][:alias] ||= []; opts[:query][:alias].push(*v.split(','))}
|
|
478
|
+
parser.on('-a', '--assignee=ASSIGNEES', 'filter out the result by the specific assignees') {|v| opts[:query][:assigned_to] ||= []; opts[:query][:assigned_to].push(*v.split(','))}
|
|
479
|
+
parser.on('--bug=BUGS', 'filter out the result by the specific bug number') {|v| opts[:query][:id] ||= []; opts[:query][:id].push(*v.split(','))}
|
|
480
|
+
parser.on('-c', '--component=COMPONENTS', 'filter out the result by the specific components') {|v| opts[:query][:component] ||= []; opts[:query][:component].push(*v.split(','))}
|
|
481
|
+
parser.on('--creator=CREATER', 'filter out the result by the specific user who reported bugs') {|v| opts[:query][:creator] ||= []; opts[:query][:creator].push(*v.split(','))}
|
|
482
|
+
parser.on('--op-sys=NAMES', 'filter out the result by the operating system') {|v| opts[:query][:op_sys] ||= []; opts[:query][:op_sys].push(*v.split(','))}
|
|
483
|
+
parser.on('--platform=PLATFORMS', 'filter out the result by the platform') {|v| opts[:query][:platform] ||= []; opts[:query][:platform].push(*v.split(','))}
|
|
484
|
+
parser.on('--priority=PRIORITY', 'filter out the result by the priority') {|v| opts[:query][:priority] ||= []; opts[:query][:priority].push(*v.split(','))}
|
|
485
|
+
parser.on('-p', '--product=PRODUCTS', 'filter out the result by the specific products') {|v| opts[:query][:product] ||= []; opts[:query][:product].push(*v.split(','))}
|
|
486
|
+
parser.on('--resolution=RESOLUSIONS', 'filter out the result by the resolusions') {|v| opts[:query][:resolution] ||= []; opts[:query][:resolusion].push(*v.split(','))}
|
|
487
|
+
parser.on('--severity=SEVERITY', 'filter out the result by the severity') {|v| opts[:query][:severity] ||= []; opts[:query][:severity].push(*v.split(','))}
|
|
488
|
+
parser.on('--summary=SUMMARY', 'filter out the result by the summary') {|v| opts[:query][:summary] ||= []; opts[:query][:summary] << v}
|
|
489
|
+
parser.on('--milestone=MILESTONE', 'filter out the result by the target milestone') {|v| opts[:query][:target_milestone] ||= []; opts[:query][:target_milestone].push(*v.split(','))}
|
|
490
|
+
parser.on('--whiteboard=STRING', 'filter out the result by the specific words in the status whiteboard') {|v| opts[:query][:whiteboard] ||= []; opts[:query][:whiteboard] << v}
|
|
491
|
+
parser.separator ""
|
|
492
|
+
parser.separator "Command options:"
|
|
493
|
+
parser.on('-t', '--title=TITLE', 'add TITLE to the graph') {|v| opts[:metrics][:title] = v}
|
|
494
|
+
parser.on('--begin-date=DATE', 'generate the graph since the beginning of month of DATE.') {|v| x = Time.parse(v); opts[:metrics][:begin_date] = Time.utc(x.year, x.month, 1, 0, 0, 0)}
|
|
495
|
+
parser.on('--end-date=DATE', 'generate the graph until the end of month of DATE.') {|v| x = Time.parse(v); opts[:metrics][:end_date] = Time.utc(x.year, x.month + 1, 1, 0, 0, 0) - 1}
|
|
496
|
+
parser.on('-o', '--output=FILE', 'generate the graph to FILE') {|v| opts[:metrics][:output] = v}
|
|
497
|
+
parser.on('--anonymous', 'access to Bugzilla anonymously') {|v| opts[:anonymous] = true}
|
|
498
|
+
parser.on('--weekly', 'genereate the graph with weekly X-coordinate') {|v| opts[:metrics][:x_coordinate] = :weekly}
|
|
499
|
+
parser.on('--monthly', 'genereate the graph with monthly X-coordinate (default)') {|v| opts[:metrics][:x_coordinate] = :monthly}
|
|
500
|
+
|
|
501
|
+
super
|
|
502
|
+
end # def parse
|
|
503
|
+
|
|
504
|
+
def do(argv, opts)
|
|
505
|
+
data = {
|
|
506
|
+
:label=>[],
|
|
507
|
+
'NEW'=>[],
|
|
508
|
+
'ASSIGNED'=>[],
|
|
509
|
+
'MODIFIED'=>[],
|
|
510
|
+
'ON_QA'=>[],
|
|
511
|
+
'CLOSED'=>[],
|
|
512
|
+
'OPEN'=>[]
|
|
513
|
+
}
|
|
514
|
+
last_label = nil
|
|
515
|
+
real_do(argv, opts) do |t, new, assigned, modified, on_qa, closed, open|
|
|
516
|
+
printf("%s, new: %d, assigned: %d, modified %d, on_qa %d, closed %d / open %d\n",
|
|
517
|
+
opts[:command][:metrics][:x_coordinate] == :weekly ? sprintf("week %d", Date.new(t.year, t.month, t.day).cweek) : t.strftime("%Y-%m"), new, assigned, modified, on_qa, closed, open)
|
|
518
|
+
data['NEW'] << new
|
|
519
|
+
data['ASSIGNED'] << assigned
|
|
520
|
+
data['MODIFIED'] << modified
|
|
521
|
+
data['ON_QA'] << on_qa
|
|
522
|
+
data['CLOSED'] << closed
|
|
523
|
+
data['OPEN'] << open
|
|
524
|
+
label = t.strftime("%Y/%m")
|
|
525
|
+
if last_label != label then
|
|
526
|
+
data[:label] << label
|
|
527
|
+
last_label = label
|
|
528
|
+
else
|
|
529
|
+
data[:label] << nil
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
timeline = data[:label]
|
|
534
|
+
data.delete(:label)
|
|
535
|
+
def timeline.to_hash
|
|
536
|
+
ret = {}
|
|
537
|
+
(0..self.length-1).each do |i|
|
|
538
|
+
ret[i] = self[i] unless self[i].nil?
|
|
539
|
+
end
|
|
540
|
+
ret
|
|
541
|
+
end # def timeline.to_hash
|
|
542
|
+
|
|
543
|
+
# output the trend graph
|
|
544
|
+
g = Gruff::Line.new
|
|
545
|
+
g.title = sprintf("Trend: %s", opts[:command][:metrics][:title])
|
|
546
|
+
g.labels = timeline.to_hash
|
|
547
|
+
data.each do |k, v|
|
|
548
|
+
next unless k == 'NEW' || k == 'OPEN' || k == 'CLOSED'
|
|
549
|
+
g.data(k, v)
|
|
550
|
+
end
|
|
551
|
+
g.write(sprintf("trend-%s", opts[:command][:metrics][:output]))
|
|
552
|
+
|
|
553
|
+
# output the activity graph
|
|
554
|
+
g = Gruff::StackedBar.new
|
|
555
|
+
g.title = sprintf("Activity: %s", opts[:command][:metrics][:title])
|
|
556
|
+
g.labels = timeline.to_hash
|
|
557
|
+
g.data('Resolved', data['CLOSED'])
|
|
558
|
+
x = []
|
|
559
|
+
(0..data['ASSIGNED'].length-1).each do |i|
|
|
560
|
+
x[i] = data['ASSIGNED'][i] + data['MODIFIED'][i] + data['ON_QA'][i]
|
|
561
|
+
end
|
|
562
|
+
g.data('Unresolved', x)
|
|
563
|
+
a = []
|
|
564
|
+
(0..data['OPEN'].length-1).each do |i|
|
|
565
|
+
a[i] = data['OPEN'][i] - x[i]
|
|
566
|
+
end
|
|
567
|
+
g.data('non-activity bugs', a)
|
|
568
|
+
g.write(sprintf("activity-%s", opts[:command][:metrics][:output]))
|
|
569
|
+
end # def do
|
|
570
|
+
|
|
571
|
+
private
|
|
572
|
+
|
|
573
|
+
def real_do(argv, opts)
|
|
574
|
+
conf = read_config(opts)
|
|
575
|
+
conf.freeze
|
|
576
|
+
argv.each do |prefix|
|
|
577
|
+
unless conf.include?(prefix) then
|
|
578
|
+
raise RuntimeError, sprintf("No host information for %s", prefix)
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
info = conf[prefix]
|
|
582
|
+
uri = URI.parse(info[:URL])
|
|
583
|
+
host = uri.host
|
|
584
|
+
port = uri.port
|
|
585
|
+
path = uri.path.empty? ? nil : uri.path
|
|
586
|
+
login = opts[:command][:anonymous] == true ? nil : info[:User]
|
|
587
|
+
pass = opts[:command][:anonymous] == true ? nil : info[:Password]
|
|
588
|
+
|
|
589
|
+
xmlrpc = Bugzilla::XMLRPC.new(host, port, path)
|
|
590
|
+
user = Bugzilla::User.new(xmlrpc)
|
|
591
|
+
user.session(login, pass) do
|
|
592
|
+
bug = Bugzilla::Bug.new(xmlrpc)
|
|
593
|
+
|
|
594
|
+
opts[:command][:query][:product].map! {|x| info.include?(:ProductAliases) && info[:ProductAliases].include?(x) ? info[:ProductAliases][x] : x} if opts[:command][:query].include?(:product)
|
|
595
|
+
|
|
596
|
+
ts = opts[:command][:metrics][:begin_date] || Time.utc(Time.new.year, 1, 1)
|
|
597
|
+
te = opts[:command][:metrics][:end_date] || Time.utc(Time.new.year+1, 1, 1) - 1
|
|
598
|
+
if opts[:command][:metrics][:x_coordinate] == :weekly then
|
|
599
|
+
# align to the week
|
|
600
|
+
d = Date.new(ts.year, ts.month, ts.day)
|
|
601
|
+
ds = Date.commercial(d.year, d.cweek, 1)
|
|
602
|
+
d = Date.new(te.year, te.month, te.day)
|
|
603
|
+
de = Date.commercial(d.year, d.cweek, 7)
|
|
604
|
+
ts = Time.utc(ds.year, ds.month, ds.day)
|
|
605
|
+
te = Time.utc(de.year, de.month, de.day)
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
searchopts = opts[:command][:query].clone
|
|
609
|
+
|
|
610
|
+
@plugin.run(:pre, host, :metrics, searchopts, opts[:metrics])
|
|
611
|
+
|
|
612
|
+
if searchopts == opts[:command][:query] then
|
|
613
|
+
raise NoMethodError, "No method to deal with the query"
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
while ts < te do
|
|
617
|
+
searchopts = opts[:command][:query].clone
|
|
618
|
+
|
|
619
|
+
# don't rely on the status to deal with NEW bugs.
|
|
620
|
+
# unable to estimate the case bugs closed quickly
|
|
621
|
+
if opts[:command][:metrics][:x_coordinate] == :weekly then
|
|
622
|
+
d = Date.new(ts.year, ts.month, ts.day)
|
|
623
|
+
de = Date.commercial(d.year, d.cweek, 7)
|
|
624
|
+
drange = [ts, Time.utc(de.year, de.month, de.day, 23, 59, 59)]
|
|
625
|
+
else
|
|
626
|
+
drange = [ts, Time.utc(ts.year, ts.month + 1, 1) - 1]
|
|
627
|
+
end
|
|
628
|
+
searchopts[:creation_time] = drange
|
|
629
|
+
|
|
630
|
+
@plugin.run(:pre, host, :metrics, searchopts)
|
|
631
|
+
|
|
632
|
+
result = bug.search(searchopts)
|
|
633
|
+
|
|
634
|
+
@plugin.run(:post, host, :search, result)
|
|
635
|
+
|
|
636
|
+
new = result.include?('bugs') ? result['bugs'].length : 0
|
|
637
|
+
|
|
638
|
+
# for open bugs
|
|
639
|
+
# what we are interested in here would be how many bugs still keeps open.
|
|
640
|
+
searchopts = opts[:command][:query].clone
|
|
641
|
+
searchopts[:last_change_time] = drange
|
|
642
|
+
searchopts[:status] = '__open__'
|
|
643
|
+
|
|
644
|
+
@plugin.run(:pre, host, :metrics, searchopts)
|
|
645
|
+
|
|
646
|
+
result = bug.search(searchopts)
|
|
647
|
+
|
|
648
|
+
@plugin.run(:post, host, :search, result)
|
|
649
|
+
|
|
650
|
+
assigned = result.include?('bugs') ? result['bugs'].map{|x| x['status'] == 'ASSIGNED' ? x : nil}.compact.length : 0
|
|
651
|
+
modified = result.include?('bugs') ? result['bugs'].map{|x| x['status'] == 'MODIFIED' ? x : nil}.compact.length : 0
|
|
652
|
+
on_qa = result.include?('bugs') ? result['bugs'].map{|x| x['status'] == 'ON_QA' ? x : nil}.compact.length : 0
|
|
653
|
+
|
|
654
|
+
# send a separate query for closed.
|
|
655
|
+
# just counting CLOSED the above is meaningless.
|
|
656
|
+
# what we are interested in here would be how much bugs are
|
|
657
|
+
# actually closed, but not how many closed bugs one worked on.
|
|
658
|
+
searchopts = opts[:command][:query].clone
|
|
659
|
+
searchopts[:last_change_time] = drange
|
|
660
|
+
searchopts[:status] = 'CLOSED'
|
|
661
|
+
|
|
662
|
+
@plugin.run(:pre, host, :metrics, searchopts)
|
|
663
|
+
|
|
664
|
+
result = bug.search(searchopts)
|
|
665
|
+
|
|
666
|
+
@plugin.run(:post, host, :search, result)
|
|
667
|
+
|
|
668
|
+
closed = result.include?('bugs') ? result['bugs'].length : 0
|
|
669
|
+
|
|
670
|
+
# obtain the information for open bugs that closed now
|
|
671
|
+
searchopts = opts[:command][:query].clone
|
|
672
|
+
searchopts[:status] = 'CLOSED'
|
|
673
|
+
searchopts[:metrics_closed_after] = drange[1] + 1
|
|
674
|
+
|
|
675
|
+
@plugin.run(:pre, host, :metrics, searchopts)
|
|
676
|
+
|
|
677
|
+
result = bug.search(searchopts)
|
|
678
|
+
|
|
679
|
+
@plugin.run(:post, host, :search, result)
|
|
680
|
+
|
|
681
|
+
open_bugs = result.include?('bugs') ? result['bugs'].length : 0
|
|
682
|
+
|
|
683
|
+
# obtain the information for open bugs
|
|
684
|
+
searchopts = opts[:command][:query].clone
|
|
685
|
+
searchopts[:metrics_not_closed] = drange[1]
|
|
686
|
+
|
|
687
|
+
@plugin.run(:pre, host, :metrics, searchopts)
|
|
688
|
+
|
|
689
|
+
result = bug.search(searchopts)
|
|
690
|
+
|
|
691
|
+
@plugin.run(:post, host, :search, result)
|
|
692
|
+
|
|
693
|
+
open_bugs += result.include?('bugs') ? result['bugs'].length : 0
|
|
694
|
+
|
|
695
|
+
yield ts, new, assigned, modified, on_qa, closed, open_bugs
|
|
696
|
+
|
|
697
|
+
ts = drange[1] + 1
|
|
698
|
+
end
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
end # def real_do
|
|
702
|
+
|
|
703
|
+
end # class Metrics
|
|
704
|
+
|
|
705
|
+
end # module BzConsole
|
|
706
|
+
|
|
707
|
+
begin
|
|
708
|
+
opts = {}
|
|
709
|
+
opts[:command] = {}
|
|
710
|
+
subargv = []
|
|
711
|
+
|
|
712
|
+
o = ARGV.options do |opt|
|
|
713
|
+
opt.banner = sprintf("Usage: %s [global options] <command> ...", File.basename(__FILE__))
|
|
714
|
+
opt.separator ""
|
|
715
|
+
opt.separator "Global options:"
|
|
716
|
+
opt.on('-c', '--config=FILE', 'read FILE as the configuration file.') {|v| opts[:config] = v}
|
|
717
|
+
opt.on('-h', '--help', 'show this message') {|x| opts[:help] = true}
|
|
718
|
+
|
|
719
|
+
cmds = BzConsole.constants.sort.map {|x| (k = eval("BzConsole::#{x}")).class == Class && x != "CommandTemplate" ? x.downcase : nil}.compact
|
|
720
|
+
|
|
721
|
+
subargv = opt.order(ARGV);
|
|
722
|
+
|
|
723
|
+
command = subargv[0]
|
|
724
|
+
|
|
725
|
+
if subargv.length > 0 then
|
|
726
|
+
n = cmds.index(command)
|
|
727
|
+
unless n.nil? then
|
|
728
|
+
opts[:instance] = eval("BzConsole::#{cmds[n].capitalize}.new(Bugzilla::Plugin::Template.new)")
|
|
729
|
+
subargv = opts[:instance].parse(opt, subargv[1..-1], opts[:command])
|
|
730
|
+
else
|
|
731
|
+
STDERR.printf("E: Unknown command: %s\n", subargv[0])
|
|
732
|
+
STDERR.printf(" Available commands: %s\n", cmds.join(' '))
|
|
733
|
+
exit 1
|
|
734
|
+
end
|
|
735
|
+
else
|
|
736
|
+
opt.separator ""
|
|
737
|
+
opt.separator "Available commands:"
|
|
738
|
+
opt.separator sprintf(" %s", cmds.join(' '))
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
if opts[:instance].nil? && subargv.length == 0 ||
|
|
742
|
+
opts[:help] == true ||
|
|
743
|
+
subargv.length < opts[:instance].n_args then
|
|
744
|
+
puts opt.help
|
|
745
|
+
exit
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
opts[:instance].do(subargv, opts)
|
|
750
|
+
end
|