[Rt-commit] rt branch, 4.4/timeworked-per-user, created. rt-4.2.11-131-gedd13c0

Todd Wade todd at bestpractical.com
Tue Aug 11 07:33:03 EDT 2015


The branch, 4.4/timeworked-per-user has been created
        at  edd13c0c291fb9701358df0929c19406d1b00c2b (commit)

- Log -----------------------------------------------------------------
commit 081f4d1b967d8162d9709cb85499db181763c83b
Author: Todd Wade <todd at bestpractical.com>
Date:   Wed Jul 8 01:01:12 2015 -0400

    add a way for tests to get ticket pages besides the display page

diff --git a/lib/RT/Test/Web.pm b/lib/RT/Test/Web.pm
index 1f44d3c..718744d 100644
--- a/lib/RT/Test/Web.pm
+++ b/lib/RT/Test/Web.pm
@@ -159,13 +159,14 @@ sub logout {
 sub goto_ticket {
     my $self = shift;
     my $id   = shift;
+    my $view = shift || 'Display';
     unless ( $id && int $id ) {
         Test::More::diag( "error: wrong id ". defined $id? $id : '(undef)' );
         return 0;
     }
 
     my $url = $self->rt_base_url;
-    $url .= "Ticket/Display.html?id=$id";
+    $url .= "Ticket/${ view }.html?id=$id";
     $self->get($url);
     unless ( $self->status == 200 ) {
         Test::More::diag( "error: status is ". $self->status );

commit 890234b21945660d22c1f10a0d3e7628cd07bead
Author: Todd Wade <todd at bestpractical.com>
Date:   Wed Jul 8 01:02:45 2015 -0400

    Automatically update the timeworked field of a ticket's parent tickets
    
    Increment parent tickets TimeWorked field when a child ticket TimeWorked
    is set or upated via scrip.
    
    For new installs, default to the behavior enabled. For upgrades, install
    the scrip disabled.

diff --git a/etc/initialdata b/etc/initialdata
index dd1daf5..981c955 100644
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -886,3 +886,34 @@ Hour:         { $SubscriptionObj->SubValue('Hour') }
         },
     },
 );
+
+# timeworked related functionality that was cored from extensions
+
+push @ScripActions => (
+    {
+        Name        => 'Update Parent TimeWorked',    # loc
+        Description => 'Update Parent TimeWorked',    # loc
+        ExecModule  => 'UpdateParentTimeWorked',
+    },
+);
+
+
+push @ScripConditions => (
+    {
+        Name                 => 'On TimeWorked Change',        # loc
+        Description          => 'When TimeWorked Change',    # loc
+        ApplicableTransTypes => 'Any',
+        ExecModule           => 'TimeWorkedChange',
+    },
+);
+
+push @Scrips => (
+    {
+        Description    => 'On TimeWorked Change Update Parent TimeWorked',
+        ScripCondition => 'On TimeWorked Change',
+        ScripAction    => 'Update Parent TimeWorked',
+        Template       => 'Blank',
+    },
+);
+
+1;
diff --git a/etc/upgrade/4.4.0rc1/content b/etc/upgrade/4.4.0rc1/content
new file mode 100644
index 0000000..c06c83f
--- /dev/null
+++ b/etc/upgrade/4.4.0rc1/content
@@ -0,0 +1,29 @@
+ at ScripActions = (
+    {
+        Name        => 'Update Parent TimeWorked',    # loc
+        Description => 'Update Parent TimeWorked',    # loc
+        ExecModule  => 'UpdateParentTimeWorked',
+    },
+);
+
+
+ at ScripConditions = (
+    {
+        Name                 => 'On TimeWorked Change',        # loc
+        Description          => 'When TimeWorked Change',    # loc
+        ApplicableTransTypes => 'Any',
+        ExecModule           => 'TimeWorkedChange',
+    },
+);
+
+ at Scrips = (
+    {
+        Description    => 'On TimeWorked Change Update Parent TimeWorked',
+        ScripCondition => 'On TimeWorked Change',
+        ScripAction    => 'Update Parent TimeWorked',
+        Template       => 'Blank',
+        Disabled       => 1,
+    },
+);
+
+1;
diff --git a/lib/RT/Action/UpdateParentTimeWorked.pm b/lib/RT/Action/UpdateParentTimeWorked.pm
new file mode 100644
index 0000000..e91479a
--- /dev/null
+++ b/lib/RT/Action/UpdateParentTimeWorked.pm
@@ -0,0 +1,43 @@
+use strict;
+use warnings;
+
+package RT::Action::UpdateParentTimeWorked;
+use base 'RT::Action';
+
+sub Prepare {
+    my $self = shift;
+    my $ticket = $self->TicketObj;
+    return 0 unless $ticket->MemberOf->Count;
+    return 1;
+}
+
+sub Commit {
+    my $self   = shift;
+    my $ticket = $self->TicketObj;
+    my $txn    = $self->TransactionObj;
+
+    my $parents     = $ticket->MemberOf;
+    my $time_worked = $txn->TimeTaken
+      || ( $txn->NewValue - $txn->OldValue );
+
+    while ( my $parent = $parents->Next ) {
+        my $parent_ticket = $parent->TargetObj;
+        my $original_actor = RT::CurrentUser->new( $txn->Creator );
+        my $actor_parent_ticket = RT::Ticket->new( $original_actor );
+        $actor_parent_ticket->Load( $parent_ticket->Id );
+        unless ( $actor_parent_ticket->Id ) {
+            RT->Logger->error("Unable to load ".$parent_ticket->Id." as ".$txn->Creator->Name);
+            return 0;
+        }
+        my ( $ret, $msg ) = $actor_parent_ticket->_Set(
+            Field   => 'TimeWorked',
+            Value   => $parent_ticket->TimeWorked + $time_worked,
+        );
+        unless ($ret) {
+            RT->Logger->error(
+                "Failed to update parent ticket's TimeWorked: $msg");
+        }
+    }
+}
+
+1;
diff --git a/lib/RT/Condition/TimeWorkedChange.pm b/lib/RT/Condition/TimeWorkedChange.pm
new file mode 100644
index 0000000..f6d9322
--- /dev/null
+++ b/lib/RT/Condition/TimeWorkedChange.pm
@@ -0,0 +1,19 @@
+use strict;
+use warnings;
+
+package RT::Condition::TimeWorkedChange;
+use base 'RT::Condition';
+
+sub IsApplicable {
+    my $self = shift;
+    my $txn = $self->TransactionObj;
+    return 1 if $txn->TimeTaken;
+    return 1
+      if $txn->Type eq 'Set'
+          && $txn->Field eq 'TimeWorked'
+          && ( $txn->NewValue - $txn->OldValue );
+    return 0;
+}
+
+
+1;
diff --git a/t/web/ticket_timeworked.t b/t/web/ticket_timeworked.t
new file mode 100644
index 0000000..91cccdc
--- /dev/null
+++ b/t/web/ticket_timeworked.t
@@ -0,0 +1,76 @@
+use strict;
+use warnings;
+use RT::Test tests => undef;
+
+my ( $baseurl, $m ) = RT::Test->started_ok;
+ok( $m->login, "Logged in" );
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+ok( $queue->id, "loaded the General queue" );
+
+my ( $child1, $child2 ) = RT::Test->create_tickets(
+    { Queue   => 'General' },
+    { Subject => 'child ticket 1', },
+    { Subject => 'child ticket 2', },
+);
+
+my ( $child1_id, $child2_id ) = ( $child1->id, $child2->id );
+my $parent_id; # id of the parent ticket
+
+diag "add ticket links of type MemberOf base"; {
+    my $ticket = RT::Test->create_ticket(
+        Queue   => 'General',
+        Subject => "timeworked parent",
+    );
+    my $id = $parent_id = $ticket->id;
+
+    $m->goto_ticket($id);
+    $m->follow_link_ok( { text => 'Links' }, "Followed link to Links" );
+
+    ok( $m->form_with_fields("MemberOf-$id"), "found the form" );
+    $m->field( "MemberOf-$id", "$child1_id $child2_id" );
+
+    $m->submit;
+
+    $m->content_like(
+        qr{"DeleteLink-.*?ticket/$child1_id-MemberOf-"},
+        "base for MemberOf: has child1 ticket",
+    );
+    $m->content_like(
+        qr{"DeleteLink-.*?ticket/$child2_id-MemberOf-"},
+        "base for MemberOf: has child2 ticket",
+    );
+
+    $m->goto_ticket($id);
+    $m->content_like( qr{$child1_id:.*?\[new\]}, "has active ticket", );
+}
+
+my @updates = ({
+    id => $child1_id,
+    view => 'Modify',
+    field => 'TimeWorked',
+    form => 'TicketModify',
+    title => "Modify ticket #$child1_id",
+}, {
+    id => $child2_id,
+    view => 'Update',
+    field => 'UpdateTimeWorked',
+    form => 'TicketUpdate',
+    title => "Update ticket #$child2_id (child ticket 2)",
+});
+
+
+foreach my $update ( @updates ) {
+    $m->goto_ticket( $update->{id}, $update->{view} );
+    $m->title_is( $update->{title}, 'have child ticket page' );
+    ok( $m->form_name( $update->{form} ), 'found the form' );
+    $m->field( $update->{field}, 90 );
+    $m->submit_form( button => 'SubmitTicket' );
+}
+
+$m->goto_ticket( $parent_id );
+$m->title_is( "#$parent_id: timeworked parent");
+$m->content_like( qr{180 minutes}, "found expected minutes in parent ticket" );
+
+undef $m;
+done_testing();
\ No newline at end of file

