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 {
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");
......
......@@ -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'}) {
......
......@@ -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;
......
......@@ -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 ";
}
......
......@@ -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
......
......@@ -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;
......
......@@ -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 ' .
......
......@@ -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
......
......@@ -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
......
......@@ -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 = ();
......
......@@ -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}) {
......
......@@ -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!
......
......@@ -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 });
}
......
......@@ -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 %]
......
......@@ -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
#%]
......
......@@ -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">
......
......@@ -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 *** %]
......
......@@ -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>
......
<!-- 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 @@
[% 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
......
......@@ -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>
......
......@@ -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 %]
......
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