diff options
-rw-r--r-- | Bugzilla/Search.pm | 61 | ||||
-rw-r--r-- | CGI.pl | 71 | ||||
-rw-r--r-- | bug_form.pl | 14 | ||||
-rwxr-xr-x | buglist.cgi | 33 | ||||
-rwxr-xr-x | checksetup.pl | 12 | ||||
-rwxr-xr-x | colchange.cgi | 5 | ||||
-rw-r--r-- | defparams.pl | 8 | ||||
-rw-r--r-- | globals.pl | 41 | ||||
-rwxr-xr-x | long_list.cgi | 13 | ||||
-rwxr-xr-x | post_bug.cgi | 21 | ||||
-rwxr-xr-x | process_bug.cgi | 51 | ||||
-rwxr-xr-x | processmail | 79 | ||||
-rwxr-xr-x | query.cgi | 18 | ||||
-rw-r--r-- | template/en/default/bug/activity/table.html.tmpl | 18 | ||||
-rw-r--r-- | template/en/default/bug/comments.html.tmpl | 12 | ||||
-rw-r--r-- | template/en/default/bug/create/create.html.tmpl | 14 | ||||
-rw-r--r-- | template/en/default/bug/edit.html.tmpl | 79 | ||||
-rw-r--r-- | template/en/default/bug/show-multiple.html.tmpl | 28 | ||||
-rw-r--r-- | template/en/default/bug/time.html.tmpl | 48 | ||||
-rw-r--r-- | template/en/default/global/user-error.html.tmpl | 23 | ||||
-rw-r--r-- | template/en/default/list/edit-multiple.html.tmpl | 19 | ||||
-rw-r--r-- | template/en/default/list/table.html.tmpl | 13 |
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"); @@ -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! @@ -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 %] [% 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 %] [% 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> </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> + [% PROCESS formattimeunit time_unit=bug.estimated_time %] + + <b>Current Est.:</b> + [% PROCESS formattimeunit + time_unit=(bug.remaining_time + bug.actual_time) %] + + <b>Hours Worked:</b> + [% PROCESS formattimeunit time_unit=bug.actual_time %] + <b>Hours Left:</b> + [% PROCESS formattimeunit time_unit=bug.remaining_time %] + + <b>Percentage Complete:</b> + [% PROCESS calculatepercentage act=bug.actual_time + rem=bug.remaining_time %] + <b>Gain</b> + [% PROCESS formattimeunit + time_unit=bug.estimated_time - (bug.actual_time + bug.remaining_time) %] + + </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 %] |