Commit 8fdb0d36 authored by terry%mozilla.org's avatar terry%mozilla.org

Massive stomp on the query page and buglist page. Added the ability

to use the "boolean charts" to do very powerful queries.
parent b23cb23b
...@@ -78,10 +78,10 @@ sub url_quote { ...@@ -78,10 +78,10 @@ sub url_quote {
} }
sub ProcessFormFields { sub ParseUrlString {
my ($buffer) = (@_); my ($buffer, $f, $m) = (@_);
undef %::FORM; undef %$f;
undef %::MFORM; undef %$m;
my %isnull; my %isnull;
my $remaining = $buffer; my $remaining = $buffer;
...@@ -105,13 +105,13 @@ sub ProcessFormFields { ...@@ -105,13 +105,13 @@ sub ProcessFormFields {
$value = ""; $value = "";
} }
if ($value ne "") { if ($value ne "") {
if (defined $::FORM{$name}) { if (defined $f->{$name}) {
$::FORM{$name} .= $value; $f->{$name} .= $value;
my $ref = $::MFORM{$name}; my $ref = $m->{$name};
push @$ref, $value; push @$ref, $value;
} else { } else {
$::FORM{$name} = $value; $f->{$name} = $value;
$::MFORM{$name} = [$value]; $m->{$name} = [$value];
} }
} else { } else {
$isnull{$name} = 1; $isnull{$name} = 1;
...@@ -119,15 +119,21 @@ sub ProcessFormFields { ...@@ -119,15 +119,21 @@ sub ProcessFormFields {
} }
if (defined %isnull) { if (defined %isnull) {
foreach my $name (keys(%isnull)) { foreach my $name (keys(%isnull)) {
if (!defined $::FORM{$name}) { if (!defined $f->{$name}) {
$::FORM{$name} = ""; $f->{$name} = "";
$::MFORM{$name} = []; $m->{$name} = [];
} }
} }
} }
} }
sub ProcessFormFields {
my ($buffer) = (@_);
return ParseUrlString($buffer, \%::FORM, \%::MFORM);
}
sub ProcessMultipartFormFields { sub ProcessMultipartFormFields {
my ($boundary) = (@_); my ($boundary) = (@_);
$boundary =~ s/^-*//; $boundary =~ s/^-*//;
......
<html> <head>
<title>The "boolean chart" section of the query page</title>
</head>
<body>
<h1>The "boolean chart" section of the query page</h1>
("Boolean chart" is a terrible term; anyone got a better one I can use
instead?)
<p>
The Bugzilla query page is designed to be reasonably easy to use.
But, with such ease of use always comes some lack of power. The
"boolean chart" section is designed to let you do very powerful
queries, but it's not the easiest thing to learn (or explain).
<p>
So.
<p>
The boolean chart starts with a single "term". A term is a
combination of two pulldown menus and a text field.
You choose items from the menus, specifying "what kind of thing
am I searching for" and "what kind of matching do I want", and type in
a value on the text field, specifying "what should it match".
<p>
The real fun starts when you click on the "Or" or "And" buttons. If
you bonk on the "Or" button, then you get a second term to the right
of the first one. You can then configure that term, and the result of
the query will be anything that matches either of the terms.
<p>
Or, you can bonk the "And" button, and get a new term below the
original one, and now the result of the query will be anything that
matches both of the terms.
<p>
And you can keep clicking "And" and "Or", and get a page with tons of
terms. "Or" has higher precedence than "And". (In other words, you
can think of each line of "Or" stuff as having parenthesis around it.)
<p>
The most subtle thing is this "Add another boolean chart" button.
This is almost the same thing as the "And" button. The difference is
if you use one of the fields where several items can be associated
with a single bug. This includes "Comments", "CC", and all the
"changed [something]" entries. Now, if you have multiple terms that
all talk about one of these fields, it's ambiguous whether they are
allowed to be talking about different instances of that field. So,
to let you have it both ways, they always mean the same instance,
unless the terms appear on different charts.
<p>
For example: if you search for "priority changed to P5" and
"priority changed by person@addr", it will only find bugs where the
given person at some time changed the priority to P5. However, if
what you really want is to find all bugs where the milestone was
changed at some time by the person, and someone (possibly someone
else) at some time changed the milestone to P5, then you would put
the two terms in two different charts.
<p>
Clear as mud? Please, I beg you, rewrite this document to make
everything crystal clear, and send the improved version to <a
href="terry@mozilla.org">Terry</a>.
<hr>
<!-- hhmts start -->
Last modified: Thu Jan 27 16:56:11 2000
<!-- hhmts end -->
</body> </html>
...@@ -46,17 +46,565 @@ sub sillyness { ...@@ -46,17 +46,565 @@ sub sillyness {
$zz = @::versions; $zz = @::versions;
}; };
my $serverpush = 0;
ConnectToDatabase(); ConnectToDatabase();
# print "Content-type: text/plain\n\n"; # Handy for debugging. # print "Content-type: text/plain\n\n"; # Handy for debugging.
# $::FORM{'debug'} = 1;
if (grep(/^cmd-/, keys(%::FORM))) {
my $url = "query.cgi#chart?$::buffer";
print qq{Refresh: 0; URL=$url
Content-type: text/html
<A HREF="$url">Adding field to query page...</A>
};
exit();
}
if (!defined $::FORM{'cmdtype'}) { if (!defined $::FORM{'cmdtype'}) {
# This can happen if there's an old bookmark to a query... # This can happen if there's an old bookmark to a query...
$::FORM{'cmdtype'} = 'doit'; $::FORM{'cmdtype'} = 'doit';
} }
sub SqlifyDate {
my ($str) = (@_);
if (!defined $str) {
$str = "";
}
my $date = str2time($str);
if (!defined $date) {
print "\n\n<P>The string '<tt>$str</tt>' is not a legal date.\n";
print "<P>Please click the <B>Back</B> button and try again.\n";
PutFooter();
exit;
}
return time2str("%Y/%m/%d %H:%M:%S", $date);
}
sub GetByWordList {
my ($field, $strs) = (@_);
my @list;
foreach my $w (split(/[\s,]+/, $strs)) {
my $word = $w;
if ($word ne "") {
$word =~ tr/A-Z/a-z/;
$word = SqlQuote(quotemeta($word));
$word =~ s/^'//;
$word =~ s/'$//;
$word = '(^|[^a-z0-9])' . $word . '($|[^a-z0-9])';
push(@list, "lower($field) regexp '$word'");
}
}
return \@list;
}
sub Error {
my ($str) = (@_);
if (!$serverpush) {
print "Content-type: text/html\n\n";
}
print $str;
print "\n<P>Please press <B>Back</B> and try again.\n";
PutFooter();
exit();
}
sub GenerateSQL {
my $debug = 0;
my ($fieldsref, $supptablesref, $wherepartref, $urlstr) = (@_);
my @fields;
my @supptables;
my @wherepart;
@fields = @$fieldsref if $fieldsref;
@supptables = @$supptablesref if $supptablesref;
@wherepart = @$wherepartref if $wherepartref;
my %F;
my %M;
ParseUrlString($urlstr, \%F, \%M);
my @specialchart;
my @andlist;
# First, deal with all the old hard-coded non-chart-based poop.
unshift(@supptables,
("profiles map_assigned_to",
"profiles map_reporter",
"LEFT JOIN profiles map_qa_contact ON bugs.qa_contact = map_qa_contact.userid"));
unshift(@wherepart,
("bugs.assigned_to = map_assigned_to.userid",
"bugs.reporter = map_reporter.userid",
"bugs.groupset & $::usergroupset = bugs.groupset"));
my $minvotes;
if (defined $F{'votes'}) {
my $c = trim($F{'votes'});
if ($c ne "") {
if ($c !~ /^[0-9]*$/) {
return Error("The 'At least ___ votes' field must be a\n" .
"simple number. You entered \"$c\", which\n" .
"doesn't cut it.");
}
push(@specialchart, ["votes", "greaterthan", $c - 1]);
}
}
if ($M{'bug_id'}) {
my $type = "anyexact";
if ($F{'bugidtype'} && $F{'bugidtype'} eq 'exclude') {
$type = "noexact";
}
push(@specialchart, ["bug_id", $type, join(',', @{$M{'bug_id'}})]);
}
if (defined $F{'sql'}) {
push(@wherepart, "( $F{'sql'} )");
}
my @legal_fields = ("product", "version", "rep_platform", "op_sys",
"bug_status", "resolution", "priority", "bug_severity",
"assigned_to", "reporter", "component",
"target_milestone", "groupset");
foreach my $field (keys %F) {
if (lsearch(\@legal_fields, $field) != -1) {
push(@specialchart, [$field, "anyexact",
join(',', @{$M{$field}})]);
}
}
if ($F{'keywords'}) {
my $t = $F{'keywords_type'};
if (!$t || $t eq "or") {
$t = "anywords";
}
push(@specialchart, ["keywords", $t, $F{'keywords'}]);
}
foreach my $id ("1", "2") {
if (!defined ($F{"email$id"})) {
next;
}
my $email = trim($F{"email$id"});
if ($email eq "") {
next;
}
my $type = $F{"emailtype$id"};
if ($type eq "exact") {
$type = "anyexact";
foreach my $name (split(',', $email)) {
$name = trim($name);
if ($name) {
DBNameToIdAndCheck($name);
}
}
}
my @clist;
foreach my $field ("assigned_to", "reporter", "cc", "qa_contact") {
if ($F{"email$field$id"}) {
push(@clist, $field, $type, $email);
}
}
if ($F{"emaillongdesc$id"}) {
my $table = "longdescs_";
push(@supptables, "longdescs $table");
push(@wherepart, "$table.bug_id = bugs.bug_id");
my $ptable = "longdescnames_";
push(@supptables,
"LEFT JOIN profiles $ptable ON $table.who = $ptable.userid");
push(@clist, "$ptable.login_name", $type, $email);
}
if (@clist) {
push(@specialchart, \@clist);
} else {
return Error("You must specify one or more fields in which to\n" .
"search for <tt>$email</tt>.\n");
}
}
if (defined $F{'changedin'}) {
my $c = trim($F{'changedin'});
if ($c ne "") {
if ($c !~ /^[0-9]*$/) {
return Error("The 'changed in last ___ days' field must be\n" .
"a simple number. You entered \"$c\", which\n" .
"doesn't cut it.");
}
push(@specialchart, ["changedin",
"lessthan", $c + 1]);
}
}
my $ref = $M{'chfield'};
if (defined $ref) {
my $which = lsearch($ref, "[Bug creation]");
if ($which >= 0) {
splice(@$ref, $which, 1);
push(@specialchart, ["creation_ts", "greaterthan",
SqlifyDate($F{'chfieldfrom'})]);
my $to = $F{'chfieldto'};
if (defined $to) {
$to = trim($to);
if ($to ne "" && $to !~ /^now$/i) {
push(@specialchart, ["creation_ts", "lessthan",
SqlifyDate($to)]);
}
}
}
}
if (defined $ref && 0 < @$ref) {
push(@supptables, "bugs_activity actcheck");
my @list;
foreach my $f (@$ref) {
push(@list, "\nactcheck.fieldid = " . GetFieldID($f));
}
push(@wherepart, "actcheck.bug_id = bugs.bug_id");
push(@wherepart, "(" . join(' OR ', @list) . ")");
push(@wherepart, "actcheck.bug_when >= " .
SqlQuote(SqlifyDate($F{'chfieldfrom'})));
my $to = $F{'chfieldto'};
if (defined $to) {
$to = trim($to);
if ($to ne "" && $to !~ /^now$/i) {
push(@wherepart, "actcheck.bug_when <= " .
SqlQuote(SqlifyDate($to)));
}
}
my $value = $F{'chfieldvalue'};
if (defined $value) {
$value = trim($value);
if ($value ne "") {
push(@wherepart, "actcheck.newvalue = " .
SqlQuote($value))
}
}
}
foreach my $f ("short_desc", "long_desc", "bug_file_loc",
"status_whiteboard") {
if (defined $F{$f}) {
my $s = trim($F{$f});
if ($s ne "") {
my $n = $f;
my $q = SqlQuote($s);
my $type = $F{$f . "_type"};
push(@specialchart, [$f, $type, $s]);
}
}
}
my $chartid;
my $f;
my $t;
my $q;
my $v;
my $term;
my %funcsbykey;
my @funcdefs =
(
"^(assigned_to|reporter)," => sub {
push(@supptables, "profiles map_$f");
push(@wherepart, "bugs.$f = map_$f.userid");
$f = "map_$f.login_name";
},
"^qa_contact," => sub {
push(@supptables,
"LEFT JOIN profiles map_qa_contact ON bugs.qa_contact = map_qa_contact.userid");
$f = "map_$f.login_name";
},
"^cc," => sub {
push(@supptables,
("LEFT JOIN cc cc_$chartid ON bugs.bug_id = cc_$chartid.bug_id LEFT JOIN profiles map_cc_$chartid ON cc_$chartid.who = map_cc_$chartid.userid"));
$f = "map_cc_$chartid.login_name";
},
"^long_?desc,changedby" => sub {
my $table = "longdescs_$chartid";
push(@supptables, "longdescs $table");
push(@wherepart, "$table.bug_id = bugs.bug_id");
my $id = DBNameToIdAndCheck($v);
$term = "$table.who = $id";
},
"^long_?desc,changedbefore" => sub {
my $table = "longdescs_$chartid";
push(@supptables, "longdescs $table");
push(@wherepart, "$table.bug_id = bugs.bug_id");
$term = "$table.who < " . SqlQuote(SqlifyDate($v));
},
"^long_?desc,changedafter" => sub {
my $table = "longdescs_$chartid";
push(@supptables, "longdescs $table");
push(@wherepart, "$table.bug_id = bugs.bug_id");
$term = "$table.who > " . SqlQuote(SqlifyDate($v));
},
"^long_?desc," => sub {
my $table = "longdescs_$chartid";
push(@supptables, "longdescs $table");
push(@wherepart, "$table.bug_id = bugs.bug_id");
$f = "$table.thetext";
},
"^changedin," => sub {
$f = "(to_days(now()) - to_days(bugs.delta_ts))";
},
"^keywords," => sub {
GetVersionTable();
my @list;
my $table = "keywords_$chartid";
foreach my $value (split(/[\s,]+/, $v)) {
if ($value eq '') {
next;
}
my $id = $::keywordsbyname{$value};
if ($id) {
push(@list, "$table.keywordid = $id");
} else {
return Error("Unknown keyword named <code>$v</code>.\n" .
"<P>The legal keyword names are\n" .
"<A HREF=describekeywords.cgi>" .
"listed here</A>.\n");
}
}
my $haveawordterm;
if (@list) {
$haveawordterm = "(" . join(' OR ', @list) . ")";
if ($t eq "anywords") {
$term = $haveawordterm;
} elsif ($t eq "allwords") {
$ref = $funcsbykey{",$t"};
&$ref;
if ($term && $haveawordterm) {
$term = "(($term) AND $haveawordterm)";
}
}
}
if ($term) {
push(@supptables, "keywords $table");
push(@wherepart, "$table.bug_id = bugs.bug_id");
}
},
",equals" => sub {
$term = "$f = $q";
},
",notequals" => sub {
$term = "$f != $q";
},
",casesubstring" => sub {
$term = "INSTR($f, $q)";
},
",(substring|substr)" => sub {
$term = "INSTR(LOWER($f), " . lc($q) . ")";
},
",notsubstring" => sub {
$term = "INSTR(LOWER($f), " . lc($q) . ") = 0";
},
",regexp" => sub {
$term = "LOWER($f) REGEXP $q";
},
",notregexp" => sub {
$term = "LOWER($f) NOT REGEXP $q";
},
",lessthan" => sub {
$term = "$f < $q";
},
",greaterthan" => sub {
$term = "$f > $q";
},
",anyexact" => sub {
my @list;
foreach my $w (split(/,/, $v)) {
if ($w eq "---") {
$w = "";
}
push(@list, "$f = " . SqlQuote($w));
}
$term = join(" OR ", @list);
},
",anywords" => sub {
$term = join(" OR ", @{GetByWordList($f, $v)});
},
",allwords" => sub {
$term = join(" AND ", @{GetByWordList($f, $v)});
},
",nowords" => sub {
my @list = @{GetByWordList($f, $v)};
if (@list) {
$term = "NOT (" . join(" OR ", @list) . ")";
}
},
",changedbefore" => sub {
my $table = "act_$chartid";
my $ftable = "fielddefs_$chartid";
push(@supptables, "bugs_activity $table");
push(@supptables, "fielddefs $ftable");
push(@wherepart, "$table.bug_id = bugs.bug_id");
push(@wherepart, "$table.fieldid = $ftable.fieldid");
$term = "($ftable.name = '$f' AND $table.bug_when < $q)";
},
",changedafter" => sub {
my $table = "act_$chartid";
my $ftable = "fielddefs_$chartid";
push(@supptables, "bugs_activity $table");
push(@supptables, "fielddefs $ftable");
push(@wherepart, "$table.bug_id = bugs.bug_id");
push(@wherepart, "$table.fieldid = $ftable.fieldid");
$term = "($ftable.name = '$f' AND $table.bug_when > $q)";
},
",changedto" => sub {
my $table = "act_$chartid";
my $ftable = "fielddefs_$chartid";
push(@supptables, "bugs_activity $table");
push(@supptables, "fielddefs $ftable");
push(@wherepart, "$table.bug_id = bugs.bug_id");
push(@wherepart, "$table.fieldid = $ftable.fieldid");
$term = "($ftable.name = '$f' AND $table.newvalue = $q)";
},
",changedby" => sub {
my $table = "act_$chartid";
my $ftable = "fielddefs_$chartid";
push(@supptables, "bugs_activity $table");
push(@supptables, "fielddefs $ftable");
push(@wherepart, "$table.bug_id = bugs.bug_id");
push(@wherepart, "$table.fieldid = $ftable.fieldid");
my $id = DBNameToIdAndCheck($v);
$term = "($ftable.name = '$f' AND $table.who = $id)";
},
);
my @funcnames;
while (@funcdefs) {
my $key = shift(@funcdefs);
my $value = shift(@funcdefs);
if ($key =~ /^[^,]*$/) {
die "All defs in %funcs must have a comma in their name: $key";
}
if (exists $funcsbykey{$key}) {
die "Duplicate key in %funcs: $key";
}
$funcsbykey{$key} = $value;
push(@funcnames, $key);
}
my $chart = -1;
my $row = 0;
foreach my $ref (@specialchart) {
my $col = 0;
while (@$ref) {
$F{"field$chart-$row-$col"} = shift(@$ref);
$F{"type$chart-$row-$col"} = shift(@$ref);
$F{"value$chart-$row-$col"} = shift(@$ref);
if ($debug) {
print qq{<P>$F{"field$chart-$row-$col"} | $F{"type$chart-$row-$col"} | $F{"value$chart-$row-$col"}*\n};
}
$col++;
}
$row++;
}
for ($chart=-1 ;
$chart < 0 || exists $F{"field$chart-0-0"} ;
$chart++) {
$chartid = $chart >= 0 ? $chart : "";
for (my $row = 0 ;
exists $F{"field$chart-$row-0"} ;
$row++) {
my @orlist;
for (my $col = 0 ;
exists $F{"field$chart-$row-$col"} ;
$col++) {
$f = $F{"field$chart-$row-$col"} || "noop";
$t = $F{"type$chart-$row-$col"} || "noop";
$v = $F{"value$chart-$row-$col"};
$v = "" if !defined $v;
$v = trim($v);
if ($f eq "noop" || $t eq "noop" || $v eq "") {
next;
}
$q = SqlQuote($v);
my $func;
$term = undef;
foreach my $key (@funcnames) {
if ("$f,$t" =~ m/$key/) {
my $ref = $funcsbykey{$key};
if ($debug) {
print "<P>$key ($f , $t ) => ";
}
&$ref;
if ($debug) {
print "$f , $t , $term";
}
if ($term) {
last;
}
}
}
if ($term) {
push(@orlist, $term);
} else {
my $errstr = "Can't seem to handle " .
qq{'<code>$F{"field$chart-$row-$col"}</code>' and } .
qq{'<code>$F{"type$chart-$row-$col"}</code>' } .
"together";
die "Internal error: $errstr" if $chart < 0;
return Error($errstr);
}
}
if (@orlist) {
push(@andlist, "(" . join(" OR ", @orlist) . ")");
}
}
}
my %suppseen = ("bugs" => 1);
my $suppstring = "bugs";
foreach my $str (@supptables) {
if (!$suppseen{$str}) {
if ($str !~ /^LEFT JOIN/i) {
$suppstring .= ",";
}
$suppstring .= " $str";
$suppseen{$str} = 1;
}
}
my $query = ("SELECT " . join(', ', @fields) .
" FROM $suppstring" .
" WHERE " . join(' AND ', (@wherepart, @andlist)) .
" GROUP BY bugs.bug_id");
if ($debug) {
print "<P><CODE>" . value_quote($query) . "</CODE><P>\n";
exit();
}
return $query;
}
sub LookupNamedQuery { sub LookupNamedQuery {
my ($name) = (@_); my ($name) = (@_);
confirm_login(); confirm_login();
...@@ -164,7 +712,6 @@ OK, you have a new query named <code>$name</code> ...@@ -164,7 +712,6 @@ OK, you have a new query named <code>$name</code>
} }
my $serverpush = 0;
if ($ENV{'HTTP_USER_AGENT'} =~ /Mozilla.[3-9]/ && $ENV{'HTTP_USER_AGENT'} !~ /[Cc]ompatible/ ) { if ($ENV{'HTTP_USER_AGENT'} =~ /Mozilla.[3-9]/ && $ENV{'HTTP_USER_AGENT'} !~ /[Cc]ompatible/ ) {
# Search for real Netscape 3 and up. http://www.browsercaps.org used as source of # Search for real Netscape 3 and up. http://www.browsercaps.org used as source of
# browsers compatbile with server-push. It's a Netscape hack, incompatbile # browsers compatbile with server-push. It's a Netscape hack, incompatbile
...@@ -205,9 +752,11 @@ DefCol("severity", "substring(bugs.bug_severity, 1, 3)", "Sev", ...@@ -205,9 +752,11 @@ DefCol("severity", "substring(bugs.bug_severity, 1, 3)", "Sev",
DefCol("priority", "substring(bugs.priority, 1, 3)", "Pri", "bugs.priority"); DefCol("priority", "substring(bugs.priority, 1, 3)", "Pri", "bugs.priority");
DefCol("platform", "substring(bugs.rep_platform, 1, 3)", "Plt", DefCol("platform", "substring(bugs.rep_platform, 1, 3)", "Plt",
"bugs.rep_platform"); "bugs.rep_platform");
DefCol("owner", "assign.login_name", "Owner", "assign.login_name"); DefCol("owner", "map_assigned_to.login_name", "Owner",
DefCol("reporter", "report.login_name", "Reporter", "report.login_name"); "map_assigned_to.login_name");
DefCol("qa_contact", "qacont.login_name", "QAContact", "qacont.login_name"); DefCol("reporter", "map_reporter.login_name", "Reporter",
"map_reporter.login_name");
DefCol("qa_contact", "map_qa_contact.login_name", "QAContact", "map_qa_contact.login_name");
DefCol("status", "substring(bugs.bug_status,1,4)", "State", "bugs.bug_status"); DefCol("status", "substring(bugs.bug_status,1,4)", "State", "bugs.bug_status");
DefCol("resolution", "substring(bugs.resolution,1,4)", "Result", DefCol("resolution", "substring(bugs.resolution,1,4)", "Result",
"bugs.resolution"); "bugs.resolution");
...@@ -233,16 +782,7 @@ if (defined $::COOKIE{'COLUMNLIST'}) { ...@@ -233,16 +782,7 @@ if (defined $::COOKIE{'COLUMNLIST'}) {
my $minvotes; my $minvotes;
if (defined $::FORM{'votes'}) { if (defined $::FORM{'votes'}) {
my $c = trim($::FORM{'votes'}); if (trim($::FORM{'votes'}) ne "") {
if ($c ne "") {
if ($c !~ /^[0-9]*$/) {
print "\n\n<P>The 'At least ___ votes' field must be a simple ";
print "number. You entered \"$c\", which doesn't cut it.";
print "<P>Please click the <B>Back</B> button and try again.\n";
PutFooter();
exit;
}
$minvotes = $c;
if (! (grep {/^votes$/} @collist)) { if (! (grep {/^votes$/} @collist)) {
push(@collist, 'votes'); push(@collist, 'votes');
} }
...@@ -259,36 +799,20 @@ if ($dotweak) { ...@@ -259,36 +799,20 @@ if ($dotweak) {
} }
my $query = "select bugs.bug_id, bugs.groupset"; my @fields = ("bugs.bug_id", "bugs.groupset");
foreach my $c (@collist) { foreach my $c (@collist) {
if (exists $::needquote{$c}) { if (exists $::needquote{$c}) {
$query .= ", push(@fields, "$::key{$c}");
\t$::key{$c}";
} }
} }
if ($dotweak) { if ($dotweak) {
$query .= ", push(@fields, "bugs.product", "bugs.bug_status");
bugs.product,
bugs.bug_status";
} }
$query .= "
from bugs,
profiles assign,
profiles report
left join profiles qacont on bugs.qa_contact = qacont.userid,
versions projector
where bugs.assigned_to = assign.userid
and bugs.reporter = report.userid
and bugs.product = projector.program
and bugs.version = projector.value
and bugs.groupset & $::usergroupset = bugs.groupset
";
if ($::FORM{'regetlastlist'}) { if ($::FORM{'regetlastlist'}) {
if (!$::COOKIE{'BUGLIST'}) { if (!$::COOKIE{'BUGLIST'}) {
...@@ -309,332 +833,11 @@ query. You will have to start over at the <A HREF="query.cgi">query page</A>. ...@@ -309,332 +833,11 @@ query. You will have to start over at the <A HREF="query.cgi">query page</A>.
url_quote($::FORM{'order'}); url_quote($::FORM{'order'});
} }
if ((defined $::FORM{'emailcc1'} && $::FORM{'emailcc1'}) ||
(defined $::FORM{'emailcc2'} && $::FORM{'emailcc2'})) {
# We need to poke into the CC table. Do weird SQL left join stuff so that
# we can look in the CC table, but won't reject any bugs that don't have
# any CC fields.
$query =~ s/bugs,/bugs left join cc on bugs.bug_id = cc.bug_id left join profiles ccname on cc.who = ccname.userid,/;
}
my $needlongdescs = 0; # Whether we need to patch in the longdescs
# table.
if ($::MFORM{'bug_id'}) {
my @list = grep(!/^$/, split(/[^0-9]+/, join(',', @{$::MFORM{'bug_id'}})));
if (@list) {
my $verb = "IN";
if ($::FORM{'bugidtype'} && $::FORM{'bugidtype'} eq 'exclude') {
$verb = "NOT IN";
}
$query .= " AND bugs.bug_id $verb (" . join(',', @list) . ") ";
}
}
if (defined $::FORM{'sql'}) {
$query .= "and (\n$::FORM{'sql'}\n)"
} else {
my @legal_fields = ("product", "version", "rep_platform", "op_sys",
"bug_status", "resolution", "priority", "bug_severity",
"assigned_to", "reporter", "component",
"target_milestone", "groupset");
foreach my $field (keys %::FORM) {
my $or = "";
if (lsearch(\@legal_fields, $field) != -1 && $::FORM{$field} ne "") {
$query .= "\tand (\n";
if ($field eq "assigned_to" || $field eq "reporter") {
foreach my $p (split(/,/, $::FORM{$field})) {
my $whoid = DBNameToIdAndCheck($p);
$query .= "\t\t${or}bugs.$field = $whoid\n";
$or = "or ";
}
} else {
my $ref = $::MFORM{$field};
foreach my $v (@$ref) {
if ($v eq "(empty)") {
$query .= "\t\t${or}bugs.$field is null\n";
} else {
if ($v eq "---") {
$query .= "\t\t${or}bugs.$field = ''\n";
} else {
$query .= "\t\t${or}bugs.$field = " . SqlQuote($v) .
"\n";
}
}
$or = "or ";
}
}
$query .= "\t)\n";
}
}
}
if ($::FORM{'keywords'}) {
GetVersionTable();
my @list;
foreach my $v (split(/[\s,]+/, $::FORM{'keywords'})) {
if ($v eq '') {
next;
}
my $id = $::keywordsbyname{$v};
if ($id) {
push(@list, "keywords.keywordid = $id");
} else {
print "Unknown keyword named <code>$v</code>.\n";
print "<P>The legal keyword names are <A HREF=describekeywords.cgi>";
print "listed here</A>.\n";
print "<P>Please click the <B>Back</B> button and try again.\n";
PutFooter();
exit;
}
}
if (@list) {
$query =~ s/where/, keywords where/;
my $type = $::FORM{'keywords_type'};
my $notopt = "";
if ($type eq "nowords") {
# Ought to take advantage of keyword table somehow! ###
my $extra = GetByWordList("bugs.keywords", $::FORM{'keywords'},
"or");
$extra =~ s/AND/AND NOT/i;
$query .= $extra;
} else {
$query .= "and keywords.bug_id = bugs.bug_id and $notopt (" .
join(" or ", @list) . ")\n";
if ($type eq "allwords") {
# This needs to be tuned to take better advantage of the
# keyword table!
$query .= GetByWordList("bugs.keywords", $::FORM{'keywords'},
"and");
}
}
}
}
foreach my $id ("1", "2") {
if (!defined ($::FORM{"email$id"})) {
next;
}
my $email = trim($::FORM{"email$id"});
if ($email eq "") {
next;
}
my $qemail = SqlQuote($email);
my $type = $::FORM{"emailtype$id"};
my $emailid;
if ($type eq "exact") {
$emailid = DBNameToIdAndCheck($email);
}
my $foundone = 0;
my $lead= "and (\n";
foreach my $field ("assigned_to", "reporter", "cc", "qa_contact",
"longdesc") {
my $doit = $::FORM{"email$field$id"};
if (!$doit) {
next;
}
$foundone = 1;
my $table;
if ($field eq "assigned_to") {
$table = "assign";
} elsif ($field eq "reporter") {
$table = "report";
} elsif ($field eq "qa_contact") {
$table = "qacont";
} elsif ($field eq "longdesc") {
$table = "longdescname";
$needlongdescs = 1;
} else {
$table = "ccname";
}
if ($type eq "exact") {
if ($field eq "cc") {
$query .= "\t$lead cc.who = $emailid\n";
} elsif ($field eq "longdesc") {
$query .= "\t$lead longdescs.who = $emailid\n";
} else {
$query .= "\t$lead $field = $emailid\n";
}
} elsif ($type eq "regexp") {
$query .= "\t$lead $table.login_name regexp $qemail\n";
} elsif ($type eq "notregexp") {
$query .= "\t$lead $table.login_name not regexp $qemail\n";
} else {
$query .= "\t$lead instr($table.login_name, $qemail)\n";
}
$lead = " or ";
}
if (!$foundone) {
print "\n\n<P>You must specify one or more fields in which to search for <tt>$email</tt>.\n";
print "<P>Please click the <B>Back</B> button and try again.\n";
PutFooter();
exit;
}
if ($lead eq " or ") {
$query .= ")\n";
}
}
if (defined $::FORM{'changedin'}) {
my $c = trim($::FORM{'changedin'});
if ($c ne "") {
if ($c !~ /^[0-9]*$/) {
print "\n\n<P>The 'changed in last ___ days' field must be a simple ";
print "number. You entered \"$c\", which doesn't cut it.";
print "<P>Please click the <B>Back</B> button and try again.\n";
PutFooter();
exit;
}
$query .= "and to_days(now()) - to_days(bugs.delta_ts) <= $c ";
}
}
if (defined $minvotes) {
$query .= "and votes >= $minvotes ";
}
my $ref = $::MFORM{'chfield'};
sub SqlifyDate {
my ($str) = (@_);
if (!defined $str) {
$str = "";
}
my $date = str2time($str);
if (!defined $date) {
print "\n\n<P>The string '<tt>$str</tt>' is not a legal date.\n";
print "<P>Please click the <B>Back</B> button and try again.\n";
PutFooter();
exit;
}
return time2str("'%Y/%m/%d %H:%M:%S'", $date);
}
if (defined $ref) {
my $which = lsearch($ref, "[Bug creation]");
if ($which >= 0) {
splice(@$ref, $which, 1);
$query .= "and bugs.creation_ts >= " .
SqlifyDate($::FORM{'chfieldfrom'}) . "\n";
my $to = $::FORM{'chfieldto'};
if (defined $to) {
$to = trim($to);
if ($to ne "" && $to !~ /^now$/i) {
$query .= "and bugs.creation_ts <= " .
SqlifyDate($to) . "\n";
}
}
}
}
my $query = GenerateSQL(\@fields, undef, undef, $::buffer);
if (defined $ref && 0 < @$ref) {
# Do surgery on the query to tell it to patch in the bugs_activity
# table.
$query =~ s/where/, bugs_activity where/;
my @list;
foreach my $f (@$ref) {
push(@list, "\nbugs_activity.fieldid = " . GetFieldID($f));
}
$query .= "and bugs_activity.bug_id = bugs.bug_id and (" .
join(' or ', @list) . ") ";
$query .= "and bugs_activity.bug_when >= " .
SqlifyDate($::FORM{'chfieldfrom'}) . "\n";
my $to = $::FORM{'chfieldto'};
if (defined $to) {
$to = trim($to);
if ($to ne "" && $to !~ /^now$/i) {
$query .= "and bugs_activity.bug_when <= " . SqlifyDate($to) . "\n";
}
}
my $value = $::FORM{'chfieldvalue'};
if (defined $value) {
$value = trim($value);
if ($value ne "") {
$query .= "and bugs_activity.newvalue = " .
SqlQuote($value) . "\n";
}
}
}
sub GetByWordList {
my ($field, $strs, $verb) = (@_);
my @list;
foreach my $w (split(/[\s,]+/, $strs)) {
my $word = $w;
if ($word ne "") {
$word =~ tr/A-Z/a-z/;
$word = SqlQuote(quotemeta($word));
$word =~ s/^'//;
$word =~ s/'$//;
$word = '(^|[^a-z0-9])' . $word . '($|[^a-z0-9])';
push(@list, "lower($field) regexp '$word'");
}
}
if (0 == @list) {
return "";
}
return "and (" . join(" $verb ", @list) . ")\n";
}
foreach my $f ("short_desc", "long_desc", "bug_file_loc",
"status_whiteboard") {
if (defined $::FORM{$f}) {
my $s = trim($::FORM{$f});
if ($s ne "") {
my $n = $f;
my $q = SqlQuote($s);
my $type = $::FORM{$f . "_type"};
if ($f eq "long_desc") {
$needlongdescs = 1; # Patch in the longdescs table.
$query .= "and longdescs.bug_id = bugs.bug_id\n";
$n = "longdescs.thetext";
}
if ($type eq "regexp") {
$query .= "and $n regexp $q\n";
} elsif ($type eq "notregexp") {
$query .= "and $n not regexp $q\n";
} elsif ($type eq "casesubstring") {
$query .= "and instr($n, $q)\n";
} elsif ($type eq "allwords") {
$query .= GetByWordList($n, $s, "and");
} elsif ($type eq "anywords") {
$query .= GetByWordList($n, $s, "or");
} else {
$query .= "and instr(lower($n), lower($q))\n";
}
}
}
}
if ($needlongdescs) {
$query =~ s/where/, longdescs left join profiles longdescname on longdescs.who = longdescname.userid where/;
$query .= " AND longdescs.bug_id = bugs.bug_id ";
}
$query .= "group by bugs.bug_id\n";
if ($::COOKIE{'LASTORDER'}) { if ($::COOKIE{'LASTORDER'}) {
if ((!$::FORM{'order'}) || $::FORM{'order'} =~ /^reuse/i) { if ((!$::FORM{'order'}) || $::FORM{'order'} =~ /^reuse/i) {
...@@ -644,9 +847,12 @@ if ($::COOKIE{'LASTORDER'}) { ...@@ -644,9 +847,12 @@ if ($::COOKIE{'LASTORDER'}) {
if (defined $::FORM{'order'} && $::FORM{'order'} ne "") { if (defined $::FORM{'order'} && $::FORM{'order'} ne "") {
$query .= "order by "; $query .= " ORDER BY ";
$::FORM{'order'} =~ s/votesum/bugs.votes/; # Silly backwards compatability $::FORM{'order'} =~ s/votesum/bugs.votes/; # Silly backwards compatability
# hack. # hack.
$::FORM{'order'} =~ s/assign\.login_name/map_assigned_to.login_name/g;
# Another backwards compatability hack.
ORDER: for ($::FORM{'order'}) { ORDER: for ($::FORM{'order'}) {
/\./ && do { /\./ && do {
# This (hopefully) already has fieldnames in it, so we're done. # This (hopefully) already has fieldnames in it, so we're done.
...@@ -661,18 +867,18 @@ if (defined $::FORM{'order'} && $::FORM{'order'} ne "") { ...@@ -661,18 +867,18 @@ if (defined $::FORM{'order'} && $::FORM{'order'} ne "") {
last ORDER; last ORDER;
}; };
/Assign/ && do { /Assign/ && do {
$::FORM{'order'} = "assign.login_name, bugs.bug_status, priority, bugs.bug_id"; $::FORM{'order'} = "map_assigned_to.login_name, bugs.bug_status, priority, bugs.bug_id";
last ORDER; last ORDER;
}; };
# DEFAULT # DEFAULT
$::FORM{'order'} = "bugs.bug_status, bugs.priority, assign.login_name, bugs.bug_id"; $::FORM{'order'} = "bugs.bug_status, bugs.priority, map_assigned_to.login_name, bugs.bug_id";
} }
$query .= $::FORM{'order'}; $query .= $::FORM{'order'};
} }
if ($::FORM{'debug'} && $serverpush) { if ($::FORM{'debug'} && $serverpush) {
print "<PRE>$query</PRE>\n"; print "<P><CODE>" . value_quote($query) . "</CODE><P>\n";
} }
...@@ -866,7 +1072,7 @@ print " ...@@ -866,7 +1072,7 @@ print "
<B>" . time2str("%a %b %e %T %Z %Y", time()) . "</B>"; <B>" . time2str("%a %b %e %T %Z %Y", time()) . "</B>";
if (defined $::FORM{'debug'}) { if (defined $::FORM{'debug'}) {
print "<PRE>$query</PRE>\n"; print "<P><CODE>" . value_quote($query) . "</CODE><P>\n";
} }
if ($toolong) { if ($toolong) {
......
...@@ -882,19 +882,24 @@ AddFDef("short_desc", "Summary", 1); ...@@ -882,19 +882,24 @@ AddFDef("short_desc", "Summary", 1);
AddFDef("product", "Product", 1); AddFDef("product", "Product", 1);
AddFDef("version", "Version", 1); AddFDef("version", "Version", 1);
AddFDef("rep_platform", "Platform", 1); AddFDef("rep_platform", "Platform", 1);
AddFDef("bug_file_loc", "URL", 1);
AddFDef("op_sys", "OS/Version", 1); AddFDef("op_sys", "OS/Version", 1);
AddFDef("bug_status", "Status", 1); AddFDef("bug_status", "Status", 1);
AddFDef("status_whiteboard", "Status Whiteboard", 1);
AddFDef("keywords", "Keywords", 1);
AddFDef("resolution", "Resolution", 1); AddFDef("resolution", "Resolution", 1);
AddFDef("bug_severity", "Severity", 1); AddFDef("bug_severity", "Severity", 1);
AddFDef("priority", "Priority", 1); AddFDef("priority", "Priority", 1);
AddFDef("component", "Component", 1); AddFDef("component", "Component", 1);
AddFDef("assigned_to", "AssignedTo", 1); AddFDef("assigned_to", "AssignedTo", 1);
AddFDef("reporter", "ReportedBy", 1); AddFDef("reporter", "ReportedBy", 1);
AddFDef("votes", "Votes", 0);
AddFDef("qa_contact", "QAContact", 0); AddFDef("qa_contact", "QAContact", 0);
AddFDef("cc", "CC", 0); AddFDef("cc", "CC", 0);
AddFDef("dependson", "BugsThisDependsOn", 0); AddFDef("dependson", "BugsThisDependsOn", 0);
AddFDef("blocked", "OtherBugsDependingOnThis", 0); AddFDef("blocked", "OtherBugsDependingOnThis", 0);
AddFDef("target_milestone", "Target Milestone", 0); AddFDef("target_milestone", "Target Milestone", 0);
AddFDef("longdesc", "Comment", 0);
......
...@@ -468,7 +468,7 @@ print " ...@@ -468,7 +468,7 @@ print "
</td> </td>
<td align=left valign=top> <td align=left valign=top>
@{[make_selection_widget(\"platform\",\@::legal_platform,$default{'platform'}, $type{'platform'}, 1)]} @{[make_selection_widget(\"rep_platform\",\@::legal_platform,$default{'platform'}, $type{'platform'}, 1)]}
</td> </td>
<td align=left valign=top> <td align=left valign=top>
...@@ -663,6 +663,104 @@ print " ...@@ -663,6 +663,104 @@ print "
<p> <p>
"; ";
my @fields;
push(@fields, ["noop", "---"]);
SendSQL("SELECT name, description FROM fielddefs ORDER BY sortkey");
while (MoreSQLData()) {
my ($name, $description) = (FetchSQLData());
push(@fields, [$name, $description]);
}
my @types = (
["noop", "---"],
["equals", "equal to"],
["notequals", "not equal to"],
["casesubstring", "contains (case-sensitive) substring"],
["substring", "contains (case-insensitive) substring"],
["notsubstring", "does not contain (case-insensitive) substring"],
["regexp", "contains regexp"],
["notregexp", "does not contain regexp"],
["lessthan", "less than"],
["greaterthan", "greater than"],
["anywords", "any words"],
["allwords", "all words"],
["nowords", "none of the words"],
["changedbefore", "changed before"],
["changedafter", "changed after"],
["changedto", "changed to"],
["changedby", "changed by"],
);
foreach my $cmd (grep(/^cmd-/, keys(%::FORM))) {
if ($cmd =~ /^cmd-add(\d+)-(\d+)-(\d+)$/) {
$::FORM{"field$1-$2-$3"} = "xyzzy";
}
}
# foreach my $i (sort(keys(%::FORM))) {
# print "$i : " . value_quote($::FORM{$i}) . "<BR>\n";
# }
if (!exists $::FORM{'field0-0-0'}) {
$::FORM{'field0-0-0'} = "xyzzy";
}
print qq{<A NAME="chart"> </A>\n};
my $chart;
for ($chart=0 ; exists $::FORM{"field$chart-0-0"} ; $chart++) {
my @rows;
my $row;
for ($row = 0 ; exists $::FORM{"field$chart-$row-0"} ; $row++) {
my @cols;
my $col;
for ($col = 0 ; exists $::FORM{"field$chart-$row-$col"} ; $col++) {
my $key = "$chart-$row-$col";
my $deffield = $::FORM{"field$key"} || "";
my $deftype = $::FORM{"type$key"} || "";
my $defvalue = value_quote($::FORM{"value$key"} || "");
my $line = "";
$line .= "<TD>";
$line .= BuildPulldown("field$key", \@fields, $deffield);
$line .= BuildPulldown("type$key", \@types, $deftype);
$line .= qq{<INPUT NAME="value$key" VALUE="$defvalue">};
$line .= "</TD>\n";
push(@cols, $line);
}
push(@rows, "<TR>" . join(qq{<TD ALIGN="center"> or </TD>\n}, @cols) .
qq{<TD><INPUT TYPE="submit" VALUE="Or" NAME="cmd-add$chart-$row-$col"></TD></TR>});
}
print qq{
<HR>
<TABLE>
};
print join('<TR><TD>And</TD></TR>', @rows);
print qq{
<TR><TD><INPUT TYPE="submit" VALUE="And" NAME="cmd-add$chart-$row-0">
};
my $n = $chart + 1;
if (!exists $::FORM{"field$n-0-0"}) {
print qq{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<INPUT TYPE="submit" VALUE="Add another boolean chart" NAME="cmd-add$n-0-0">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<NOBR><A HREF="booleanchart.html">What is this stuff?</A></NOBR>
};
}
print qq{
</TD>
</TR>
</TABLE>
};
}
print qq{<HR>};
if (!$userid) { if (!$userid) {
print qq{<INPUT TYPE="hidden" NAME="cmdtype" VALUE="doit">}; print qq{<INPUT TYPE="hidden" NAME="cmdtype" VALUE="doit">};
} else { } else {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment