From: terry%mozilla.org <> Date: Fri, 28 Jan 2000 09:01:36 +0000 (+0000) Subject: Massive stomp on the query page and buglist page. Added the ability X-Git-Tag: bugzilla-2.12~411 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8fdb0d3601e63fb8a07bff32945fb5d84fbd4678;p=thirdparty%2Fbugzilla.git Massive stomp on the query page and buglist page. Added the ability to use the "boolean charts" to do very powerful queries. --- diff --git a/CGI.pl b/CGI.pl index 770a8d98bd..a5c8c73213 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 0000000000..5834c80b7c --- /dev/null +++ b/booleanchart.html @@ -0,0 +1,79 @@ +
++ +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). +
+So. +
+ +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". + +
+ +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. + +
+ +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. + +
+ +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.) + +
+ +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. + +
+ +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. + +
+ +Clear as mud? Please, I beg you, rewrite this document to make +everything crystal clear, and send the improved version to Terry. + +
The string '$str' is not a legal date.\n"; + print "
Please click the Back 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
Please press Back 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 $email.\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 $v
.\n" .
+ "
The legal keyword names are\n" . + "" . + "listed here.\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{
$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 "
$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{'$F{"field$chart-$row-$col"}
' and } .
+ qq{'$F{"type$chart-$row-$col"}
' } .
+ "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 "
" . value_quote($query) . "
\n";
+ exit();
+ }
+ return $query;
+}
+
+
+
sub LookupNamedQuery {
my ($name) = (@_);
confirm_login();
@@ -164,7 +712,6 @@ OK, you have a new query named $name
}
-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
The 'At least ___ votes' field must be a simple "; - print "number. You entered \"$c\", which doesn't cut it."; - print "
Please click the Back 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 query page.
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 $v
.\n";
- print "
The legal keyword names are "; - print "listed here.\n"; - print "
Please click the Back 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
You must specify one or more fields in which to search for $email.\n"; - print "
Please click the Back 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
The 'changed in last ___ days' field must be a simple "; - print "number. You entered \"$c\", which doesn't cut it."; - print "
Please click the Back 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
The string '$str' is not a legal date.\n"; - print "
Please click the Back 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 "
$query\n"; + print "
" . value_quote($query) . "
\n"; } @@ -866,7 +1072,7 @@ print " " . time2str("%a %b %e %T %Z %Y", time()) . ""; if (defined $::FORM{'debug'}) { - print "
$query\n"; + print "
" . value_quote($query) . "
\n"; } if ($toolong) { diff --git a/checksetup.pl b/checksetup.pl index f3937c0d16..ed25386682 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 5ed72da9b5..6e3b2bd668 100755 --- a/query.cgi +++ b/query.cgi @@ -468,7 +468,7 @@ print "
";
+
+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}) . "
\n";
+# }
+
+
+if (!exists $::FORM{'field0-0-0'}) {
+ $::FORM{'field0-0-0'} = "xyzzy";
+}
+
+print qq{ \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 .= "
And |
+};
+ my $n = $chart + 1;
+ if (!exists $::FORM{"field$n-0-0"}) {
+ print qq{
+
+
+
+ |
+