Commit daa533e7 authored by Max Kanat-Alexander's avatar Max Kanat-Alexander

Bug 601848: Fix percentage_complete searches for all operators on both MySQL

and PostgreSQL r=mkanat, a=mkanat (module owner)
parent e861a6de
...@@ -3327,7 +3327,10 @@ sub percentage_complete { ...@@ -3327,7 +3327,10 @@ sub percentage_complete {
my $actual = $self->actual_time; my $actual = $self->actual_time;
my $total = $remaining + $actual; my $total = $remaining + $actual;
return undef if $total == 0; return undef if $total == 0;
return 100 * ($actual / $total); # Search.pm truncates this value to an integer, so we want to as well,
# since this is mostly used in a test where its value needs to be
# identical to what the database will return.
return int(100 * ($actual / $total));
} }
sub product { sub product {
......
...@@ -406,6 +406,11 @@ use constant COLUMN_DEPENDS => { ...@@ -406,6 +406,11 @@ use constant COLUMN_DEPENDS => {
# DB::Schema to figure out what needs to be joined, but for some # DB::Schema to figure out what needs to be joined, but for some
# fields it needs a little help. # fields it needs a little help.
use constant COLUMN_JOINS => { 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 => { assigned_to => {
from => 'assigned_to', from => 'assigned_to',
to => 'userid', to => 'userid',
...@@ -441,10 +446,6 @@ use constant COLUMN_JOINS => { ...@@ -441,10 +446,6 @@ use constant COLUMN_JOINS => {
to => 'id', to => 'id',
join => 'INNER', join => 'INNER',
}, },
actual_time => {
table => 'longdescs',
join => 'INNER',
},
'flagtypes.name' => { 'flagtypes.name' => {
as => 'map_flags', as => 'map_flags',
table => 'flags', table => 'flags',
...@@ -504,18 +505,20 @@ sub COLUMNS { ...@@ -504,18 +505,20 @@ sub COLUMNS {
# Next we define columns that have special SQL instead of just something # Next we define columns that have special SQL instead of just something
# like "bugs.bug_id". # like "bugs.bug_id".
my $actual_time = '(SUM(map_actual_time.work_time)' my $total_time = "(map_actual_time.total + bugs.remaining_time)";
. ' * COUNT(DISTINCT map_actual_time.bug_when)/COUNT(bugs.bug_id))';
my %special_sql = ( my %special_sql = (
deadline => $dbh->sql_date_format('bugs.deadline', '%Y-%m-%d'), 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 => percentage_complete =>
"(CASE WHEN $actual_time + bugs.remaining_time = 0.0" "(CASE WHEN $total_time = 0"
. " THEN 0.0" . " THEN 0"
. " ELSE 100" . " ELSE FLOOR(100 * (map_actual_time.total / $total_time))"
. " * ($actual_time / ($actual_time + bugs.remaining_time))" . " END)",
. " END)",
'flagtypes.name' => $dbh->sql_group_concat('DISTINCT ' 'flagtypes.name' => $dbh->sql_group_concat('DISTINCT '
. $dbh->sql_string_concat('map_flagtypes.name', 'map_flags.status')), . $dbh->sql_string_concat('map_flagtypes.name', 'map_flags.status')),
...@@ -614,7 +617,6 @@ sub REPORT_COLUMNS { ...@@ -614,7 +617,6 @@ sub REPORT_COLUMNS {
# is here because it *always* goes into the GROUP BY as the first item, # 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. # so it should be skipped when determining extra GROUP BY columns.
use constant GROUP_BY_SKIP => EMPTY_COLUMN, qw( use constant GROUP_BY_SKIP => EMPTY_COLUMN, qw(
actual_time
bug_id bug_id
flagtypes.name flagtypes.name
keywords keywords
...@@ -2240,30 +2242,12 @@ sub _work_time { ...@@ -2240,30 +2242,12 @@ sub _work_time {
sub _percentage_complete { sub _percentage_complete {
my ($self, $args) = @_; my ($self, $args) = @_;
my ($chart_id, $joins, $operator, $having, $fields) =
@$args{qw(chart_id joins operator having fields)}; $args->{full_field} = COLUMNS->{percentage_complete}->{name};
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');
$self->_do_operator_function($args); # We need actual_time in _select_columns, otherwise we can't use
push(@$having, $args->{term}); # it in the expression for searching percentage_complete.
$self->_add_extra_column('actual_time');
# We put something into $args->{term} so that do_search_function
# stops processing.
$args->{term} = '';
} }
sub _bug_group_nonchanged { sub _bug_group_nonchanged {
......
...@@ -140,7 +140,6 @@ use constant SKIP_FIELDS => qw( ...@@ -140,7 +140,6 @@ use constant SKIP_FIELDS => qw(
# right in OR tests, and it's too much work to document the exact tests # right in OR tests, and it's too much work to document the exact tests
# that they cause to fail. # that they cause to fail.
use constant OR_SKIP => qw( use constant OR_SKIP => qw(
percentage_complete
flagtypes.name flagtypes.name
); );
...@@ -167,7 +166,7 @@ use constant FIELD_SUBSTR_SIZE => { ...@@ -167,7 +166,7 @@ use constant FIELD_SUBSTR_SIZE => {
deadline => -5, deadline => -5,
creation_ts => -8, creation_ts => -8,
delta_ts => -8, delta_ts => -8,
percentage_complete => 7, percentage_complete => 1,
work_time => 3, work_time => 3,
remaining_time => 3, remaining_time => 3,
target_milestone => 15, target_milestone => 15,
...@@ -361,19 +360,8 @@ use constant KNOWN_BROKEN => { ...@@ -361,19 +360,8 @@ use constant KNOWN_BROKEN => {
work_time => { contains => [2,3,4] }, work_time => { contains => [2,3,4] },
}, },
greaterthan => { GREATERTHAN_BROKEN }, greaterthan => { GREATERTHAN_BROKEN },
greaterthaneq => { 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] },
},
'allwordssubstr-<1>' => { ALLWORDS_BROKEN }, 'allwordssubstr-<1>' => { ALLWORDS_BROKEN },
# flagtypes.name does not work here, probably because they all try to # flagtypes.name does not work here, probably because they all try to
...@@ -399,7 +387,6 @@ use constant KNOWN_BROKEN => { ...@@ -399,7 +387,6 @@ use constant KNOWN_BROKEN => {
work_time => { contains => [1] }, work_time => { contains => [1] },
}, },
'anywords-<1> <2>' => { 'anywords-<1> <2>' => {
percentage_complete => { contains => [2] },
work_time => { contains => [1,2] }, work_time => { contains => [1,2] },
}, },
...@@ -481,16 +468,6 @@ use constant PG_BROKEN => { ...@@ -481,16 +468,6 @@ use constant PG_BROKEN => {
notregexp => { contains => [5] }, notregexp => { contains => [5] },
nowords => { 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] },
},
}; };
################### ###################
...@@ -512,7 +489,6 @@ use constant COMMON_BROKEN_NOT => ( ...@@ -512,7 +489,6 @@ use constant COMMON_BROKEN_NOT => (
"flagtypes.name" => { contains => [5] }, "flagtypes.name" => { contains => [5] },
"keywords" => { contains => [5] }, "keywords" => { contains => [5] },
"longdescs.isprivate" => { contains => [1] }, "longdescs.isprivate" => { contains => [1] },
"percentage_complete" => { contains => [1 .. 5] },
"see_also" => { contains => [5] }, "see_also" => { contains => [5] },
FIELD_TYPE_BUG_ID, { contains => [5] }, FIELD_TYPE_BUG_ID, { contains => [5] },
FIELD_TYPE_DATETIME, { contains => [5] }, FIELD_TYPE_DATETIME, { contains => [5] },
...@@ -527,7 +503,7 @@ use constant CHANGED_BROKEN_NOT => ( ...@@ -527,7 +503,7 @@ use constant CHANGED_BROKEN_NOT => (
"classification" => { contains => [1] }, "classification" => { contains => [1] },
"commenter" => { contains => [1] }, "commenter" => { contains => [1] },
"delta_ts" => { contains => [1] }, "delta_ts" => { contains => [1] },
"percentage_complete" => { contains => [1] }, percentage_complete => { contains => [1] },
"requestees.login_name" => { contains => [1] }, "requestees.login_name" => { contains => [1] },
"setters.login_name" => { contains => [1] }, "setters.login_name" => { contains => [1] },
"work_time" => { contains => [1] }, "work_time" => { contains => [1] },
...@@ -549,7 +525,6 @@ use constant NEGATIVE_BROKEN_NOT => ( ...@@ -549,7 +525,6 @@ use constant NEGATIVE_BROKEN_NOT => (
"bug_group" => { contains => [5] }, "bug_group" => { contains => [5] },
"dependson" => { contains => [2, 4, 5] }, "dependson" => { contains => [2, 4, 5] },
"flagtypes.name" => { contains => [1 .. 5] }, "flagtypes.name" => { contains => [1 .. 5] },
"percentage_complete" => { contains => [1 .. 5] },
); );
# These are field/operator combinations that are broken when run under NOT(). # These are field/operator combinations that are broken when run under NOT().
...@@ -603,7 +578,6 @@ use constant BROKEN_NOT => { ...@@ -603,7 +578,6 @@ use constant BROKEN_NOT => {
}, },
'anyexact-<1>, <2>' => { 'anyexact-<1>, <2>' => {
bug_group => { contains => [1] }, bug_group => { contains => [1] },
percentage_complete => { contains => [1,3,4,5] },
keywords => { contains => [1,5] }, keywords => { contains => [1,5] },
see_also => { }, see_also => { },
FIELD_TYPE_MULTI_SELECT, { }, FIELD_TYPE_MULTI_SELECT, { },
...@@ -616,7 +590,6 @@ use constant BROKEN_NOT => { ...@@ -616,7 +590,6 @@ use constant BROKEN_NOT => {
}, },
'anywords-<1> <2>' => { 'anywords-<1> <2>' => {
'attach_data.thedata' => { contains => [5] }, 'attach_data.thedata' => { contains => [5] },
"percentage_complete" => { contains => [1,3,4,5] },
work_time => { contains => [1,2] }, work_time => { contains => [1,2] },
}, },
anywordssubstr => { anywordssubstr => {
...@@ -624,7 +597,6 @@ use constant BROKEN_NOT => { ...@@ -624,7 +597,6 @@ use constant BROKEN_NOT => {
"work_time" => { contains => [1, 2] }, "work_time" => { contains => [1, 2] },
}, },
'anywordssubstr-<1> <2>' => { 'anywordssubstr-<1> <2>' => {
percentage_complete => { contains => [1,2,3,4,5] },
FIELD_TYPE_MULTI_SELECT, { contains => [5] }, FIELD_TYPE_MULTI_SELECT, { contains => [5] },
}, },
casesubstring => { casesubstring => {
...@@ -645,8 +617,8 @@ use constant BROKEN_NOT => { ...@@ -645,8 +617,8 @@ use constant BROKEN_NOT => {
"attach_data.thedata" => { contains => [2, 3, 4] }, "attach_data.thedata" => { contains => [2, 3, 4] },
"classification" => { contains => [2, 3, 4] }, "classification" => { contains => [2, 3, 4] },
"commenter" => { contains => [2, 3, 4] }, "commenter" => { contains => [2, 3, 4] },
percentage_complete => { contains => [2, 3, 4] },
"delta_ts" => { contains => [2, 3, 4] }, "delta_ts" => { contains => [2, 3, 4] },
"percentage_complete" => { contains => [2, 3, 4] },
"requestees.login_name" => { contains => [2, 3, 4] }, "requestees.login_name" => { contains => [2, 3, 4] },
"setters.login_name" => { contains => [2, 3, 4] }, "setters.login_name" => { contains => [2, 3, 4] },
}, },
...@@ -693,7 +665,6 @@ use constant BROKEN_NOT => { ...@@ -693,7 +665,6 @@ use constant BROKEN_NOT => {
cc => { contains => [1] }, cc => { contains => [1] },
"flagtypes.name" => { contains => [2, 5] }, "flagtypes.name" => { contains => [2, 5] },
"work_time" => { contains => [2, 3, 4] }, "work_time" => { contains => [2, 3, 4] },
percentage_complete => { contains => [1,3,4,5] },,
FIELD_TYPE_MULTI_SELECT, { contains => [5] }, FIELD_TYPE_MULTI_SELECT, { contains => [5] },
}, },
lessthan => { lessthan => {
...@@ -717,14 +688,14 @@ use constant BROKEN_NOT => { ...@@ -717,14 +688,14 @@ use constant BROKEN_NOT => {
notsubstring => { NEGATIVE_BROKEN_NOT }, notsubstring => { NEGATIVE_BROKEN_NOT },
nowords => { nowords => {
NEGATIVE_BROKEN_NOT, NEGATIVE_BROKEN_NOT,
"work_time" => { contains => [2, 3, 4] }, "work_time" => { contains => [2, 3, 4] },
"flagtypes.name" => { }, "flagtypes.name" => { },
}, },
nowordssubstr => { nowordssubstr => {
NEGATIVE_BROKEN_NOT, NEGATIVE_BROKEN_NOT,
"attach_data.thedata" => { }, "attach_data.thedata" => { },
"flagtypes.name" => { }, "flagtypes.name" => { },
"work_time" => { contains => [2, 3, 4] }, "work_time" => { contains => [2, 3, 4] },
}, },
regexp => { regexp => {
COMMON_BROKEN_NOT, COMMON_BROKEN_NOT,
...@@ -774,7 +745,7 @@ use constant REGEX_OVERRIDE => { ...@@ -774,7 +745,7 @@ use constant REGEX_OVERRIDE => {
remaining_time => { value => '^9.0' }, remaining_time => { value => '^9.0' },
work_time => { value => '^1.0' }, work_time => { value => '^1.0' },
longdesc => { value => '^1-' }, longdesc => { value => '^1-' },
percentage_complete => { value => '^10.0' }, percentage_complete => { value => '^10' },
FIELD_TYPE_BUG_ID, { value => '^<1>$' }, FIELD_TYPE_BUG_ID, { value => '^<1>$' },
FIELD_TYPE_DATETIME, { value => '^2037-03-01' } FIELD_TYPE_DATETIME, { value => '^2037-03-01' }
}; };
...@@ -915,22 +886,46 @@ use constant TESTS => { ...@@ -915,22 +886,46 @@ use constant TESTS => {
{ contains => [2,3,4,5], value => '<1>' }, { contains => [2,3,4,5], value => '<1>' },
], ],
substring => [ substring => [
{ contains => [1], value => '<1>' }, { contains => [1], value => '<1>',
override => {
percentage_complete => { contains => [1,2,3] },
}
},
], ],
casesubstring => [ casesubstring => [
{ contains => [1], value => '<1>' }, { contains => [1], value => '<1>',
override => {
percentage_complete => { contains => [1,2,3] },
}
},
{ contains => [], value => '<1>', transform => sub { lc($_[0]) }, { 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 => [ notsubstring => [
{ contains => [2,3,4,5], value => '<1>' }, { contains => [2,3,4,5], value => '<1>',
override => {
percentage_complete => { contains => [4,5] },
},
}
], ],
regexp => [ 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 }, { contains => [1], value => '^1-', override => REGEX_OVERRIDE },
], ],
notregexp => [ 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 }, { contains => [2,3,4,5], value => '^1-', override => REGEX_OVERRIDE },
], ],
lessthan => [ lessthan => [
...@@ -1031,14 +1026,26 @@ use constant TESTS => { ...@@ -1031,14 +1026,26 @@ use constant TESTS => {
], ],
anywordssubstr => [ anywordssubstr => [
{ contains => [1,2], value => '<1> <2>', { contains => [1,2], value => '<1> <2>',
override => { ANY_OVERRIDE } }, override => {
ANY_OVERRIDE,
percentage_complete => { contains => [1,2,3] },
}
},
], ],
allwordssubstr => [ allwordssubstr => [
{ contains => [1], value => '<1>', { 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>', { contains => [], value => '<1>,<2>',
override => { override => {
dependson => { value => '<1-id> <3-id>', contains => [] }, 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] }
} }
}, },
], ],
...@@ -1048,6 +1055,7 @@ use constant TESTS => { ...@@ -1048,6 +1055,7 @@ use constant TESTS => {
# longdescs.isprivate translates to "1 0", so no bugs should # longdescs.isprivate translates to "1 0", so no bugs should
# show up. # show up.
'longdescs.isprivate' => { contains => [] }, 'longdescs.isprivate' => { contains => [] },
percentage_complete => { contains => [4,5] },
# 1.0 0.0 exludes bug 5. # 1.0 0.0 exludes bug 5.
# XXX However, it also shouldn't match 2, 3, or 4, because # XXX However, it also shouldn't match 2, 3, or 4, because
# they contain at least one comment with 0.0 work_time. # they contain at least one comment with 0.0 work_time.
......
...@@ -166,7 +166,9 @@ sub transformed_value_was_equal { ...@@ -166,7 +166,9 @@ sub transformed_value_was_equal {
sub bug_is_contained { sub bug_is_contained {
my ($self, $number) = @_; my ($self, $number) = @_;
my $contains = $self->test->{contains}; my $contains = $self->test->{contains};
if ($self->transformed_value_was_equal($number)) { if ($self->transformed_value_was_equal($number)
and !$self->test->{override}->{$self->field}->{contains})
{
$contains = $self->test->{if_equal}->{contains}; $contains = $self->test->{if_equal}->{contains};
} }
return grep($_ == $number, @$contains) ? 1 : 0; return grep($_ == $number, @$contains) ? 1 : 0;
...@@ -482,11 +484,6 @@ sub _substr_value { ...@@ -482,11 +484,6 @@ sub _substr_value {
$substr_size += length($field); $substr_size += length($field);
} }
my $string = substr($value, 0, $substr_size); my $string = substr($value, 0, $substr_size);
# Make percentage_complete substrings strings match integers uniquely,
# by searching for the full decimal number.
if ($field eq 'percentage_complete' and length($string) < $substr_size) {
$string .= ".000";
}
return $string; return $string;
} }
return substr($value, $substr_size); return substr($value, $substr_size);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment