# DB::Schema to figure out what needs to be joined, but for some
# fields it needs a little help.
use constant COLUMN_JOINS => {
+ actual_time => {
+ table => '(SELECT bug_id, SUM(work_time) AS total'
+ . ' FROM longdescs GROUP BY bug_id)',
+ join => 'INNER',
+ },
assigned_to => {
from => 'assigned_to',
to => 'userid',
to => 'id',
join => 'INNER',
},
- actual_time => {
- table => 'longdescs',
- join => 'INNER',
- },
'flagtypes.name' => {
as => 'map_flags',
table => 'flags',
# Next we define columns that have special SQL instead of just something
# like "bugs.bug_id".
- my $actual_time = '(SUM(map_actual_time.work_time)'
- . ' * COUNT(DISTINCT map_actual_time.bug_when)/COUNT(bugs.bug_id))';
+ my $total_time = "(map_actual_time.total + bugs.remaining_time)";
my %special_sql = (
deadline => $dbh->sql_date_format('bugs.deadline', '%Y-%m-%d'),
- actual_time => $actual_time,
+ actual_time => 'map_actual_time.total',
+ # "FLOOR" is in there to turn this into an integer, making searches
+ # totally predictable. Otherwise you get floating-point numbers that
+ # are rather hard to search reliably if you're asking for exact
+ # numbers.
percentage_complete =>
- "(CASE WHEN $actual_time + bugs.remaining_time = 0.0"
- . " THEN 0.0"
- . " ELSE 100"
- . " * ($actual_time / ($actual_time + bugs.remaining_time))"
- . " END)",
+ "(CASE WHEN $total_time = 0"
+ . " THEN 0"
+ . " ELSE FLOOR(100 * (map_actual_time.total / $total_time))"
+ . " END)",
'flagtypes.name' => $dbh->sql_group_concat('DISTINCT '
. $dbh->sql_string_concat('map_flagtypes.name', 'map_flags.status')),
# is here because it *always* goes into the GROUP BY as the first item,
# so it should be skipped when determining extra GROUP BY columns.
use constant GROUP_BY_SKIP => EMPTY_COLUMN, qw(
- actual_time
bug_id
flagtypes.name
keywords
sub _percentage_complete {
my ($self, $args) = @_;
- my ($chart_id, $joins, $operator, $having, $fields) =
- @$args{qw(chart_id joins operator having fields)};
-
- my $table = "longdescs_$chart_id";
-
- # We can't just use "percentage_complete" as the field, because
- # (a) PostgreSQL doesn't accept it in the HAVING clause
- # and (b) it wouldn't work in multiple chart rows, because it uses
- # a fixed name for the table, "ldtime".
- my $expression = COLUMNS->{percentage_complete}->{name};
- $expression =~ s/\bldtime\b/$table/g;
- $args->{full_field} = "($expression)";
- push(@$joins, { table => 'longdescs', as => $table });
-
- # We need remaining_time in _select_columns, otherwise we can't use
- # it in the expression for creating percentage_complete.
- $self->_add_extra_column('remaining_time');
+
+ $args->{full_field} = COLUMNS->{percentage_complete}->{name};
- $self->_do_operator_function($args);
- push(@$having, $args->{term});
-
- # We put something into $args->{term} so that do_search_function
- # stops processing.
- $args->{term} = '';
+ # We need actual_time in _select_columns, otherwise we can't use
+ # it in the expression for searching percentage_complete.
+ $self->_add_extra_column('actual_time');
}
sub _bug_group_nonchanged {
# right in OR tests, and it's too much work to document the exact tests
# that they cause to fail.
use constant OR_SKIP => qw(
- percentage_complete
flagtypes.name
);
deadline => -5,
creation_ts => -8,
delta_ts => -8,
- percentage_complete => 7,
+ percentage_complete => 1,
work_time => 3,
remaining_time => 3,
target_milestone => 15,
work_time => { contains => [2,3,4] },
},
- greaterthan => { GREATERTHAN_BROKEN },
-
- # percentage_complete is broken -- it won't match equal values.
- greaterthaneq => {
- GREATERTHAN_BROKEN,
- percentage_complete => { contains => [2] },
- },
-
- # percentage_complete doesn't do a numeric comparison, so
- # it doesn't find decimal values.
- anyexact => {
- percentage_complete => { contains => [2] },
- },
+ greaterthan => { GREATERTHAN_BROKEN },
+ greaterthaneq => { GREATERTHAN_BROKEN },
'allwordssubstr-<1>' => { ALLWORDS_BROKEN },
# flagtypes.name does not work here, probably because they all try to
work_time => { contains => [1] },
},
'anywords-<1> <2>' => {
- percentage_complete => { contains => [2] },
work_time => { contains => [1,2] },
},
notregexp => { contains => [5] },
nowords => { contains => [5] },
},
- percentage_complete => {
- 'allwordssubstr-<1>' => { contains => [3] },
- anywordssubstr => { contains => [2,3] },
- casesubstring => { contains => [3] },
- 'notregexp-<1>' => { contains => [3] },
- notsubstring => { contains => [3] },
- nowordssubstr => { contains => [3] },
- 'regexp-<1>' => { contains => [3] },
- substring => { contains => [3] },
- },
};
###################
"flagtypes.name" => { contains => [5] },
"keywords" => { contains => [5] },
"longdescs.isprivate" => { contains => [1] },
- "percentage_complete" => { contains => [1 .. 5] },
"see_also" => { contains => [5] },
FIELD_TYPE_BUG_ID, { contains => [5] },
FIELD_TYPE_DATETIME, { contains => [5] },
"classification" => { contains => [1] },
"commenter" => { contains => [1] },
"delta_ts" => { contains => [1] },
- "percentage_complete" => { contains => [1] },
+ percentage_complete => { contains => [1] },
"requestees.login_name" => { contains => [1] },
"setters.login_name" => { contains => [1] },
"work_time" => { contains => [1] },
"bug_group" => { contains => [5] },
"dependson" => { contains => [2, 4, 5] },
"flagtypes.name" => { contains => [1 .. 5] },
- "percentage_complete" => { contains => [1 .. 5] },
);
# These are field/operator combinations that are broken when run under NOT().
},
'anyexact-<1>, <2>' => {
bug_group => { contains => [1] },
- percentage_complete => { contains => [1,3,4,5] },
keywords => { contains => [1,5] },
see_also => { },
FIELD_TYPE_MULTI_SELECT, { },
},
'anywords-<1> <2>' => {
'attach_data.thedata' => { contains => [5] },
- "percentage_complete" => { contains => [1,3,4,5] },
work_time => { contains => [1,2] },
},
anywordssubstr => {
"work_time" => { contains => [1, 2] },
},
'anywordssubstr-<1> <2>' => {
- percentage_complete => { contains => [1,2,3,4,5] },
FIELD_TYPE_MULTI_SELECT, { contains => [5] },
},
casesubstring => {
"attach_data.thedata" => { contains => [2, 3, 4] },
"classification" => { contains => [2, 3, 4] },
"commenter" => { contains => [2, 3, 4] },
+ percentage_complete => { contains => [2, 3, 4] },
"delta_ts" => { contains => [2, 3, 4] },
- "percentage_complete" => { contains => [2, 3, 4] },
"requestees.login_name" => { contains => [2, 3, 4] },
"setters.login_name" => { contains => [2, 3, 4] },
},
cc => { contains => [1] },
"flagtypes.name" => { contains => [2, 5] },
"work_time" => { contains => [2, 3, 4] },
- percentage_complete => { contains => [1,3,4,5] },,
FIELD_TYPE_MULTI_SELECT, { contains => [5] },
},
lessthan => {
notsubstring => { NEGATIVE_BROKEN_NOT },
nowords => {
NEGATIVE_BROKEN_NOT,
- "work_time" => { contains => [2, 3, 4] },
+ "work_time" => { contains => [2, 3, 4] },
"flagtypes.name" => { },
},
nowordssubstr => {
NEGATIVE_BROKEN_NOT,
"attach_data.thedata" => { },
"flagtypes.name" => { },
- "work_time" => { contains => [2, 3, 4] },
+ "work_time" => { contains => [2, 3, 4] },
},
regexp => {
COMMON_BROKEN_NOT,
remaining_time => { value => '^9.0' },
work_time => { value => '^1.0' },
longdesc => { value => '^1-' },
- percentage_complete => { value => '^10.0' },
+ percentage_complete => { value => '^10' },
FIELD_TYPE_BUG_ID, { value => '^<1>$' },
FIELD_TYPE_DATETIME, { value => '^2037-03-01' }
};
{ contains => [2,3,4,5], value => '<1>' },
],
substring => [
- { contains => [1], value => '<1>' },
+ { contains => [1], value => '<1>',
+ override => {
+ percentage_complete => { contains => [1,2,3] },
+ }
+ },
],
casesubstring => [
- { contains => [1], value => '<1>' },
+ { contains => [1], value => '<1>',
+ override => {
+ percentage_complete => { contains => [1,2,3] },
+ }
+ },
{ contains => [], value => '<1>', transform => sub { lc($_[0]) },
- extra_name => 'lc', if_equal => { contains => [1] } },
+ extra_name => 'lc', if_equal => { contains => [1] },
+ override => {
+ percentage_complete => { contains => [1,2,3] },
+ }
+ },
],
notsubstring => [
- { contains => [2,3,4,5], value => '<1>' },
+ { contains => [2,3,4,5], value => '<1>',
+ override => {
+ percentage_complete => { contains => [4,5] },
+ },
+ }
],
regexp => [
- { contains => [1], value => '<1>', escape => 1 },
+ { contains => [1], value => '<1>', escape => 1,
+ override => {
+ percentage_complete => { value => '^10' },
+ }
+ },
{ contains => [1], value => '^1-', override => REGEX_OVERRIDE },
],
notregexp => [
- { contains => [2,3,4,5], value => '<1>', escape => 1 },
+ { contains => [2,3,4,5], value => '<1>', escape => 1,
+ override => {
+ percentage_complete => { value => '^10' },
+ }
+ },
{ contains => [2,3,4,5], value => '^1-', override => REGEX_OVERRIDE },
],
lessthan => [
],
anywordssubstr => [
{ contains => [1,2], value => '<1> <2>',
- override => { ANY_OVERRIDE } },
+ override => {
+ ANY_OVERRIDE,
+ percentage_complete => { contains => [1,2,3] },
+ }
+ },
],
allwordssubstr => [
{ contains => [1], value => '<1>',
- override => { MULTI_BOOLEAN_OVERRIDE } },
+ override => {
+ MULTI_BOOLEAN_OVERRIDE,
+ # We search just the number "1" for percentage_complete,
+ # which matches a lot of bugs.
+ percentage_complete => { contains => [1,2,3] },
+ },
+ },
{ contains => [], value => '<1>,<2>',
override => {
dependson => { value => '<1-id> <3-id>', contains => [] },
+ # bug 3 has the value "21" here, so matches "2,1"
+ percentage_complete => { value => '<2>,<3>', contains => [3] }
}
},
],
# longdescs.isprivate translates to "1 0", so no bugs should
# show up.
'longdescs.isprivate' => { contains => [] },
+ percentage_complete => { contains => [4,5] },
# 1.0 0.0 exludes bug 5.
# XXX However, it also shouldn't match 2, 3, or 4, because
# they contain at least one comment with 0.0 work_time.