commit edd13c0c291fb9701358df0929c19406d1b00c2b
Author: Todd Wade <todd at bestpractical.com>
Date:   Wed Jul 8 22:00:06 2015 -0400

    Break out the time worked on a ticket per user
    
    Under the total time worked in the ticket basics on the ticket display
    page, display the time that each user has worked on the ticket. A scrip
    is used to cache the time worked per user in a ticket attribute.
    
    For new installs, default to the behavior enabled. For upgrades, install
    the scrip disabled.

diff --git a/etc/initialdata b/etc/initialdata
index 981c955..405df0a 100644
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -895,6 +895,11 @@ push @ScripActions => (
         Description => 'Update Parent TimeWorked',    # loc
         ExecModule  => 'UpdateParentTimeWorked',
     },
+    {
+        Name        => 'Update User TimeWorked',    # loc
+        Description => 'Update User TimeWorked',    # loc
+        ExecModule  => 'UpdateUserTimeWorked',
+    },
 );
 
 
@@ -914,6 +919,10 @@ push @Scrips => (
         ScripAction    => 'Update Parent TimeWorked',
         Template       => 'Blank',
     },
+    {
+        Description    => 'On TimeWorked Change Update User TimeWorked',
+        ScripCondition => 'On TimeWorked Change',
+        ScripAction    => 'Update User TimeWorked',
+        Template       => 'Blank',
+    },
 );
