diff options
author | terry%mozilla.org <> | 2000-01-28 10:01:36 +0100 |
---|---|---|
committer | terry%mozilla.org <> | 2000-01-28 10:01:36 +0100 |
commit | 8fdb0d3601e63fb8a07bff32945fb5d84fbd4678 (patch) | |
tree | dbed4ea3cefbe7b317f3f997860e12528499eb0a | |
parent | b23cb23b54adf11260e69dbc0c6294702ea42159 (diff) | |
download | bugzilla-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.pl | 30 | ||||
-rw-r--r-- | booleanchart.html | 79 | ||||
-rwxr-xr-x | buglist.cgi | 928 | ||||
-rwxr-xr-x | checksetup.pl | 5 | ||||
-rwxr-xr-x | query.cgi | 100 |
5 files changed, 768 insertions, 374 deletions
@@ -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); @@ -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{ + +<INPUT TYPE="submit" VALUE="Add another boolean chart" NAME="cmd-add$n-0-0"> + +<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 { |