Commit fd742d6f authored by bugreport%peshkin.net's avatar bugreport%peshkin.net

Bug 24789 [E|A|R] Add Estimated, Actual, Remaining Time Fields

patch by jeff.hedlund@matrixsi.com 2xr=joel,justdave
parent f61593be
...@@ -149,6 +149,11 @@ sub init { ...@@ -149,6 +149,11 @@ sub init {
push(@specialchart, ["keywords", $t, $F{'keywords'}]); 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") { foreach my $id ("1", "2") {
if (!defined ($F{"email$id"})) { if (!defined ($F{"email$id"})) {
next; next;
...@@ -323,6 +328,62 @@ sub init { ...@@ -323,6 +328,62 @@ sub init {
push(@wherepart, "$table.bug_id = bugs.bug_id"); push(@wherepart, "$table.bug_id = bugs.bug_id");
$f = "$table.thetext"; $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 { "^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"); push(@supptables, "LEFT JOIN bug_group_map bug_group_map_$chartid ON bugs.bug_id = bug_group_map_$chartid.bug_id");
......
...@@ -956,6 +956,7 @@ sub GetBugActivity { ...@@ -956,6 +956,7 @@ sub GetBugActivity {
my $query = " my $query = "
SELECT IFNULL(fielddefs.description, bugs_activity.fieldid), SELECT IFNULL(fielddefs.description, bugs_activity.fieldid),
fielddefs.name,
bugs_activity.attach_id, bugs_activity.attach_id,
bugs_activity.bug_when, bugs_activity.bug_when,
bugs_activity.removed, bugs_activity.added, bugs_activity.removed, bugs_activity.added,
...@@ -974,41 +975,59 @@ sub GetBugActivity { ...@@ -974,41 +975,59 @@ sub GetBugActivity {
my $changes = []; my $changes = [];
my $incomplete_data = 0; my $incomplete_data = 0;
while (my ($field, $attachid, $when, $removed, $added, $who) while (my ($field, $fieldname, $attachid, $when, $removed, $added, $who)
= FetchSQLData()) = FetchSQLData())
{ {
my %change; my %change;
my $activity_visible = 1;
# This gets replaced with a hyperlink in the template. # check if the user should see this field's activity
$field =~ s/^Attachment// if $attachid; if ($fieldname eq 'remaining_time' ||
$fieldname eq 'estimated_time' ||
$fieldname eq 'work_time') {
# Check for the results of an old Bugzilla data corruption bug if (!UserInGroup(Param('timetrackinggroup'))) {
$incomplete_data = 1 if ($added =~ /^\?/ || $removed =~ /^\?/); $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 # An operation, done by 'who' at time 'when', has a number of
# 'changes' associated with it. # 'changes' associated with it.
# If this is the start of a new operation, store the data from the # If this is the start of a new operation, store the data from the
# previous one, and set up the new one. # previous one, and set up the new one.
if ($operation->{'who'} if ($operation->{'who'}
&& ($who ne $operation->{'who'} && ($who ne $operation->{'who'}
|| $when ne $operation->{'when'})) || $when ne $operation->{'when'}))
{ {
$operation->{'changes'} = $changes; $operation->{'changes'} = $changes;
push (@operations, $operation); push (@operations, $operation);
# Create new empty anonymous data structures. # Create new empty anonymous data structures.
$operation = {}; $operation = {};
$changes = []; $changes = [];
} }
$operation->{'who'} = $who; $operation->{'who'} = $who;
$operation->{'when'} = $when; $operation->{'when'} = $when;
$change{'field'} = $field; $change{'field'} = $field;
$change{'attachid'} = $attachid; $change{'fieldname'} = $fieldname;
$change{'removed'} = $removed; $change{'attachid'} = $attachid;
$change{'added'} = $added; $change{'removed'} = $removed;
push (@$changes, \%change); $change{'added'} = $added;
push (@$changes, \%change);
}
} }
if ($operation->{'who'}) { if ($operation->{'who'}) {
......
...@@ -86,7 +86,8 @@ sub show_bug { ...@@ -86,7 +86,8 @@ sub show_bug {
reporter, bug_file_loc, short_desc, target_milestone, reporter, bug_file_loc, short_desc, target_milestone,
qa_contact, status_whiteboard, qa_contact, status_whiteboard,
date_format(creation_ts,'%Y-%m-%d %H:%i'), 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 FROM bugs LEFT JOIN votes USING(bug_id), products, components
WHERE bugs.bug_id = $id WHERE bugs.bug_id = $id
AND bugs.product_id = products.id AND bugs.product_id = products.id
...@@ -110,7 +111,8 @@ sub show_bug { ...@@ -110,7 +111,8 @@ sub show_bug {
"priority", "bug_severity", "component_id", "component", "priority", "bug_severity", "component_id", "component",
"assigned_to", "reporter", "bug_file_loc", "short_desc", "assigned_to", "reporter", "bug_file_loc", "short_desc",
"target_milestone", "qa_contact", "status_whiteboard", "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); $value = shift(@row);
if ($field eq "calc_disp_date") { if ($field eq "calc_disp_date") {
...@@ -233,6 +235,14 @@ sub show_bug { ...@@ -233,6 +235,14 @@ sub show_bug {
push(@list, $i); 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; $bug{'dependson'} = \@list;
my @list2; my @list2;
......
...@@ -382,8 +382,10 @@ DefineColumn("os" , "bugs.op_sys" , "OS" ...@@ -382,8 +382,10 @@ DefineColumn("os" , "bugs.op_sys" , "OS"
DefineColumn("target_milestone" , "bugs.target_milestone" , "Target Milestone" ); DefineColumn("target_milestone" , "bugs.target_milestone" , "Target Milestone" );
DefineColumn("votes" , "bugs.votes" , "Votes" ); DefineColumn("votes" , "bugs.votes" , "Votes" );
DefineColumn("keywords" , "bugs.keywords" , "Keywords" ); 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 # Display Column Determination
################################################################################ ################################################################################
...@@ -430,6 +432,14 @@ if (trim($::FORM{'votes'}) && !grep($_ eq 'votes', @displaycolumns)) { ...@@ -430,6 +432,14 @@ if (trim($::FORM{'votes'}) && !grep($_ eq 'votes', @displaycolumns)) {
push(@displaycolumns, 'votes'); 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 # Select Column Determination
...@@ -440,6 +450,12 @@ if (trim($::FORM{'votes'}) && !grep($_ eq 'votes', @displaycolumns)) { ...@@ -440,6 +450,12 @@ if (trim($::FORM{'votes'}) && !grep($_ eq 'votes', @displaycolumns)) {
# The bug ID is always selected because bug IDs are always displayed # The bug ID is always selected because bug IDs are always displayed
my @selectcolumns = ("id"); 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. # Display columns are selected because otherwise we could not display them.
push (@selectcolumns, @displaycolumns); push (@selectcolumns, @displaycolumns);
...@@ -459,6 +475,10 @@ if ($dotweak) { ...@@ -459,6 +475,10 @@ if ($dotweak) {
# Convert the list of columns being selected into a list of column names. # Convert the list of columns being selected into a list of column names.
my @selectnames = map($columns->{$_}->{'name'}, @selectcolumns); 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. # Generate the basic SQL query that will be used to generate the bug list.
my $search = new Bugzilla::Search('fields' => \@selectnames, my $search = new Bugzilla::Search('fields' => \@selectnames,
'url' => $::buffer); 'url' => $::buffer);
...@@ -538,6 +558,15 @@ if ($order) { ...@@ -538,6 +558,15 @@ if ($order) {
# sort order was given # sort order was given
$db_order =~ s/bugs.votes\s*(,|$)/bugs.votes desc$1/i; $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 "; $query .= " ORDER BY $db_order ";
} }
......
...@@ -1438,6 +1438,8 @@ $table{bugs} = ...@@ -1438,6 +1438,8 @@ $table{bugs} =
everconfirmed tinyint not null, everconfirmed tinyint not null,
reporter_accessible tinyint not null default 1, reporter_accessible tinyint not null default 1,
cclist_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), alias varchar(20),
index (assigned_to), index (assigned_to),
...@@ -1478,6 +1480,7 @@ $table{longdescs} = ...@@ -1478,6 +1480,7 @@ $table{longdescs} =
'bug_id mediumint not null, 'bug_id mediumint not null,
who mediumint not null, who mediumint not null,
bug_when datetime not null, bug_when datetime not null,
work_time decimal(5,2) not null default 0,
thetext mediumtext, thetext mediumtext,
isprivate tinyint not null default 0, isprivate tinyint not null default 0,
index(bug_id), index(bug_id),
...@@ -1853,6 +1856,8 @@ AddFDef("everconfirmed", "Ever Confirmed", 0); ...@@ -1853,6 +1856,8 @@ AddFDef("everconfirmed", "Ever Confirmed", 0);
AddFDef("reporter_accessible", "Reporter Accessible", 0); AddFDef("reporter_accessible", "Reporter Accessible", 0);
AddFDef("cclist_accessible", "CC Accessible", 0); AddFDef("cclist_accessible", "CC Accessible", 0);
AddFDef("bug_group", "Group", 0); AddFDef("bug_group", "Group", 0);
AddFDef("estimated_time", "Estimated Hours", 1);
AddFDef("remaining_time", "Remaining Hours", 0);
# Oops. Bug 163299 # Oops. Bug 163299
$dbh->do("DELETE FROM fielddefs WHERE name='cc_accessible'"); $dbh->do("DELETE FROM fielddefs WHERE name='cc_accessible'");
...@@ -1860,6 +1865,8 @@ $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("flagtypes.name", "Flag", 0);
AddFDef("requesters.login_name", "Flag Requester", 0); AddFDef("requesters.login_name", "Flag Requester", 0);
AddFDef("setters.login_name", "Flag Setter", 0); AddFDef("setters.login_name", "Flag Setter", 0);
AddFDef("work_time", "Hours Worked", 0);
AddFDef("percentage_complete", "Percentage Complete", 0);
########################################################################### ###########################################################################
# Detect changed local settings # Detect changed local settings
...@@ -2903,6 +2910,11 @@ if (GetFieldDef("bugs","qacontact_accessible")) { ...@@ -2903,6 +2910,11 @@ if (GetFieldDef("bugs","qacontact_accessible")) {
DropField("bugs", "assignee_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-03-15 bbaetz@student.usyd.edu.au - bug 129466
# 2002-05-13 preed@sigkill.com - bug 129446 patch backported to the # 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 # BUGZILLA-2_14_1-BRANCH as a security blocker for the 2.14.2 release
......
...@@ -58,6 +58,11 @@ if (@::legal_keywords) { ...@@ -58,6 +58,11 @@ if (@::legal_keywords) {
push(@masterlist, "keywords"); push(@masterlist, "keywords");
} }
if (UserInGroup(Param("timetrackinggroup"))) {
push(@masterlist, ("estimated_time", "remaining_time", "actual_time",
"percentage_complete"));
}
push(@masterlist, ("summary", "summaryfull")); push(@masterlist, ("summary", "summaryfull"));
$vars->{'masterlist'} = \@masterlist; $vars->{'masterlist'} = \@masterlist;
......
...@@ -862,6 +862,14 @@ Reason: %reason% ...@@ -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', name => 'loginnetmask',
desc => 'The number of bits for the netmask used if a user chooses to ' . 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 ' . 'allow a login to be valid for more than a single IP. Setting ' .
......
...@@ -296,7 +296,8 @@ sub FetchOneColumn { ...@@ -296,7 +296,8 @@ sub FetchOneColumn {
"status", "resolution", "summary"); "status", "resolution", "summary");
sub AppendComment { 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 # Use the date/time we were given if possible (allowing calling code
# to synchronize the comment's timestamp with those of other records). # to synchronize the comment's timestamp with those of other records).
...@@ -304,15 +305,26 @@ sub AppendComment { ...@@ -304,15 +305,26 @@ sub AppendComment {
$comment =~ s/\r\n/\n/g; # Get rid of windows-style line endings. $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. $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; return;
} }
my $whoid = DBNameToIdAndCheck($who); my $whoid = DBNameToIdAndCheck($who);
my $privacyval = $isprivate ? 1 : 0 ; 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) . ", " . "VALUES($bugid, $whoid, $timestamp, " . SqlQuote($comment) . ", " .
$privacyval . ")"); $privacyval . ", " . SqlQuote($work_time) . ")");
SendSQL("UPDATE bugs SET delta_ts = now() WHERE bug_id = $bugid"); SendSQL("UPDATE bugs SET delta_ts = now() WHERE bug_id = $bugid");
} }
...@@ -1104,7 +1116,7 @@ sub GetLongDescriptionAsText { ...@@ -1104,7 +1116,7 @@ sub GetLongDescriptionAsText {
$query .= "ORDER BY longdescs.bug_when"; $query .= "ORDER BY longdescs.bug_when";
SendSQL($query); SendSQL($query);
while (MoreSQLData()) { while (MoreSQLData()) {
my ($who, $when, $text, $isprivate) = (FetchSQLData()); my ($who, $when, $text, $isprivate, $work_time) = (FetchSQLData());
if ($count) { if ($count) {
$result .= "\n\n------- Additional Comments From $who".Param('emailsuffix')." ". $result .= "\n\n------- Additional Comments From $who".Param('emailsuffix')." ".
time2str("%Y-%m-%d %H:%M", str2time($when)) . " -------\n"; time2str("%Y-%m-%d %H:%M", str2time($when)) . " -------\n";
...@@ -1124,7 +1136,7 @@ sub GetComments { ...@@ -1124,7 +1136,7 @@ sub GetComments {
my @comments; my @comments;
SendSQL("SELECT profiles.realname, profiles.login_name, SendSQL("SELECT profiles.realname, profiles.login_name,
date_format(longdescs.bug_when,'%Y-%m-%d %H:%i'), date_format(longdescs.bug_when,'%Y-%m-%d %H:%i'),
longdescs.thetext, longdescs.thetext, longdescs.work_time,
isprivate, isprivate,
date_format(longdescs.bug_when,'%Y%m%d%H%i%s') date_format(longdescs.bug_when,'%Y%m%d%H%i%s')
FROM longdescs, profiles FROM longdescs, profiles
...@@ -1134,7 +1146,8 @@ sub GetComments { ...@@ -1134,7 +1146,8 @@ sub GetComments {
while (MoreSQLData()) { while (MoreSQLData()) {
my %comment; 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{'isprivate'}, $comment{'when'}) = FetchSQLData();
$comment{'email'} .= Param('emailsuffix'); $comment{'email'} .= Param('emailsuffix');
...@@ -1490,6 +1503,20 @@ sub PerformSubsts { ...@@ -1490,6 +1503,20 @@ sub PerformSubsts {
return $str; 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 # Global Templatization Code
......
...@@ -56,7 +56,9 @@ my $generic_query = " ...@@ -56,7 +56,9 @@ my $generic_query = "
bugs.target_milestone, bugs.target_milestone,
bugs.qa_contact, bugs.qa_contact,
bugs.status_whiteboard, bugs.status_whiteboard,
bugs.keywords bugs.keywords,
bugs.estimated_time,
bugs.remaining_time
FROM bugs,profiles assign,profiles report, products, components FROM bugs,profiles assign,profiles report, products, components
WHERE assign.userid = bugs.assigned_to AND report.userid = bugs.reporter WHERE assign.userid = bugs.assigned_to AND report.userid = bugs.reporter
AND bugs.product_id=products.id AND bugs.component_id=components.id"; AND bugs.product_id=products.id AND bugs.component_id=components.id";
...@@ -79,7 +81,8 @@ foreach my $bug_id (split(/[:,]/, $buglist)) { ...@@ -79,7 +81,8 @@ foreach my $bug_id (split(/[:,]/, $buglist)) {
"op_sys", "bug_status", "resolution", "priority", "op_sys", "bug_status", "resolution", "priority",
"bug_severity", "component", "assigned_to", "reporter", "bug_severity", "component", "assigned_to", "reporter",
"bug_file_loc", "short_desc", "target_milestone", "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; $bug{$field} = shift @row;
} }
...@@ -91,6 +94,12 @@ foreach my $bug_id (split(/[:,]/, $buglist)) { ...@@ -91,6 +94,12 @@ foreach my $bug_id (split(/[:,]/, $buglist)) {
push (@bugs, \%bug); 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 # Add the list of bug hashes to the variables
......
...@@ -233,7 +233,8 @@ if ($::FORM{'keywords'} && UserInGroup("editbugs")) { ...@@ -233,7 +233,8 @@ if ($::FORM{'keywords'} && UserInGroup("editbugs")) {
# Build up SQL string to add bug. # Build up SQL string to add bug.
my $sql = "INSERT INTO bugs " . my $sql = "INSERT INTO bugs " .
"(" . join(",", @used_fields) . ", reporter, creation_ts) " . "(" . join(",", @used_fields) . ", reporter, creation_ts, " .
"estimated_time, remaining_time) " .
"VALUES ("; "VALUES (";
foreach my $field (@used_fields) { foreach my $field (@used_fields) {
...@@ -246,7 +247,23 @@ $comment = trim($comment); ...@@ -246,7 +247,23 @@ $comment = trim($comment);
# OK except for the fact that it causes e-mail to be suppressed. # OK except for the fact that it causes e-mail to be suppressed.
$comment = $comment ? $comment : " "; $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 # Groups
my @groupstoadd = (); my @groupstoadd = ();
......
...@@ -703,6 +703,25 @@ if (defined $::FORM{'qa_contact'}) { ...@@ -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, # 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 # and that bug is restricted to a group, process the checkboxes that
...@@ -808,6 +827,12 @@ SWITCH: for ($::FORM{'knob'}) { ...@@ -808,6 +827,12 @@ SWITCH: for ($::FORM{'knob'}) {
last SWITCH; last SWITCH;
}; };
/^resolve$/ && CheckonComment( "resolve" ) && do { /^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 # Check here, because its the only place we require the resolution
CheckFormField(\%::FORM, 'resolution', \@::settable_resolution); CheckFormField(\%::FORM, 'resolution', \@::settable_resolution);
ChangeStatus('RESOLVED'); ChangeStatus('RESOLVED');
...@@ -1170,6 +1195,26 @@ foreach my $id (@idlist) { ...@@ -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) { if (@::legal_keywords) {
# There are three kinds of "keywordsaction": makeexact, add, delete. # There are three kinds of "keywordsaction": makeexact, add, delete.
# For makeexact, we delete everything, and then add our things. # For makeexact, we delete everything, and then add our things.
...@@ -1229,17 +1274,11 @@ foreach my $id (@idlist) { ...@@ -1229,17 +1274,11 @@ foreach my $id (@idlist) {
SendSQL("DELETE FROM bug_group_map SendSQL("DELETE FROM bug_group_map
WHERE bug_id = $id AND group_id = $grouptodel"); WHERE bug_id = $id AND group_id = $grouptodel");
} }
SendSQL("select now()");
$timestamp = FetchOneColumn();
my $groupDelNames = join(',', @groupDelNames); my $groupDelNames = join(',', @groupDelNames);
my $groupAddNames = join(',', @groupAddNames); my $groupAddNames = join(',', @groupAddNames);
LogActivityEntry($id, "bug_group", $groupDelNames, $groupAddNames); LogActivityEntry($id, "bug_group", $groupDelNames, $groupAddNames);
if (defined $::FORM{'comment'}) {
AppendComment($id, $::COOKIE{'Bugzilla_login'}, $::FORM{'comment'},
$::FORM{'commentprivacy'}, $timestamp);
}
my $removedCcString = ""; my $removedCcString = "";
if (defined $::FORM{newcc} || defined $::FORM{removecc} || defined $::FORM{masscc}) { if (defined $::FORM{newcc} || defined $::FORM{removecc} || defined $::FORM{masscc}) {
......
...@@ -129,12 +129,13 @@ sub ProcessOneBug { ...@@ -129,12 +129,13 @@ sub ProcessOneBug {
if ($values{'qa_contact'}) { if ($values{'qa_contact'}) {
$values{'qa_contact'} = DBID_to_name($values{'qa_contact'}); $values{'qa_contact'} = DBID_to_name($values{'qa_contact'});
} }
$values{'estimated_time'} = FormatTimeUnit($values{'estimated_time'});
my @diffs; my @diffs;
SendSQL("SELECT profiles.login_name, fielddefs.description, " . 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 " . "FROM bugs_activity, fielddefs, profiles " .
"WHERE bug_id = $id " . "WHERE bug_id = $id " .
" AND fielddefs.fieldid = bugs_activity.fieldid " . " AND fielddefs.fieldid = bugs_activity.fieldid " .
...@@ -150,21 +151,32 @@ sub ProcessOneBug { ...@@ -150,21 +151,32 @@ sub ProcessOneBug {
} }
my $difftext = ""; my $difftext = "";
my $diffheader = "";
my $diffpart = {};
my @diffparts;
my $lastwho = ""; my $lastwho = "";
foreach my $ref (@diffs) { 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) { if ($who ne $lastwho) {
$lastwho = $who; $lastwho = $who;
$difftext .= "\n$who" . Param('emailsuffix') . " changed:\n\n"; $diffheader = "\n$who" . Param('emailsuffix') . " changed:\n\n";
$difftext .= FormatTriple("What ", "Removed", "Added"); $diffheader .= FormatTriple("What ", "Removed", "Added");
$difftext .= ('-' x 76) . "\n"; $diffheader .= ('-' x 76) . "\n";
} }
$what =~ s/^Attachment/Attachment #$attachid/ if $attachid; $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 = ""; my $deptext = "";
...@@ -220,7 +232,9 @@ sub ProcessOneBug { ...@@ -220,7 +232,9 @@ sub ProcessOneBug {
$deptext = trim($deptext); $deptext = trim($deptext);
if ($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 { ...@@ -301,9 +315,9 @@ sub ProcessOneBug {
if ( !defined(NewProcessOnePerson($person, $count, \@headerlist, if ( !defined(NewProcessOnePerson($person, $count, \@headerlist,
\@reasons, \%values, \@reasons, \%values,
\%defmailhead, \%defmailhead,
\%fielddescription, $difftext, \%fielddescription, \@diffparts,
$newcomments, $anyprivate, $newcomments,
$start, $id, $anyprivate, $start, $id,
\@depbugs))) \@depbugs)))
{ {
...@@ -613,14 +627,16 @@ sub filterEmailGroup ($$$) { ...@@ -613,14 +627,16 @@ sub filterEmailGroup ($$$) {
} }
sub NewProcessOnePerson ($$$$$$$$$$$$$) { sub NewProcessOnePerson ($$$$$$$$$$$$$) {
my ($person, $count, $hlRef, $reasonsRef, $valueRef, $dmhRef, $fdRef, $difftext, my ($person, $count, $hlRef, $reasonsRef, $valueRef, $dmhRef, $fdRef,
$newcomments, $anyprivate, $start, $id, $depbugsRef) = @_; $diffRef, $newcomments, $anyprivate, $start,
$id, $depbugsRef) = @_;
my %values = %$valueRef; my %values = %$valueRef;
my @headerlist = @$hlRef; my @headerlist = @$hlRef;
my @reasons = @$reasonsRef; my @reasons = @$reasonsRef;
my %defmailhead = %$dmhRef; my %defmailhead = %$dmhRef;
my %fielddescription = %$fdRef; my %fielddescription = %$fdRef;
my @diffparts = @$diffRef;
my @depbugs = @$depbugsRef; my @depbugs = @$depbugsRef;
if ($seen{$person}) { if ($seen{$person}) {
...@@ -680,10 +696,41 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) { ...@@ -680,10 +696,41 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) {
if (! $value) { if (! $value) {
next; next;
} }
my $desc = $fielddescription{$f}; # Don't send estimated_time if user not in the group, or not enabled
$head .= FormatDouble($desc, $value); 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 "") { if ($difftext eq "" && $newcomments eq "") {
# Whoops, no differences! # Whoops, no differences!
......
...@@ -281,7 +281,16 @@ shift @::legal_resolution; ...@@ -281,7 +281,16 @@ shift @::legal_resolution;
# Another hack - this array contains "" for some reason. See bug 106589. # Another hack - this array contains "" for some reason. See bug 106589.
$vars->{'resolution'} = \@::legal_resolution; $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->{'bug_status'} = \@::legal_bug_status;
$vars->{'rep_platform'} = \@::legal_platform; $vars->{'rep_platform'} = \@::legal_platform;
$vars->{'op_sys'} = \@::legal_opsys; $vars->{'op_sys'} = \@::legal_opsys;
...@@ -295,6 +304,13 @@ push(@fields, { name => "noop", description => "---" }); ...@@ -295,6 +304,13 @@ push(@fields, { name => "noop", description => "---" });
SendSQL("SELECT name, description FROM fielddefs ORDER BY sortkey"); SendSQL("SELECT name, description FROM fielddefs ORDER BY sortkey");
while (MoreSQLData()) { while (MoreSQLData()) {
my ($name, $description) = FetchSQLData(); 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 }); push(@fields, { name => $name, description => $description });
} }
......
...@@ -32,6 +32,8 @@ ...@@ -32,6 +32,8 @@
# incomplete_data: boolean. True if some of the data is incomplete (because # incomplete_data: boolean. True if some of the data is incomplete (because
# it was affected by an old Bugzilla bug.) # it was affected by an old Bugzilla bug.)
#%] #%]
[% PROCESS bug/time.html.tmpl %]
[% IF incomplete_data %] [% IF incomplete_data %]
<p> <p>
...@@ -72,14 +74,26 @@ ...@@ -72,14 +74,26 @@
</td> </td>
<td> <td>
[% IF change.removed %] [% 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 %] [% ELSE %]
&nbsp; &nbsp;
[% END %] [% END %]
</td> </td>
<td> <td>
[% IF change.added %] [% 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 %] [% ELSE %]
&nbsp; &nbsp;
[% END %] [% END %]
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
[% count = count + 1 %] [% count = count + 1 %]
[% END %] [% END %]
[% PROCESS bug/time.html.tmpl %]
[%############################################################################%] [%############################################################################%]
[%# Block for individual comments #%] [%# Block for individual comments #%]
...@@ -43,9 +44,11 @@ ...@@ -43,9 +44,11 @@
<i>------- Additional Comment <i>------- Additional Comment
<a name="c[% count %]" href="#c[% count %]">#[% count %]</a> From <a name="c[% count %]" href="#c[% count %]">#[% count %]</a> From
<a href="mailto:[% comment.email FILTER html %]">[% comment.name FILTER html %]</a> <a href="mailto:[% comment.email FILTER html %]">[% comment.name FILTER html %]</a>
[%+ comment.time %] ------- [%+ comment.time %]
-------
</i> </i>
[% END %] [% END %]
[% IF mode == "edit" && isinsider %] [% IF mode == "edit" && isinsider %]
<i> <i>
<input type=hidden name="oisprivate-[% count %]" <input type=hidden name="oisprivate-[% count %]"
...@@ -55,7 +58,12 @@ ...@@ -55,7 +58,12 @@
[% " checked=\"checked\"" IF comment.isprivate %]> Private [% " checked=\"checked\"" IF comment.isprivate %]> Private
</i> </i>
[% END %] [% 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 [%# Don't indent the <pre> block, since then the spaces are displayed in the
# generated HTML # generated HTML
#%] #%]
......
...@@ -155,6 +155,20 @@ ...@@ -155,6 +155,20 @@
<td colspan="3"></td> <td colspan="3"></td>
</tr> </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> <tr>
<td align="right"><strong>URL:</strong></td> <td align="right"><strong>URL:</strong></td>
<td colspan="3"> <td colspan="3">
......
...@@ -32,6 +32,29 @@ ...@@ -32,6 +32,29 @@
[% END %] [% END %]
[% PROCESS bug/navigate.html.tmpl %] [% 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> <hr>
...@@ -264,6 +287,62 @@ ...@@ -264,6 +287,62 @@
</tr> </tr>
[% END %] [% END %]
</table> </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 *** %] [%# *** Attachments *** %]
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
title = "Full Text Bug Listing" title = "Full Text Bug Listing"
style_urls = [ "css/show_multiple.css" ] style_urls = [ "css/show_multiple.css" ]
%] %]
[% PROCESS bug/time.html.tmpl %]
[% IF bugs.first %] [% IF bugs.first %]
[% FOREACH bug = bugs %] [% FOREACH bug = bugs %]
[% PROCESS bug_display %] [% PROCESS bug_display %]
...@@ -34,6 +35,7 @@ ...@@ -34,6 +35,7 @@
</p> </p>
[% END %] [% END %]
[% PROCESS global/footer.html.tmpl %] [% PROCESS global/footer.html.tmpl %]
...@@ -130,6 +132,32 @@ ...@@ -130,6 +132,32 @@
</tr> </tr>
[% END %] [% 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> <tr>
<td colspan="4"> <td colspan="4">
<b>Description:</b> <b>Description:</b>
......
<!-- 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 %]
...@@ -330,8 +330,16 @@ ...@@ -330,8 +330,16 @@
[% ELSIF error == "need_component" %] [% ELSIF error == "need_component" %]
[% title = "Component Required" %] [% 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" %] [% ELSIF error == "need_product" %]
[% title = "Product Required" %] [% title = "Product Required" %]
You must specify a product to help determine the new owner of these bugs. You must specify a product to help determine the new owner of these bugs.
...@@ -445,7 +453,7 @@ ...@@ -445,7 +453,7 @@
[% ELSIF error == "report_access_denied" %] [% ELSIF error == "report_access_denied" %]
[% title = "Access Denied" %] [% title = "Access Denied" %]
You do not have the permissions necessary to view reports for this product. You do not have the permissions necessary to view reports for this product.
[% ELSIF error == "requestee_too_short" %] [% ELSIF error == "requestee_too_short" %]
[% title = "Requestee Name Too Short" %] [% title = "Requestee Name Too Short" %]
One or two characters match too many users, so please enter at least One or two characters match too many users, so please enter at least
...@@ -470,6 +478,11 @@ ...@@ -470,6 +478,11 @@
[% ELSIF error == "require_summary" %] [% ELSIF error == "require_summary" %]
[% title = "Summary Needed" %] [% title = "Summary Needed" %]
You must enter a summary for this bug. 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" %] [% ELSIF error == "sanity_check_access_denied" %]
[% title = "Access Denied" %] [% title = "Access Denied" %]
...@@ -521,6 +534,10 @@ ...@@ -521,6 +534,10 @@
[% title = "Wrong Token" %] [% title = "Wrong Token" %]
That token cannot be used to change your email address. 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" %] [% ELSIF error == "z_axis_defined_with_no_x_axis" %]
[% title = "Nonsensical Options" %] [% title = "Nonsensical Options" %]
You've defined a field for multiple tables without having defined You've defined a field for multiple tables without having defined
......
...@@ -116,6 +116,25 @@ ...@@ -116,6 +116,25 @@
</tr> </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") %] [% IF Param("useqacontact") %]
<tr> <tr>
<th><label for="qa_contact">QA Contact:</label></th> <th><label for="qa_contact">QA Contact:</label></th>
......
...@@ -49,11 +49,14 @@ ...@@ -49,11 +49,14 @@
"version" => { maxlength => 5 , title => "Vers" } , "version" => { maxlength => 5 , title => "Vers" } ,
"os" => { maxlength => 4 } , "os" => { maxlength => 4 } ,
"target_milestone" => { title => "TargetM" } , "target_milestone" => { title => "TargetM" } ,
"percentage_complete" => { format_value => "%d %%" } ,
} }
%] %]
[% qorder = order FILTER url_quote IF order %] [% qorder = order FILTER url_quote IF order %]
[% PROCESS bug/time.html.tmpl %]
[%############################################################################%] [%############################################################################%]
[%# Table Header #%] [%# Table Header #%]
[%############################################################################%] [%############################################################################%]
...@@ -132,7 +135,15 @@ ...@@ -132,7 +135,15 @@
[% FOREACH column = displaycolumns %] [% FOREACH column = displaycolumns %]
<td> <td>
[% '<nobr>' IF NOT abbrev.$column.wrap %] [% '<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 %] [%- '</nobr>' IF NOT abbrev.$column.wrap %]
</td> </td>
[% END %] [% END %]
......
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