-
-1;
diff --git a/etc/upgrade/4.4.0rc1/content b/etc/upgrade/4.3.8/content
similarity index 57%
rename from etc/upgrade/4.4.0rc1/content
rename to etc/upgrade/4.3.8/content
index c06c83f..e1949b5 100644
--- a/etc/upgrade/4.4.0rc1/content
+++ b/etc/upgrade/4.3.8/content
@@ -1,13 +1,21 @@
- at ScripActions = (
+use warnings;
+use strict;
+
+our @ScripActions = (
     {
         Name        => 'Update Parent TimeWorked',    # loc
         Description => 'Update Parent TimeWorked',    # loc
         ExecModule  => 'UpdateParentTimeWorked',
     },
+    {
+        Name        => 'Update User TimeWorked',    # loc
+        Description => 'Update User TimeWorked',    # loc
+        ExecModule  => 'UpdateUserTimeWorked',
+    },
 );
 
 
- at ScripConditions = (
+our @ScripConditions = (
     {
         Name                 => 'On TimeWorked Change',        # loc
         Description          => 'When TimeWorked Change',    # loc
@@ -16,7 +24,7 @@
     },
 );
 
- at Scrips = (
+our @Scrips = (
     {
         Description    => 'On TimeWorked Change Update Parent TimeWorked',
         ScripCondition => 'On TimeWorked Change',
@@ -24,6 +32,13 @@
         Template       => 'Blank',
         Disabled       => 1,
     },
+    {
+        Description    => 'On TimeWorked Change Update User TimeWorked',
+        ScripCondition => 'On TimeWorked Change',
+        ScripAction    => 'Update User TimeWorked',
+        Template       => 'Blank',
+        Disabled       => 1,
+    },
 );
 
 1;
diff --git a/lib/RT/Action/UpdateParentTimeWorked.pm b/lib/RT/Action/UpdateParentTimeWorked.pm
index e91479a..6904f55 100644
--- a/lib/RT/Action/UpdateParentTimeWorked.pm
+++ b/lib/RT/Action/UpdateParentTimeWorked.pm
@@ -1,9 +1,72 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
 use strict;
 use warnings;
 
 package RT::Action::UpdateParentTimeWorked;
 use base 'RT::Action';
 
+=head1 NAME
+
+RT::Action::UpdateParentTimeWorked - RT's scrip action to set/update the time
+worked on a parent ticket when a child ticket's TimeWorked is added to.
+
+=head1 DESCRIPTION
+
+This action is used as an action for the 'On TimeWorked Change' condition.
+
+When it fires it finds a ticket's parent tickets and increments the time on
+those tickets along with the built in behavior of incrementing the TimeWorked
+on the current ticket.
+
+=cut
+
 sub Prepare {
     my $self = shift;
     my $ticket = $self->TicketObj;
@@ -40,4 +103,10 @@ sub Commit {
     }
 }
 
+=head1 AUTHOR
+
+Best Practical Solutions, LLC E<lt>modules at bestpractical.comE<gt>
+
+=cut
+
 1;
diff --git a/lib/RT/Action/UpdateUserTimeWorked.pm b/lib/RT/Action/UpdateUserTimeWorked.pm
new file mode 100644
index 0000000..ddd221b
--- /dev/null
+++ b/lib/RT/Action/UpdateUserTimeWorked.pm
@@ -0,0 +1,95 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Action::UpdateUserTimeWorked;
+use base 'RT::Action';
+
+=head1 NAME
+
+RT::Action::UpdateUserTimeWorked - RT's scrip action to set/update the time
+worked for a user each time they log time worked on a ticket
+
+=head1 DESCRIPTION
+
+This action is used as an action for the 'On TimeWorked Change' condition.
+
+When it fires, a ticket attribute stores the amount of time the user updating
+the ticket worked on it.
+
+=cut
+
+sub Prepare {
+    return 1;
+}
+
+sub Commit {
+    my $self   = shift;
+    my $ticket = $self->TicketObj;
+    my $txn    = $self->TransactionObj;
+
+    my $time_worked_attr = $ticket->FirstAttribute('TimeWorked');
+    # if the attribute is not defined, we will initialize it in the callback,
+    # so no need to handle it here
+    if ( $time_worked_attr ) {
+        my $time_worked = $time_worked_attr->Content;
+        $time_worked->{ $txn->CreatorObj->Name } += $txn->TimeTaken
+          || $txn->NewValue - $txn->OldValue;
+        $time_worked_attr->SetContent( $time_worked );
+    }
+}
+
+=head1 AUTHOR
+
+Best Practical Solutions, LLC E<lt>modules at bestpractical.comE<gt>
+
+=cut
+
+1;
diff --git a/lib/RT/Condition/TimeWorkedChange.pm b/lib/RT/Condition/TimeWorkedChange.pm
index f6d9322..469cb2b 100644
--- a/lib/RT/Condition/TimeWorkedChange.pm
+++ b/lib/RT/Condition/TimeWorkedChange.pm
@@ -1,9 +1,69 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
 use strict;
 use warnings;
 
 package RT::Condition::TimeWorkedChange;
 use base 'RT::Condition';
 
+=head1 NAME
+
+RT::Condition::TimeWorkedChange - RT's scrip condition that fires when the
+TimeWorked field has a value at form submission.
+
+=head1 DESCRIPTION
+
+This condition is true when the transaction has a TimeTaken value or the
+TimeWorked field is being updated.
+
+=cut
+
 sub IsApplicable {
     my $self = shift;
     my $txn = $self->TransactionObj;
@@ -15,5 +75,10 @@ sub IsApplicable {
     return 0;
 }
 
+=head1 AUTHOR
+
+Best Practical Solutions, LLC E<lt>modules at bestpractical.comE<gt>
+
+=cut
 
 1;
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 193b2c2..3f288e0 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -3978,6 +3978,10 @@ our %SCRUBBER_ALLOWED_ATTRIBUTES = (
     }ix,
     dir    => qr/^(rtl|ltr)$/i,
     lang   => qr/^\w+(-\w+)?$/,
+
+    # timeworked per user attributes
+    'data-ticket-id'    => 1,
+    'data-ticket-class' => 1,
 );
 
 our %SCRUBBER_RULES = ();
diff --git a/share/html/Ticket/Elements/ShowBasics b/share/html/Ticket/Elements/ShowBasics
index 546f581..9c37eb3 100644
--- a/share/html/Ticket/Elements/ShowBasics
+++ b/share/html/Ticket/Elements/ShowBasics
@@ -62,11 +62,26 @@
 % }
 % $m->callback( %ARGS, CallbackName => 'AfterTimeEstimated', TicketObj => $Ticket );
 % if ($Ticket->TimeWorked) {
-  <tr class="time worked">
+  <tr class="time worked sum">
     <td class="label"><&|/l&>Worked</&>:</td>
     <td class="value"><& ShowTime, minutes => $Ticket->TimeWorked &></td>
   </tr>
 % }
+% if ( keys %$time_worked ) {
+<tr class="time worked by-user">
+  <td class="label"><&|/l&>Users</&>:</td>
+  <td>
+    <table>
+%   for my $user ( keys %$time_worked ) {
+      <tr>
+        <td class="value"><% $user %>:</td>
+        <td class="value"><& /Ticket/Elements/ShowTime, minutes => $time_worked->{$user} &></td>
+      </tr>
+%   }
+    </table>
+  </td>
+</tr>
+% }
 % $m->callback( %ARGS, CallbackName => 'AfterTimeWorked', TicketObj => $Ticket );
 % if ($Ticket->TimeLeft) {
   <tr class="time left">
@@ -95,3 +110,43 @@
 $Ticket => undef
 $UngroupedCFs => 0
 </%ARGS>
+<%init>
+my $time_worked;
+if ( $Ticket->TimeWorked ) {
+    my $time_worked_attr = $Ticket->FirstAttribute('TimeWorked');
+
+    if ($time_worked_attr) {
+        $time_worked = $time_worked_attr->Content;
+    } else {
+        $time_worked = {};
+        my $transactions = $Ticket->Transactions;
+        $transactions->Limit(
+            FIELD     => 'Type',
+            VALUE     => 'Set',
+            SUBCLAUSE => 'timeworked',
+        );
+
+        $transactions->Limit(
+            FIELD           => 'Field',
+            VALUE           => 'TimeWorked',
+            SUBCLAUSE       => 'timeworked',
+            ENTRYAGGREGATOR => 'AND',
+        );
+
+        $transactions->Limit(
+            FIELD           => 'TimeTaken',
+            VALUE           => 0,
+            OPERATOR        => '>',
+            SUBCLAUSE       => 'timeworked',
+            ENTRYAGGREGATOR => 'OR',
+        );
+
+        while ( my $txn = $transactions->Next ) {
+            $time_worked->{ $txn->CreatorObj->Name } += $txn->TimeTaken
+              || $txn->NewValue - $txn->OldValue;
+        }
+        $Ticket->SetAttribute( Name => 'TimeWorked', Content => $time_worked );
+    }
+}
+
+</%init>
diff --git a/share/static/css/base/ticket-form.css b/share/static/css/base/ticket-form.css
index 15ce713..358ae4f 100644
--- a/share/static/css/base/ticket-form.css
+++ b/share/static/css/base/ticket-form.css
@@ -81,3 +81,8 @@ iframe.richtext-editor {
     position: absolute;
     margin-left: 0.5em;
 }
+
+form.add_time_worked {
+    display: inline;
+    margin-left: 10px;
+}
diff --git a/share/static/js/util.js b/share/static/js/util.js
index 6cc2acf..46ae5bd 100644
--- a/share/static/js/util.js
+++ b/share/static/js/util.js
@@ -501,3 +501,28 @@ jQuery(function() {
     jQuery('select.chosen').chosen({ width: '20em', placeholder_text_multiple: ' ', no_results_text: ' ', search_contains: true });
     AddAttachmentWarning();
 });
+
+// timeworked javascript
+jQuery( function () {
+    jQuery('a[data-ticket-class=add_time_worked]').click( function () {
+        var form = jQuery(this).siblings('form');
+        form.find('input[name=TimeWorked]').focus();
+        return false;
+    });
+
+    jQuery('form.add_time_worked.template input[name=ActivityDate]').val(
+        jQuery('input[name=ActivityDate]:first').val());
+
+    var form_template = jQuery('form.add_time_worked.template');
+    jQuery('a[data-ticket-class=add_time_worked]').each( function ( index, e ) {
+        var id = jQuery(this).attr('data-ticket-id');
+        var form = form_template.clone();
+        form.insertAfter(e);
+        form.find('input[name=Ticket]').val(id);
+        form.removeClass('hidden template');
+    });
+
+    jQuery('input[name=ActivityDate]:first').change( function() {
+        jQuery(this).closest('form').submit();
+    });
+});
diff --git a/t/web/ticket_timeworked.t b/t/web/ticket_timeworked.t
index 91cccdc..4737bda 100644
--- a/t/web/ticket_timeworked.t
+++ b/t/web/ticket_timeworked.t
@@ -17,7 +17,7 @@ my ( $child1, $child2 ) = RT::Test->create_tickets(
 my ( $child1_id, $child2_id ) = ( $child1->id, $child2->id );
 my $parent_id; # id of the parent ticket
 
-diag "add ticket links of type MemberOf base"; {
+diag "add ticket links for timeworked tests"; {
     my $ticket = RT::Test->create_ticket(
         Queue   => 'General',
         Subject => "timeworked parent",
@@ -45,32 +45,106 @@ diag "add ticket links of type MemberOf base"; {
     $m->content_like( qr{$child1_id:.*?\[new\]}, "has active ticket", );
 }
 
-my @updates = ({
-    id => $child1_id,
-    view => 'Modify',
-    field => 'TimeWorked',
-    form => 'TicketModify',
-    title => "Modify ticket #$child1_id",
-}, {
-    id => $child2_id,
-    view => 'Update',
-    field => 'UpdateTimeWorked',
-    form => 'TicketUpdate',
-    title => "Update ticket #$child2_id (child ticket 2)",
-});
-
-
-foreach my $update ( @updates ) {
-    $m->goto_ticket( $update->{id}, $update->{view} );
-    $m->title_is( $update->{title}, 'have child ticket page' );
-    ok( $m->form_name( $update->{form} ), 'found the form' );
-    $m->field( $update->{field}, 90 );
-    $m->submit_form( button => 'SubmitTicket' );
+diag "adding timeworked values for child tickets"; {
+    my $user_a = RT::Test->load_or_create_user(
+        Name => 'user_a', Password => 'password',
+    );
+    ok $user_a && $user_a->id, 'loaded or created user';
+
+    my $user_b = RT::Test->load_or_create_user(
+        Name => 'user_b', Password => 'password',
+    );
+    ok $user_b && $user_b->id, 'loaded or created user';
+
+    ok( RT::Test->set_rights(
+        { Principal => $user_a, Right => [qw(SeeQueue ShowTicket ModifyTicket CommentOnTicket)] },
+        { Principal => $user_b, Right => [qw(SeeQueue ShowTicket ModifyTicket CommentOnTicket)] },
+    ), 'set rights');
+
+
+    my @updates = ({
+        id => $child1_id,
+        view => 'Modify',
+        field => 'TimeWorked',
+        form => 'TicketModify',
+        title => "Modify ticket #$child1_id",
+        time => 45,
+        user => 'user_a',
+    }, {
+        id => $child2_id,
+        view => 'Modify',
+        field => 'TimeWorked',
+        form => 'TicketModify',
+        title => "Modify ticket #$child2_id",
+        time => 35,
+        user => 'user_a',
+    }, {
+        id => $child2_id,
+        view => 'Update',
+        field => 'UpdateTimeWorked',
+        form => 'TicketUpdate',
+        title => "Update ticket #$child2_id (child ticket 2)",
+        time => 90,
+        user => 'user_b',
+    });
+
+    foreach my $update ( @updates ) {
+        my $agent = RT::Test::Web->new;
+        ok $agent->login($update->{user}, 'password'), 'logged in as user';
+        $agent->goto_ticket( $update->{id}, $update->{view} );
+        $agent->title_is( $update->{title}, 'have child ticket page' );
+        ok( $agent->form_name( $update->{form} ), 'found the form' );
+        $agent->field( $update->{field}, $update->{time} );
+        $agent->submit_form( button => 'SubmitTicket' );
+    }
+}
+
+diag "checking parent ticket for expected timeworked data"; {
+    $m->goto_ticket( $parent_id );
+    $m->title_is( "#$parent_id: timeworked parent");
+    $m->content_like(
+        qr{(?s)Worked:.+?value">2\.8 hours \(170 minutes\)},
+        "found expected total TimeWorked in parent ticket"
+    );
+    $m->content_like(
+        qr{(?s)user_a:.+?value">1\.3 hours \(80 minutes\)},
+        "found expected user_a TimeWorked in parent ticket"
+    );
+    $m->content_like(
+        qr{(?s)user_b:.+?value">1\.5 hours \(90 minutes\)},
+        "found expected user_b TimeWorked in parent ticket"
+    );
+}
+
+diag "checking child ticket 1 for expected timeworked data"; {
+    $m->goto_ticket( $child1_id );
+    $m->title_is( "#$child1_id: child ticket 1");
+    $m->content_like(
+        qr{(?s)Worked:.+?value">45 minutes},
+        "found expected total TimeWorked in child ticket 1"
+    );
+    $m->content_like(
+        qr{(?s)user_a:.+?value">45 minutes},
+        "found expected user_a TimeWorked in child ticket 1"
+    );
 }
 
-$m->goto_ticket( $parent_id );
-$m->title_is( "#$parent_id: timeworked parent");
-$m->content_like( qr{180 minutes}, "found expected minutes in parent ticket" );
+diag "checking child ticket 2 for expected timeworked data"; {
+    $m->goto_ticket( $child2_id );
+    $m->title_is( "#$child2_id: child ticket 2");
+    $m->content_like(
+        qr{(?s)Worked:.+?value">2\.1 hours \(125 minutes\)},
+        "found expected total TimeWorked in child ticket 2"
+    );
+    $m->content_like(
+        qr{(?s)user_a:.+?value">35 minutes},
+        "found expected user_a TimeWorked in child ticket 2"
+    );
+    $m->content_like(
+        qr{(?s)user_b:.+?value">1\.5 hours \(90 minutes\)},
+        "found expected user_b TimeWorked in child ticket 2"
+    );
+}
 
 undef $m;
-done_testing();
\ No newline at end of file
+done_testing();

-----------------------------------------------------------------------


More information about the rt-commit mailing list