summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbugreport%peshkin.net <>2002-10-13 13:26:02 +0200
committerbugreport%peshkin.net <>2002-10-13 13:26:02 +0200
commitfd742d6fc8849328749866dbff2936d43abcc7d1 (patch)
tree1e4d7646a4589bcf44adceb452b38924286f7af1
parentf61593bee73b37fc12caabbb2958b6515d688420 (diff)
downloadbugzilla-fd742d6fc8849328749866dbff2936d43abcc7d1.tar.gz
bugzilla-fd742d6fc8849328749866dbff2936d43abcc7d1.tar.xz
Bug 24789 [E|A|R] Add Estimated, Actual, Remaining Time Fields
patch by jeff.hedlund@matrixsi.com 2xr=joel,justdave
-rw-r--r--Bugzilla/Search.pm61
-rw-r--r--CGI.pl71
-rw-r--r--bug_form.pl14
-rwxr-xr-xbuglist.cgi33
-rwxr-xr-xchecksetup.pl12
-rwxr-xr-xcolchange.cgi5
-rw-r--r--defparams.pl8
-rw-r--r--globals.pl41
-rwxr-xr-xlong_list.cgi13
-rwxr-xr-xpost_bug.cgi21
-rwxr-xr-xprocess_bug.cgi51
-rwxr-xr-xprocessmail79
-rwxr-xr-xquery.cgi18
-rw-r--r--template/en/default/bug/activity/table.html.tmpl18
-rw-r--r--template/en/default/bug/comments.html.tmpl12
-rw-r--r--template/en/default/bug/create/create.html.tmpl14
-rw-r--r--template/en/default/bug/edit.html.tmpl79
-rw-r--r--template/en/default/bug/show-multiple.html.tmpl28
-rw-r--r--template/en/default/bug/time.html.tmpl48
-rw-r--r--template/en/default/global/user-error.html.tmpl23
-rw-r--r--template/en/default/list/edit-multiple.html.tmpl19
-rw-r--r--template/en/default/list/table.html.tmpl13
22 files changed, 609 insertions, 72 deletions
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index db97af3f2..36311d6c4 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -149,6 +149,11 @@ sub init {
push(@specialchart, ["keywords", $t, $F{'keywords'}]);
}
+ if (lsearch($fieldsref, "(SUM(ldtime.work_time)*COUNT(DISTINCT ldtime.bug_when)/COUNT(bugs.bug_id)) AS actual_time") != -1) {
+ push(@supptables, "longdescs AS ldtime");
+ push(@wherepart, "ldtime.bug_id = bugs.bug_id");
+ }
+
foreach my $id ("1", "2") {
if (!defined ($F{"email$id"})) {
next;
@@ -323,6 +328,62 @@ sub init {
push(@wherepart, "$table.bug_id = bugs.bug_id");
$f = "$table.thetext";
},
+ "^work_time,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";
+ $term .= ") AND ($table.work_time <> 0))";
+ },
+ "^work_time,changedbefore" => sub {
+ my $table = "longdescs_$chartid";
+ push(@supptables, "longdescs $table");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ $term = "(($table.bug_when < " . &::SqlQuote(SqlifyDate($v));
+ $term .= ") AND ($table.work_time <> 0))";
+ },
+ "^work_time,changedafter" => sub {
+ my $table = "longdescs_$chartid";
+ push(@supptables, "longdescs $table");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ $term = "(($table.bug_when > " . &::SqlQuote(SqlifyDate($v));
+ $term .= ") AND ($table.work_time <> 0))";
+ },
+ "^work_time," => sub {
+ my $table = "longdescs_$chartid";
+ push(@supptables, "longdescs $table");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ $f = "$table.work_time";
+ },
+ "^percentage_complete," => sub {
+ my $oper;
+ if ($t eq "equals") {
+ $oper = "=";
+ } elsif ($t eq "greaterthan") {
+ $oper = ">";
+ } elsif ($t eq "lessthan") {
+ $oper = "<";
+ } elsif ($t eq "notequal") {
+ $oper = "<>";
+ } elsif ($t eq "regexp") {
+ $oper = "REGEXP";
+ } elsif ($t eq "notregexp") {
+ $oper = "NOT REGEXP";
+ } else {
+ $oper = "noop";
+ }
+ if ($oper ne "noop") {
+ my $table = "longdescs_$chartid";
+ push(@supptables, "longdescs $table");
+ push(@wherepart, "$table.bug_id = bugs.bug_id");
+ my $field = "(100*((SUM($table.work_time)*COUNT(DISTINCT $table.bug_when)/COUNT(bugs.bug_id))/((SUM($table.work_time)*COUNT(DISTINCT $table.bug_when)/COUNT(bugs.bug_id))+bugs.remaining_time))) AS percentage_complete_$table";
+ push(@fields, $field);
+ push(@having,
+ "percentage_complete_$table $oper " . &::SqlQuote($v));
+ }
+ $term = "0=0";
+ },
"^bug_group,(?!changed)" => sub {
push(@supptables, "LEFT JOIN bug_group_map bug_group_map_$chartid ON bugs.bug_id = bug_group_map_$chartid.bug_id");
diff --git a/CGI.pl b/CGI.pl
index 20f257d60..cb2d3d76d 100644
--- a/CGI.pl
+++ b/CGI.pl
@@ -956,6 +956,7 @@ sub GetBugActivity {
my $query = "
SELECT IFNULL(fielddefs.description, bugs_activity.fieldid),
+ fielddefs.name,
bugs_activity.attach_id,
bugs_activity.bug_when,
bugs_activity.removed, bugs_activity.added,
@@ -974,41 +975,59 @@ sub GetBugActivity {
my $changes = [];
my $incomplete_data = 0;
- while (my ($field, $attachid, $when, $removed, $added, $who)
+ while (my ($field, $fieldname, $attachid, $when, $removed, $added, $who)
= FetchSQLData())
{
my %change;
+ my $activity_visible = 1;
- # This gets replaced with a hyperlink in the template.
- $field =~ s/^Attachment// if $attachid;
+ # check if the user should see this field's activity
+ if ($fieldname eq 'remaining_time' ||
+ $fieldname eq 'estimated_time' ||
+ $fieldname eq 'work_time') {
- # Check for the results of an old Bugzilla data corruption bug
- $incomplete_data = 1 if ($added =~ /^\?/ || $removed =~ /^\?/);
+ if (!UserInGroup(Param('timetrackinggroup'))) {
+ $activity_visible = 0;
+ } else {
+ $activity_visible = 1;
+ }
+ } else {
+ $activity_visible = 1;
+ }
+
+ if ($activity_visible) {
+ # This gets replaced with a hyperlink in the template.
+ $field =~ s/^Attachment// if $attachid;
+
+ # Check for the results of an old Bugzilla data corruption bug
+ $incomplete_data = 1 if ($added =~ /^\?/ || $removed =~ /^\?/);
- # An operation, done by 'who' at time 'when', has a number of
- # 'changes' associated with it.
- # If this is the start of a new operation, store the data from the
- # previous one, and set up the new one.
- if ($operation->{'who'}
- && ($who ne $operation->{'who'}
- || $when ne $operation->{'when'}))
- {
- $operation->{'changes'} = $changes;
- push (@operations, $operation);
+ # An operation, done by 'who' at time 'when', has a number of
+ # 'changes' associated with it.
+ # If this is the start of a new operation, store the data from the
+ # previous one, and set up the new one.
+ if ($operation->{'who'}
+ && ($who ne $operation->{'who'}
+ || $when ne $operation->{'when'}))
+ {
+ $operation->{'changes'} = $changes;
+ push (@operations, $operation);
- # Create new empty anonymous data structures.
- $operation = {};
- $changes = [];
- }
+ # Create new empty anonymous data structures.
+ $operation = {};
+ $changes = [];
+ }
- $operation->{'who'} = $who;
- $operation->{'when'} = $when;
+ $operation->{'who'} = $who;
+ $operation->{'when'} = $when;
- $change{'field'} = $field;
- $change{'attachid'} = $attachid;
- $change{'removed'} = $removed;
- $change{'added'} = $added;
- push (@$changes, \%change);
+ $change{'field'} = $field;
+ $change{'fieldname'} = $fieldname;
+ $change{'attachid'} = $attachid;
+ $change{'removed'} = $removed;
+ $change{'added'} = $added;
+ push (@$changes, \%change);
+ }
}
if ($operation->{'who'}) {
diff --git a/bug_form.pl b/bug_form.pl
index c664f2de1..f0ad8e377 100644
--- a/bug_form.pl
+++ b/bug_form.pl
@@ -86,7 +86,8 @@ sub show_bug {
reporter, bug_file_loc, short_desc, target_milestone,
qa_contact, status_whiteboard,
date_format(creation_ts,'%Y-%m-%d %H:%i'),
- delta_ts, sum(votes.count), delta_ts calc_disp_date
+ delta_ts, sum(votes.count), delta_ts calc_disp_date,
+ estimated_time, remaining_time
FROM bugs LEFT JOIN votes USING(bug_id), products, components
WHERE bugs.bug_id = $id
AND bugs.product_id = products.id
@@ -110,7 +111,8 @@ sub show_bug {
"priority", "bug_severity", "component_id", "component",
"assigned_to", "reporter", "bug_file_loc", "short_desc",
"target_milestone", "qa_contact", "status_whiteboard",
- "creation_ts", "delta_ts", "votes", "calc_disp_date")
+ "creation_ts", "delta_ts", "votes", "calc_disp_date",
+ "estimated_time", "remaining_time")
{
$value = shift(@row);
if ($field eq "calc_disp_date") {
@@ -233,6 +235,14 @@ sub show_bug {
push(@list, $i);
}
+ if (UserInGroup(Param("timetrackinggroup"))) {
+
+ SendSQL("SELECT SUM(work_time)
+ FROM longdescs WHERE longdescs.bug_id=$id");
+ $bug{'actual_time'} = FetchSQLData();
+
+ }
+
$bug{'dependson'} = \@list;
my @list2;
diff --git a/buglist.cgi b/buglist.cgi
index 9f00e15ae..e71db0618 100755
--- a/buglist.cgi
+++ b/buglist.cgi
@@ -382,8 +382,10 @@ DefineColumn("os" , "bugs.op_sys" , "OS"
DefineColumn("target_milestone" , "bugs.target_milestone" , "Target Milestone" );
DefineColumn("votes" , "bugs.votes" , "Votes" );
DefineColumn("keywords" , "bugs.keywords" , "Keywords" );
-
-
+DefineColumn("estimated_time" , "bugs.estimated_time" , "Estimated Hours" );
+DefineColumn("remaining_time" , "bugs.remaining_time" , "Remaining Hours" );
+DefineColumn("actual_time" , "(SUM(ldtime.work_time)*COUNT(DISTINCT ldtime.bug_when)/COUNT(bugs.bug_id)) AS actual_time", "Actual Hours");
+DefineColumn("percentage_complete","(100*((SUM(ldtime.work_time)*COUNT(DISTINCT ldtime.bug_when)/COUNT(bugs.bug_id))/((SUM(ldtime.work_time)*COUNT(DISTINCT ldtime.bug_when)/COUNT(bugs.bug_id))+bugs.remaining_time))) AS percentage_complete", "% Complete");
################################################################################
# Display Column Determination
################################################################################
@@ -430,6 +432,14 @@ if (trim($::FORM{'votes'}) && !grep($_ eq 'votes', @displaycolumns)) {
push(@displaycolumns, 'votes');
}
+# Remove the timetracking columns if they are not a part of the group
+# (happens if a user had access to time tracking and it was revoked/disabled)
+if (!UserInGroup(Param("timetrackinggroup"))) {
+ @displaycolumns = grep($_ ne 'estimated_time', @displaycolumns);
+ @displaycolumns = grep($_ ne 'remaining_time', @displaycolumns);
+ @displaycolumns = grep($_ ne 'actual_time', @displaycolumns);
+ @displaycolumns = grep($_ ne 'percentage_complete', @displaycolumns);
+}
################################################################################
# Select Column Determination
@@ -440,6 +450,12 @@ if (trim($::FORM{'votes'}) && !grep($_ eq 'votes', @displaycolumns)) {
# The bug ID is always selected because bug IDs are always displayed
my @selectcolumns = ("id");
+# remaining and actual_time are required for precentage_complete calculation:
+if (lsearch(\@displaycolumns, "percentage_complete")) {
+ push (@selectcolumns, "remaining_time");
+ push (@selectcolumns, "actual_time");
+}
+
# Display columns are selected because otherwise we could not display them.
push (@selectcolumns, @displaycolumns);
@@ -459,6 +475,10 @@ if ($dotweak) {
# Convert the list of columns being selected into a list of column names.
my @selectnames = map($columns->{$_}->{'name'}, @selectcolumns);
+# Remove columns with no names, such as percentage_complete
+# (or a removed *_time column due to permissions)
+@selectnames = grep($_ ne '', @selectnames);
+
# Generate the basic SQL query that will be used to generate the bug list.
my $search = new Bugzilla::Search('fields' => \@selectnames,
'url' => $::buffer);
@@ -538,6 +558,15 @@ if ($order) {
# sort order was given
$db_order =~ s/bugs.votes\s*(,|$)/bugs.votes desc$1/i;
+ # the 'actual_time' field is defined as an aggregate function, but
+ # for order we just need the column name 'actual_time'
+ my $aggregate_search = quotemeta($columns->{'actual_time'}->{'name'});
+ $db_order =~ s/$aggregate_search/actual_time/g;
+
+ # the 'percentage_complete' field is defined as an aggregate too
+ $aggregate_search = quotemeta($columns->{'percentage_complete'}->{'name'});
+ $db_order =~ s/$aggregate_search/percentage_complete/g;
+
$query .= " ORDER BY $db_order ";
}
diff --git a/checksetup.pl b/checksetup.pl
index 83cc06a5f..c44c03235 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -1438,6 +1438,8 @@ $table{bugs} =
everconfirmed tinyint not null,
reporter_accessible tinyint not null default 1,
cclist_accessible tinyint not null default 1,
+ estimated_time decimal(5,2) not null default 0,
+ remaining_time decimal(5,2) not null default 0,
alias varchar(20),
index (assigned_to),
@@ -1478,6 +1480,7 @@ $table{longdescs} =
'bug_id mediumint not null,
who mediumint not null,
bug_when datetime not null,
+ work_time decimal(5,2) not null default 0,
thetext mediumtext,
isprivate tinyint not null default 0,
index(bug_id),
@@ -1853,6 +1856,8 @@ AddFDef("everconfirmed", "Ever Confirmed", 0);
AddFDef("reporter_accessible", "Reporter Accessible", 0);
AddFDef("cclist_accessible", "CC Accessible", 0);
AddFDef("bug_group", "Group", 0);
+AddFDef("estimated_time", "Estimated Hours", 1);
+AddFDef("remaining_time", "Remaining Hours", 0);
# Oops. Bug 163299
$dbh->do("DELETE FROM fielddefs WHERE name='cc_accessible'");
@@ -1860,6 +1865,8 @@ $dbh->do("DELETE FROM fielddefs WHERE name='cc_accessible'");
AddFDef("flagtypes.name", "Flag", 0);
AddFDef("requesters.login_name", "Flag Requester", 0);
AddFDef("setters.login_name", "Flag Setter", 0);
+AddFDef("work_time", "Hours Worked", 0);
+AddFDef("percentage_complete", "Percentage Complete", 0);
###########################################################################
# Detect changed local settings
@@ -2903,6 +2910,11 @@ if (GetFieldDef("bugs","qacontact_accessible")) {
DropField("bugs", "assignee_accessible");
}
+# 2002-02-20 jeff.hedlund@matrixsi.com - bug 24789 time tracking
+AddField("longdescs", "work_time", "decimal(5,2) not null default 0");
+AddField("bugs", "estimated_time", "decimal(5,2) not null default 0");
+AddField("bugs", "remaining_time", "decimal(5,2) not null default 0");
+
# 2002-03-15 bbaetz@student.usyd.edu.au - bug 129466
# 2002-05-13 preed@sigkill.com - bug 129446 patch backported to the
# BUGZILLA-2_14_1-BRANCH as a security blocker for the 2.14.2 release
diff --git a/colchange.cgi b/colchange.cgi
index 2ef7c7d10..3d1ea0476 100755
--- a/colchange.cgi
+++ b/colchange.cgi
@@ -58,6 +58,11 @@ if (@::legal_keywords) {
push(@masterlist, "keywords");
}
+if (UserInGroup(Param("timetrackinggroup"))) {
+ push(@masterlist, ("estimated_time", "remaining_time", "actual_time",
+ "percentage_complete"));
+}
+
push(@masterlist, ("summary", "summaryfull"));
$vars->{'masterlist'} = \@masterlist;
diff --git a/defparams.pl b/defparams.pl
index 232b6c346..bb5d43df7 100644
--- a/defparams.pl
+++ b/defparams.pl
@@ -862,6 +862,14 @@ Reason: %reason%
},
{
+ name => 'timetrackinggroup',
+ desc => 'The name of the group of users who can see/change time tracking ' .
+ 'information.',
+ type => 't',
+ default => ''
+ },
+
+ {
name => 'loginnetmask',
desc => 'The number of bits for the netmask used if a user chooses to ' .
'allow a login to be valid for more than a single IP. Setting ' .
diff --git a/globals.pl b/globals.pl
index 167d0918a..ad96eaf51 100644
--- a/globals.pl
+++ b/globals.pl
@@ -296,7 +296,8 @@ sub FetchOneColumn {
"status", "resolution", "summary");
sub AppendComment {
- my ($bugid, $who, $comment, $isprivate, $timestamp) = @_;
+ my ($bugid, $who, $comment, $isprivate, $timestamp, $work_time) = @_;
+ $work_time ||= 0;
# Use the date/time we were given if possible (allowing calling code
# to synchronize the comment's timestamp with those of other records).
@@ -304,15 +305,26 @@ sub AppendComment {
$comment =~ s/\r\n/\n/g; # Get rid of windows-style line endings.
$comment =~ s/\r/\n/g; # Get rid of mac-style line endings.
- if ($comment =~ /^\s*$/) { # Nothin' but whitespace.
+
+ # allowing negatives though so people can back out errors in time reporting
+ if (defined $work_time) {
+ # regexp verifies one or more digits, optionally followed by a period and
+ # zero or more digits, OR we have a period followed by one or more digits
+ if ($work_time !~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) {
+ ThrowUserError("need_numeric_value");
+ return;
+ }
+ } else { $work_time = 0 };
+
+ if ($comment =~ /^\s*$/) { # Nothin' but whitespace
return;
}
my $whoid = DBNameToIdAndCheck($who);
my $privacyval = $isprivate ? 1 : 0 ;
- SendSQL("INSERT INTO longdescs (bug_id, who, bug_when, thetext, isprivate) " .
+ SendSQL("INSERT INTO longdescs (bug_id, who, bug_when, thetext, isprivate, work_time) " .
"VALUES($bugid, $whoid, $timestamp, " . SqlQuote($comment) . ", " .
- $privacyval . ")");
+ $privacyval . ", " . SqlQuote($work_time) . ")");
SendSQL("UPDATE bugs SET delta_ts = now() WHERE bug_id = $bugid");
}
@@ -1104,7 +1116,7 @@ sub GetLongDescriptionAsText {
$query .= "ORDER BY longdescs.bug_when";
SendSQL($query);
while (MoreSQLData()) {
- my ($who, $when, $text, $isprivate) = (FetchSQLData());
+ my ($who, $when, $text, $isprivate, $work_time) = (FetchSQLData());
if ($count) {
$result .= "\n\n------- Additional Comments From $who".Param('emailsuffix')." ".
time2str("%Y-%m-%d %H:%M", str2time($when)) . " -------\n";
@@ -1124,7 +1136,7 @@ sub GetComments {
my @comments;
SendSQL("SELECT profiles.realname, profiles.login_name,
date_format(longdescs.bug_when,'%Y-%m-%d %H:%i'),
- longdescs.thetext,
+ longdescs.thetext, longdescs.work_time,
isprivate,
date_format(longdescs.bug_when,'%Y%m%d%H%i%s')
FROM longdescs, profiles
@@ -1134,7 +1146,8 @@ sub GetComments {
while (MoreSQLData()) {
my %comment;
- ($comment{'name'}, $comment{'email'}, $comment{'time'}, $comment{'body'},
+ ($comment{'name'}, $comment{'email'}, $comment{'time'},
+ $comment{'body'}, $comment{'work_time'},
$comment{'isprivate'}, $comment{'when'}) = FetchSQLData();
$comment{'email'} .= Param('emailsuffix');
@@ -1490,6 +1503,20 @@ sub PerformSubsts {
return $str;
}
+sub FormatTimeUnit {
+ # Returns a number with 2 digit precision, unless the last digit is a 0
+ # then it returns only 1 digit precision
+ my ($time) = (@_);
+
+ my $newtime = sprintf("%.2f", $time);
+
+ if ($newtime =~ /0\Z/) {
+ $newtime = sprintf("%.1f", $time);
+ }
+
+ return $newtime;
+
+}
###############################################################################
# Global Templatization Code
diff --git a/long_list.cgi b/long_list.cgi
index 6df8a8bad..5047d271e 100755
--- a/long_list.cgi
+++ b/long_list.cgi
@@ -56,7 +56,9 @@ my $generic_query = "
bugs.target_milestone,
bugs.qa_contact,
bugs.status_whiteboard,
- bugs.keywords
+ bugs.keywords,
+ bugs.estimated_time,
+ bugs.remaining_time
FROM bugs,profiles assign,profiles report, products, components
WHERE assign.userid = bugs.assigned_to AND report.userid = bugs.reporter
AND bugs.product_id=products.id AND bugs.component_id=components.id";
@@ -79,7 +81,8 @@ foreach my $bug_id (split(/[:,]/, $buglist)) {
"op_sys", "bug_status", "resolution", "priority",
"bug_severity", "component", "assigned_to", "reporter",
"bug_file_loc", "short_desc", "target_milestone",
- "qa_contact", "status_whiteboard", "keywords")
+ "qa_contact", "status_whiteboard", "keywords",
+ "estimated_time", "remaining_time")
{
$bug{$field} = shift @row;
}
@@ -91,6 +94,12 @@ foreach my $bug_id (split(/[:,]/, $buglist)) {
push (@bugs, \%bug);
}
+
+ if (UserInGroup(Param("timetrackinggroup"))) {
+ SendSQL("SELECT SUM(work_time) FROM longdescs WHERE bug_id=$bug_id");
+
+ $bug{'actual_time'} = FetchSQLData();
+ }
}
# Add the list of bug hashes to the variables
diff --git a/post_bug.cgi b/post_bug.cgi
index c07d07d99..d519a484c 100755
--- a/post_bug.cgi
+++ b/post_bug.cgi
@@ -233,7 +233,8 @@ if ($::FORM{'keywords'} && UserInGroup("editbugs")) {
# Build up SQL string to add bug.
my $sql = "INSERT INTO bugs " .
- "(" . join(",", @used_fields) . ", reporter, creation_ts) " .
+ "(" . join(",", @used_fields) . ", reporter, creation_ts, " .
+ "estimated_time, remaining_time) " .
"VALUES (";
foreach my $field (@used_fields) {
@@ -246,7 +247,23 @@ $comment = trim($comment);
# OK except for the fact that it causes e-mail to be suppressed.
$comment = $comment ? $comment : " ";
-$sql .= "$::userid, now() )";
+$sql .= "$::userid, now(), ";
+
+# Time Tracking
+if (UserInGroup(Param("timetrackinggroup")) &&
+ defined $::FORM{'estimated_time'}) {
+
+ my $est_time = $::FORM{'estimated_time'};
+ if ($est_time =~ /^(?:\d+(?:\.\d*)?|\.\d+)$/) {
+ $sql .= SqlQuote($est_time) . "," . SqlQuote($est_time);
+ } else {
+ $vars->{'field'} = "estimated_time";
+ ThrowUserError("need_positive_number");
+ }
+} else {
+ $sql .= "0, 0";
+}
+$sql .= ")";
# Groups
my @groupstoadd = ();
diff --git a/process_bug.cgi b/process_bug.cgi
index f529f13ea..439587178 100755
--- a/process_bug.cgi
+++ b/process_bug.cgi
@@ -703,6 +703,25 @@ if (defined $::FORM{'qa_contact'}) {
}
}
+# jeff.hedlund@matrixsi.com time tracking data processing:
+foreach my $field ("estimated_time", "remaining_time") {
+
+ if (defined $::FORM{$field}) {
+ my $er_time = trim($::FORM{$field});
+ if ($er_time ne $::FORM{'dontchange'}) {
+ if ($er_time > 99999.99) {
+ ThrowUserError("value_out_of_range", {variable => $field});
+ }
+ if ($er_time =~ /^(?:\d+(?:\.\d*)?|\.\d+)$/) {
+ DoComma();
+ $::query .= "$field = " . SqlQuote($er_time);
+ } else {
+ $vars->{'field'} = $field;
+ ThrowUserError("need_positive_number");
+ }
+ }
+ }
+}
# If the user is submitting changes from show_bug.cgi for a single bug,
# and that bug is restricted to a group, process the checkboxes that
@@ -808,6 +827,12 @@ SWITCH: for ($::FORM{'knob'}) {
last SWITCH;
};
/^resolve$/ && CheckonComment( "resolve" ) && do {
+ if (UserInGroup(Param('timetrackinggroup'))) {
+ if (defined $::FORM{'remaining_time'} &&
+ $::FORM{'remaining_time'} > 0) {
+ ThrowUserError("resolving_remaining_time");
+ }
+ }
# Check here, because its the only place we require the resolution
CheckFormField(\%::FORM, 'resolution', \@::settable_resolution);
ChangeStatus('RESOLVED');
@@ -1170,6 +1195,26 @@ foreach my $id (@idlist) {
}
}
+ SendSQL("select now()");
+ $timestamp = FetchOneColumn();
+
+ if ($::FORM{'work_time'} > 99999.99) {
+ ThrowUserError("value_out_of_range", {variable => 'work_time'});
+ }
+ if (defined $::FORM{'comment'} || defined $::FORM{'work_time'}) {
+ if ($::FORM{'work_time'} != 0 &&
+ (!defined $::FORM{'comment'} || $::FORM{'comment'} =~ /^\s*$/)) {
+
+ ThrowUserError('comment_required');
+ } else {
+ AppendComment($id, $::COOKIE{'Bugzilla_login'}, $::FORM{'comment'},
+ $::FORM{'commentprivacy'}, $timestamp, $::FORM{'work_time'});
+ if ($::FORM{'work_time'} != 0) {
+ LogActivityEntry($id, "work_time", "", $::FORM{'work_time'});
+ }
+ }
+ }
+
if (@::legal_keywords) {
# There are three kinds of "keywordsaction": makeexact, add, delete.
# For makeexact, we delete everything, and then add our things.
@@ -1229,17 +1274,11 @@ foreach my $id (@idlist) {
SendSQL("DELETE FROM bug_group_map
WHERE bug_id = $id AND group_id = $grouptodel");
}
- SendSQL("select now()");
- $timestamp = FetchOneColumn();
my $groupDelNames = join(',', @groupDelNames);
my $groupAddNames = join(',', @groupAddNames);
LogActivityEntry($id, "bug_group", $groupDelNames, $groupAddNames);
- if (defined $::FORM{'comment'}) {
- AppendComment($id, $::COOKIE{'Bugzilla_login'}, $::FORM{'comment'},
- $::FORM{'commentprivacy'}, $timestamp);
- }
my $removedCcString = "";
if (defined $::FORM{newcc} || defined $::FORM{removecc} || defined $::FORM{masscc}) {
diff --git a/processmail b/processmail
index a47297597..3b421e922 100755
--- a/processmail
+++ b/processmail
@@ -129,12 +129,13 @@ sub ProcessOneBug {
if ($values{'qa_contact'}) {
$values{'qa_contact'} = DBID_to_name($values{'qa_contact'});
}
+ $values{'estimated_time'} = FormatTimeUnit($values{'estimated_time'});
my @diffs;
SendSQL("SELECT profiles.login_name, fielddefs.description, " .
- " bug_when, removed, added, attach_id " .
+ " bug_when, removed, added, attach_id, fielddefs.name " .
"FROM bugs_activity, fielddefs, profiles " .
"WHERE bug_id = $id " .
" AND fielddefs.fieldid = bugs_activity.fieldid " .
@@ -150,21 +151,32 @@ sub ProcessOneBug {
}
my $difftext = "";
+ my $diffheader = "";
+ my $diffpart = {};
+ my @diffparts;
my $lastwho = "";
foreach my $ref (@diffs) {
- my ($who, $what, $when, $old, $new, $attachid) = (@$ref);
+ my ($who, $what, $when, $old, $new, $attachid, $fieldname) = (@$ref);
+ $diffpart = {};
if ($who ne $lastwho) {
$lastwho = $who;
- $difftext .= "\n$who" . Param('emailsuffix') . " changed:\n\n";
- $difftext .= FormatTriple("What ", "Removed", "Added");
- $difftext .= ('-' x 76) . "\n";
+ $diffheader = "\n$who" . Param('emailsuffix') . " changed:\n\n";
+ $diffheader .= FormatTriple("What ", "Removed", "Added");
+ $diffheader .= ('-' x 76) . "\n";
}
$what =~ s/^Attachment/Attachment #$attachid/ if $attachid;
- $difftext .= FormatTriple($what, $old, $new);
+ if( $fieldname eq 'estimated_time' ||
+ $fieldname eq 'remaining_time' ) {
+ $old = FormatTimeUnit($old);
+ $new = FormatTimeUnit($new);
+ }
+ $difftext = FormatTriple($what, $old, $new);
+ $diffpart->{'header'} = $diffheader;
+ $diffpart->{'fieldname'} = $fieldname;
+ $diffpart->{'text'} = $difftext;
+ push(@diffparts, $diffpart);
}
- $difftext = trim($difftext);
-
my $deptext = "";
@@ -220,7 +232,9 @@ sub ProcessOneBug {
$deptext = trim($deptext);
if ($deptext) {
- $difftext = trim($difftext . "\n\n" . $deptext);
+ #$difftext = trim($difftext . "\n\n" . $deptext);
+ $diffpart->{'text'} = trim("\n\n" . $deptext);
+ push(@diffparts, $diffpart);
}
@@ -301,9 +315,9 @@ sub ProcessOneBug {
if ( !defined(NewProcessOnePerson($person, $count, \@headerlist,
\@reasons, \%values,
\%defmailhead,
- \%fielddescription, $difftext,
- $newcomments, $anyprivate,
- $start, $id,
+ \%fielddescription, \@diffparts,
+ $newcomments,
+ $anyprivate, $start, $id,
\@depbugs)))
{
@@ -613,14 +627,16 @@ sub filterEmailGroup ($$$) {
}
sub NewProcessOnePerson ($$$$$$$$$$$$$) {
- my ($person, $count, $hlRef, $reasonsRef, $valueRef, $dmhRef, $fdRef, $difftext,
- $newcomments, $anyprivate, $start, $id, $depbugsRef) = @_;
+ my ($person, $count, $hlRef, $reasonsRef, $valueRef, $dmhRef, $fdRef,
+ $diffRef, $newcomments, $anyprivate, $start,
+ $id, $depbugsRef) = @_;
my %values = %$valueRef;
my @headerlist = @$hlRef;
my @reasons = @$reasonsRef;
my %defmailhead = %$dmhRef;
my %fielddescription = %$fdRef;
+ my @diffparts = @$diffRef;
my @depbugs = @$depbugsRef;
if ($seen{$person}) {
@@ -680,10 +696,41 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) {
if (! $value) {
next;
}
- my $desc = $fielddescription{$f};
- $head .= FormatDouble($desc, $value);
+ # Don't send estimated_time if user not in the group, or not enabled
+ if ($f ne 'estimated_time' ||
+ UserInGroup(Param('timetrackinggroup'), $userid)) {
+
+ my $desc = $fielddescription{$f};
+ $head .= FormatDouble($desc, $value);
+ }
}
}
+
+ # Build difftext (the actions) by verifying the user should see them
+ my $difftext = "";
+ my $diffheader = "";
+ my $add_diff;
+ foreach my $diff (@diffparts) {
+
+ $add_diff = 0;
+
+ if ($diff->{'fieldname'} eq 'estimated_time' ||
+ $diff->{'fieldname'} eq 'remaining_time' ||
+ $diff->{'fieldname'} eq 'work_time') {
+ if (UserInGroup(Param("timetrackinggroup"), $userid)) {
+ $add_diff = 1;
+ }
+ } else {
+ $add_diff = 1;
+ }
+ if ($add_diff) {
+ if ($diffheader ne $diff->{'header'}) {
+ $diffheader = $diff->{'header'};
+ $difftext .= $diffheader;
+ }
+ $difftext .= $diff->{'text'};
+ }
+ }
if ($difftext eq "" && $newcomments eq "") {
# Whoops, no differences!
diff --git a/query.cgi b/query.cgi
index 1aa17b723..973d1fdbc 100755
--- a/query.cgi
+++ b/query.cgi
@@ -281,7 +281,16 @@ shift @::legal_resolution;
# Another hack - this array contains "" for some reason. See bug 106589.
$vars->{'resolution'} = \@::legal_resolution;
-$vars->{'chfield'} = ["[Bug creation]", @::log_columns];
+my @chfields = @::log_columns;
+push @chfields, "[Bug creation]";
+if (UserInGroup(Param('timetrackinggroup'))) {
+ push @chfields, "work_time";
+} else {
+ @chfields = grep($_ ne "estimated_time", @chfields);
+ @chfields = grep($_ ne "remaining_time", @chfields);
+}
+@chfields = (sort(@chfields));
+$vars->{'chfield'} = \@chfields;
$vars->{'bug_status'} = \@::legal_bug_status;
$vars->{'rep_platform'} = \@::legal_platform;
$vars->{'op_sys'} = \@::legal_opsys;
@@ -295,6 +304,13 @@ push(@fields, { name => "noop", description => "---" });
SendSQL("SELECT name, description FROM fielddefs ORDER BY sortkey");
while (MoreSQLData()) {
my ($name, $description) = FetchSQLData();
+ if (($name eq "estimated_time" ||
+ $name eq "remaining_time" ||
+ $name eq "work_time" ||
+ $name eq "percentage_complete" ) &&
+ (!UserInGroup(Param('timetrackinggroup')))) {
+ next;
+ }
push(@fields, { name => $name, description => $description });
}
diff --git a/template/en/default/bug/activity/table.html.tmpl b/template/en/default/bug/activity/table.html.tmpl
index 43529bd23..45c8e4380 100644
--- a/template/en/default/bug/activity/table.html.tmpl
+++ b/template/en/default/bug/activity/table.html.tmpl
@@ -32,6 +32,8 @@
# incomplete_data: boolean. True if some of the data is incomplete (because
# it was affected by an old Bugzilla bug.)
#%]
+
+[% PROCESS bug/time.html.tmpl %]
[% IF incomplete_data %]
<p>
@@ -72,14 +74,26 @@
</td>
<td>
[% IF change.removed %]
- [% change.removed FILTER html %]
+ [% IF change.fieldname == 'estimated_time' ||
+ change.fieldname == 'remaining_time' ||
+ change.fieldname == 'work_time' %]
+ [% PROCESS formattimeunit time_unit=change.removed %]
+ [% ELSE %]
+ [% change.removed FILTER html %]
+ [% END %]
[% ELSE %]
&nbsp;
[% END %]
</td>
<td>
[% IF change.added %]
- [% change.added FILTER html %]
+ [% IF change.fieldname == 'estimated_time' ||
+ change.fieldname == 'remaining_time' ||
+ change.fieldname == 'work_time' %]
+ [% PROCESS formattimeunit time_unit=change.added %]
+ [% ELSE %]
+ [% change.added FILTER html %]
+ [% END %]
[% ELSE %]
&nbsp;
[% END %]
diff --git a/template/en/default/bug/comments.html.tmpl b/template/en/default/bug/comments.html.tmpl
index 7a8ae73db..f5880a811 100644
--- a/template/en/default/bug/comments.html.tmpl
+++ b/template/en/default/bug/comments.html.tmpl
@@ -30,6 +30,7 @@
[% count = count + 1 %]
[% END %]
+[% PROCESS bug/time.html.tmpl %]
[%############################################################################%]
[%# Block for individual comments #%]
@@ -43,9 +44,11 @@
<i>------- Additional Comment
<a name="c[% count %]" href="#c[% count %]">#[% count %]</a> From
<a href="mailto:[% comment.email FILTER html %]">[% comment.name FILTER html %]</a>
- [%+ comment.time %] -------
+ [%+ comment.time %]
+ -------
</i>
[% END %]
+
[% IF mode == "edit" && isinsider %]
<i>
<input type=hidden name="oisprivate-[% count %]"
@@ -55,7 +58,12 @@
[% " checked=\"checked\"" IF comment.isprivate %]> Private
</i>
[% END %]
-
+ [% IF UserInGroup(Param('timetrackinggroup')) &&
+ (comment.work_time > 0 || comment.work_time < 0) %]
+ <br>
+ Additional hours worked:
+ [% PROCESS formattimeunit time_unit=comment.work_time %]
+ [% END %]
[%# Don't indent the <pre> block, since then the spaces are displayed in the
# generated HTML
#%]
diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl
index 066c11b63..354dd990c 100644
--- a/template/en/default/bug/create/create.html.tmpl
+++ b/template/en/default/bug/create/create.html.tmpl
@@ -155,6 +155,20 @@
<td colspan="3"></td>
</tr>
+[% IF UserInGroup(Param('timetrackinggroup')) %]
+ <tr>
+ <td align="right"><strong>Estimated Hours:</strong></td>
+ <td colspan="3">
+ <input name="estimated_time" size="6" maxlength="6" value="0.0"/>
+ </td>
+ </tr>
+
+ <tr>
+ <td>&nbsp;</td>
+ <td colspan="3"></td>
+ </tr>
+[% END %]
+
<tr>
<td align="right"><strong>URL:</strong></td>
<td colspan="3">
diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl
index 152852169..453b4aa65 100644
--- a/template/en/default/bug/edit.html.tmpl
+++ b/template/en/default/bug/edit.html.tmpl
@@ -32,6 +32,29 @@
[% END %]
[% PROCESS bug/navigate.html.tmpl %]
+[% PROCESS bug/time.html.tmpl %]
+
+<script type="text/javascript" language="JavaScript">
+<!--
+var fRemainingTime = [% bug.remaining_time %]; // holds the original value
+function adjustRemainingTime() {
+ // subtracts time spent from remaining time
+ var new_time;
+
+ new_time =
+ fRemainingTime - document.changeform.work_time.value;
+ // get upto 2 decimal places
+ document.changeform.remaining_time.value =
+ Math.round(new_time * 100)/100;
+}
+
+function updateRemainingTime() {
+ // if the remaining time is changed manually, update fRemainingTime
+ fRemainingTime = document.changeform.remaining_time.value;
+}
+
+//-->
+</script>
<hr>
@@ -264,6 +287,62 @@
</tr>
[% END %]
</table>
+
+ [% IF UserInGroup(Param('timetrackinggroup')) %]
+ <br>
+ <table cellpadding=0 cellspacing=0 border=1>
+ <tr>
+ <th width="16.6%" align="center" bgcolor="#cccccc">
+ Orig. Est.
+ </th>
+ <th width="16.6%" align="center" bgcolor="#cccccc">
+ Current Est.
+ </th>
+ <th width="16.6%" align="center" bgcolor="#cccccc">
+ Hours Worked
+ </th>
+ <th width="16.6%" align="center" bgcolor="#cccccc">
+ Hours Left
+ </th>
+ <th width="16.6%" align="center" bgcolor="#cccccc">
+ %Complete
+ </th>
+ <th width="16.6%" align="center" bgcolor="#cccccc">
+ Gain
+ </th>
+ </tr>
+ <tr>
+ <td align="center">
+ <input name="estimated_time"
+ value="[% PROCESS formattimeunit
+ time_unit=bug.estimated_time %]"
+ size="6" maxlength="6">
+ </td>
+ <td align="center">
+ [% PROCESS formattimeunit
+ time_unit=(bug.actual_time + bug.remaining_time) %]
+ </td>
+ <td align="center">
+ [% PROCESS formattimeunit time_unit=bug.actual_time %] +
+ <input name="work_time" value="0" size="3" maxlength="6"
+ onChange="adjustRemainingTime();">
+ </td>
+ <td align="center">
+ <input name="remaining_time"
+ value="[% PROCESS formattimeunit
+ time_unit=bug.remaining_time %]"
+ size="6" maxlength="6" onChange="updateRemainingTime();">
+ </td>
+ <td align="center">
+ [% PROCESS calculatepercentage act=bug.actual_time
+ rem=bug.remaining_time %]
+ </td>
+ <td align="center">
+ [% PROCESS formattimeunit time_unit=bug.estimated_time - (bug.actual_time + bug.remaining_time) %]
+ </td>
+ </tr>
+ </table>
+ [% END %]
[%# *** Attachments *** %]
diff --git a/template/en/default/bug/show-multiple.html.tmpl b/template/en/default/bug/show-multiple.html.tmpl
index 0c089e9c5..d7e2fcf09 100644
--- a/template/en/default/bug/show-multiple.html.tmpl
+++ b/template/en/default/bug/show-multiple.html.tmpl
@@ -24,6 +24,7 @@
title = "Full Text Bug Listing"
style_urls = [ "css/show_multiple.css" ]
%]
+[% PROCESS bug/time.html.tmpl %]
[% IF bugs.first %]
[% FOREACH bug = bugs %]
[% PROCESS bug_display %]
@@ -34,6 +35,7 @@
</p>
[% END %]
+
[% PROCESS global/footer.html.tmpl %]
@@ -130,6 +132,32 @@
</tr>
[% END %]
+ [% IF UserInGroup(Param("timetrackinggroup")) %]
+ <tr>
+ <td colspan="4">
+ <b>Orig. Est.:</b>&nbsp;
+ [% PROCESS formattimeunit time_unit=bug.estimated_time %]
+ &nbsp;
+ <b>Current Est.:</b>&nbsp;
+ [% PROCESS formattimeunit
+ time_unit=(bug.remaining_time + bug.actual_time) %]
+ &nbsp;
+ <b>Hours Worked:</b>&nbsp;
+ [% PROCESS formattimeunit time_unit=bug.actual_time %]&nbsp;
+ <b>Hours Left:</b>&nbsp;
+ [% PROCESS formattimeunit time_unit=bug.remaining_time %]
+ &nbsp;
+ <b>Percentage Complete:</b>&nbsp;
+ [% PROCESS calculatepercentage act=bug.actual_time
+ rem=bug.remaining_time %]&nbsp;
+ <b>Gain</b>&nbsp;
+ [% PROCESS formattimeunit
+ time_unit=bug.estimated_time - (bug.actual_time + bug.remaining_time) %]
+ &nbsp;
+ </td>
+ </tr>
+ [% END %]
+
<tr>
<td colspan="4">
<b>Description:</b>
diff --git a/template/en/default/bug/time.html.tmpl b/template/en/default/bug/time.html.tmpl
new file mode 100644
index 000000000..af6966930
--- /dev/null
+++ b/template/en/default/bug/time.html.tmpl
@@ -0,0 +1,48 @@
+<!-- 1.0@bugzilla.org -->
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # The Initial Developer of the Original Code is Netscape Communications
+ # Corporation. Portions created by Netscape are
+ # Copyright (C) 1998 Netscape Communications Corporation. All
+ # Rights Reserved.
+ #
+ # Contributor(s): Jeff Hedlund <jeff.hedlund@matrixsi.com>
+ #
+ #%]
+
+[% BLOCK formattimeunit %]
+ [%# INTERFACE:
+ # time_unit: the number converting, converts to 2 decimal places
+ # unless the last character is a 0, then it truncates to
+ # 1 decimal place
+ #%]
+ [% time_unit = time_unit FILTER format('%.2f') %]
+ [% IF time_unit.match('0\Z') %]
+ [% time_unit FILTER format('%.1f') %]
+ [% ELSE %]
+ [% time_unit FILTER format('%.2f') %]
+ [% END %]
+[% END %]
+
+[% BLOCK calculatepercentage %]
+ [%# INTERFACE:
+ # act: actual time
+ # rem: remaining time
+ # %]
+ [% IF (act + rem) > 0 %]
+ [% (act / (act + rem)) * 100
+ FILTER format("%d") %]
+ [% ELSE %]
+ 0
+ [% END %]
+[% END %]
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
index f487067dd..29cb3c901 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -330,8 +330,16 @@
[% ELSIF error == "need_component" %]
[% title = "Component Required" %]
- You must specify a component to help determine the new owner of these bugs.
-
+ You must specify a component to help determine the new owner of these bugs.
+
+ [% ELSIF error == "need_numeric_value" %]
+ [% title = "Numeric Value Required" %]
+ Hours requires a numeric value.
+
+ [% ELSIF error == "need_positive_number" %]
+ [% title = "Positive Number Required" %]
+ [% field %] requires a positive number.
+
[% ELSIF error == "need_product" %]
[% title = "Product Required" %]
You must specify a product to help determine the new owner of these bugs.
@@ -445,7 +453,7 @@
[% ELSIF error == "report_access_denied" %]
[% title = "Access Denied" %]
You do not have the permissions necessary to view reports for this product.
-
+
[% ELSIF error == "requestee_too_short" %]
[% title = "Requestee Name Too Short" %]
One or two characters match too many users, so please enter at least
@@ -470,6 +478,11 @@
[% ELSIF error == "require_summary" %]
[% title = "Summary Needed" %]
You must enter a summary for this bug.
+
+ [% ELSIF error == "resolving_remaining_time" %]
+ [% title = "Trying to Resolve with Hours Remaining" %]
+ You cannot resolve a bug with hours still remaining. Set
+ Remaining Hours to zero if you want to resolve the bug.
[% ELSIF error == "sanity_check_access_denied" %]
[% title = "Access Denied" %]
@@ -521,6 +534,10 @@
[% title = "Wrong Token" %]
That token cannot be used to change your email address.
+ [% ELSIF error == "value_out_of_range" %]
+ [% title = "Value Out Of Range" %]
+ Value is out of range for field [% variable %].
+
[% ELSIF error == "z_axis_defined_with_no_x_axis" %]
[% title = "Nonsensical Options" %]
You've defined a field for multiple tables without having defined
diff --git a/template/en/default/list/edit-multiple.html.tmpl b/template/en/default/list/edit-multiple.html.tmpl
index 4121d0292..4d769c5fa 100644
--- a/template/en/default/list/edit-multiple.html.tmpl
+++ b/template/en/default/list/edit-multiple.html.tmpl
@@ -116,6 +116,25 @@
</tr>
+ [% IF UserInGroup(Param("timetrackinggroup")) %]
+ <tr>
+ <th><label for="estimated_time">Estimated Hours:</label></th>
+ <td>
+ <input id="estimated_time"
+ name="estimated_time"
+ value="[% dontchange FILTER html %]"
+ size="6">
+ </td>
+ <th><label for="remaining_time">Remaining Hours:</label></th>
+ <td>
+ <input id="remaining_time"
+ name="remaining_time"
+ value="[% dontchange FILTER html %]"
+ size="6">
+ </td>
+ </tr>
+ [% END %]
+
[% IF Param("useqacontact") %]
<tr>
<th><label for="qa_contact">QA Contact:</label></th>
diff --git a/template/en/default/list/table.html.tmpl b/template/en/default/list/table.html.tmpl
index 6d5ee0d6c..eb1308961 100644
--- a/template/en/default/list/table.html.tmpl
+++ b/template/en/default/list/table.html.tmpl
@@ -49,11 +49,14 @@
"version" => { maxlength => 5 , title => "Vers" } ,
"os" => { maxlength => 4 } ,
"target_milestone" => { title => "TargetM" } ,
+ "percentage_complete" => { format_value => "%d %%" } ,
}
%]
[% qorder = order FILTER url_quote IF order %]
+[% PROCESS bug/time.html.tmpl %]
+
[%############################################################################%]
[%# Table Header #%]
[%############################################################################%]
@@ -132,7 +135,15 @@
[% FOREACH column = displaycolumns %]
<td>
[% '<nobr>' IF NOT abbrev.$column.wrap %]
- [%- bug.$column.truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html -%]
+ [% IF abbrev.$column.format_value %]
+ [%- bug.$column FILTER format(abbrev.$column.format_value) FILTER html -%]
+ [% ELSIF column == 'actual_time' ||
+ column == 'remaining_time' ||
+ column == 'estimated_time' %]
+ [% PROCESS formattimeunit time_unit=bug.$column %]
+ [% ELSE %]
+ [%- bug.$column.truncate(abbrev.$column.maxlength, abbrev.$column.ellipsis) FILTER html -%]
+ [% END %]
[%- '</nobr>' IF NOT abbrev.$column.wrap %]
</td>
[% END %]