summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorterry%mozilla.org <>2000-01-28 10:01:36 +0100
committerterry%mozilla.org <>2000-01-28 10:01:36 +0100
commit8fdb0d3601e63fb8a07bff32945fb5d84fbd4678 (patch)
treedbed4ea3cefbe7b317f3f997860e12528499eb0a
parentb23cb23b54adf11260e69dbc0c6294702ea42159 (diff)
downloadbugzilla-8fdb0d3601e63fb8a07bff32945fb5d84fbd4678.tar.gz
bugzilla-8fdb0d3601e63fb8a07bff32945fb5d84fbd4678.tar.xz
Massive stomp on the query page and buglist page. Added the ability
to use the "boolean charts" to do very powerful queries.
-rw-r--r--CGI.pl30
-rw-r--r--booleanchart.html79
-rwxr-xr-xbuglist.cgi928
-rwxr-xr-xchecksetup.pl5
-rwxr-xr-xquery.cgi100
5 files changed, 768 insertions, 374 deletions
diff --git a/CGI.pl b/CGI.pl
index 770a8d98b..a5c8c7321 100644
--- a/CGI.pl
+++ b/CGI.pl
@@ -78,10 +78,10 @@ sub url_quote {
}
-sub ProcessFormFields {
- my ($buffer) = (@_);
- undef %::FORM;
- undef %::MFORM;
+sub ParseUrlString {
+ my ($buffer, $f, $m) = (@_);
+ undef %$f;
+ undef %$m;
my %isnull;
my $remaining = $buffer;
@@ -105,13 +105,13 @@ sub ProcessFormFields {
$value = "";
}
if ($value ne "") {
- if (defined $::FORM{$name}) {
- $::FORM{$name} .= $value;
- my $ref = $::MFORM{$name};
+ if (defined $f->{$name}) {
+ $f->{$name} .= $value;
+ my $ref = $m->{$name};
push @$ref, $value;
} else {
- $::FORM{$name} = $value;
- $::MFORM{$name} = [$value];
+ $f->{$name} = $value;
+ $m->{$name} = [$value];
}
} else {
$isnull{$name} = 1;
@@ -119,15 +119,21 @@ sub ProcessFormFields {
}
if (defined %isnull) {
foreach my $name (keys(%isnull)) {
- if (!defined $::FORM{$name}) {
- $::FORM{$name} = "";
- $::MFORM{$name} = [];
+ if (!defined $f->{$name}) {
+ $f->{$name} = "";
+ $m->{$name} = [];
}
}
}
}
+sub ProcessFormFields {
+ my ($buffer) = (@_);
+ return ParseUrlString($buffer, \%::FORM, \%::MFORM);
+}
+
+
sub ProcessMultipartFormFields {
my ($boundary) = (@_);
$boundary =~ s/^-*//;
diff --git a/booleanchart.html b/booleanchart.html
new file mode 100644
index 000000000..5834c80b7
--- /dev/null
+++ b/booleanchart.html
@@ -0,0 +1,79 @@
+<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>
diff --git a/buglist.cgi b/buglist.cgi
index 5cae83b87..902d09f12 100755
--- a/buglist.cgi
+++ b/buglist.cgi
@@ -46,17 +46,565 @@ sub sillyness {
$zz = @::versions;
};
-
+my $serverpush = 0;
ConnectToDatabase();
# 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'}) {
# This can happen if there's an old bookmark to a query...
$::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 {
my ($name) = (@_);
confirm_login();
@@ -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/ ) {
# 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
@@ -205,9 +752,11 @@ DefCol("severity", "substring(bugs.bug_severity, 1, 3)", "Sev",
DefCol("priority", "substring(bugs.priority, 1, 3)", "Pri", "bugs.priority");
DefCol("platform", "substring(bugs.rep_platform, 1, 3)", "Plt",
"bugs.rep_platform");
-DefCol("owner", "assign.login_name", "Owner", "assign.login_name");
-DefCol("reporter", "report.login_name", "Reporter", "report.login_name");
-DefCol("qa_contact", "qacont.login_name", "QAContact", "qacont.login_name");
+DefCol("owner", "map_assigned_to.login_name", "Owner",
+ "map_assigned_to.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("resolution", "substring(bugs.resolution,1,4)", "Result",
"bugs.resolution");
@@ -233,16 +782,7 @@ if (defined $::COOKIE{'COLUMNLIST'}) {
my $minvotes;
if (defined $::FORM{'votes'}) {
- my $c = trim($::FORM{'votes'});
- 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 (trim($::FORM{'votes'}) ne "") {
if (! (grep {/^votes$/} @collist)) {
push(@collist, 'votes');
}
@@ -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) {
if (exists $::needquote{$c}) {
- $query .= ",
-\t$::key{$c}";
+ push(@fields, "$::key{$c}");
}
}
if ($dotweak) {
- $query .= ",
-bugs.product,
-bugs.bug_status";
+ push(@fields, "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 (!$::COOKIE{'BUGLIST'}) {
@@ -309,331 +833,10 @@ query. You will have to start over at the <A HREF="query.cgi">query page</A>.
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";
- }
-}
-
-
+my $query = GenerateSQL(\@fields, undef, undef, $::buffer);
-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";
- }
- }
- }
-}
-
-
-
-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'}) {
@@ -644,9 +847,12 @@ if ($::COOKIE{'LASTORDER'}) {
if (defined $::FORM{'order'} && $::FORM{'order'} ne "") {
- $query .= "order by ";
+ $query .= " ORDER BY ";
$::FORM{'order'} =~ s/votesum/bugs.votes/; # Silly backwards compatability
# hack.
+ $::FORM{'order'} =~ s/assign\.login_name/map_assigned_to.login_name/g;
+ # Another backwards compatability hack.
+
ORDER: for ($::FORM{'order'}) {
/\./ && do {
# This (hopefully) already has fieldnames in it, so we're done.
@@ -661,18 +867,18 @@ if (defined $::FORM{'order'} && $::FORM{'order'} ne "") {
last ORDER;
};
/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;
};
# 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'};
}
if ($::FORM{'debug'} && $serverpush) {
- print "<PRE>$query</PRE>\n";
+ print "<P><CODE>" . value_quote($query) . "</CODE><P>\n";
}
@@ -866,7 +1072,7 @@ print "
<B>" . time2str("%a %b %e %T %Z %Y", time()) . "</B>";
if (defined $::FORM{'debug'}) {
- print "<PRE>$query</PRE>\n";
+ print "<P><CODE>" . value_quote($query) . "</CODE><P>\n";
}
if ($toolong) {
diff --git a/checksetup.pl b/checksetup.pl
index f3937c0d1..ed2538668 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -882,19 +882,24 @@ AddFDef("short_desc", "Summary", 1);
AddFDef("product", "Product", 1);
AddFDef("version", "Version", 1);
AddFDef("rep_platform", "Platform", 1);
+AddFDef("bug_file_loc", "URL", 1);
AddFDef("op_sys", "OS/Version", 1);
AddFDef("bug_status", "Status", 1);
+AddFDef("status_whiteboard", "Status Whiteboard", 1);
+AddFDef("keywords", "Keywords", 1);
AddFDef("resolution", "Resolution", 1);
AddFDef("bug_severity", "Severity", 1);
AddFDef("priority", "Priority", 1);
AddFDef("component", "Component", 1);
AddFDef("assigned_to", "AssignedTo", 1);
AddFDef("reporter", "ReportedBy", 1);
+AddFDef("votes", "Votes", 0);
AddFDef("qa_contact", "QAContact", 0);
AddFDef("cc", "CC", 0);
AddFDef("dependson", "BugsThisDependsOn", 0);
AddFDef("blocked", "OtherBugsDependingOnThis", 0);
AddFDef("target_milestone", "Target Milestone", 0);
+AddFDef("longdesc", "Comment", 0);
diff --git a/query.cgi b/query.cgi
index 5ed72da9b..6e3b2bd66 100755
--- a/query.cgi
+++ b/query.cgi
@@ -468,7 +468,7 @@ print "
</td>
<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 align=left valign=top>
@@ -663,6 +663,104 @@ print "
<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) {
print qq{<INPUT TYPE="hidden" NAME="cmdtype" VALUE="doit">};
} else {