[Rt-commit] rt branch, 4.4.2-releng, created. rt-4.4.1-423-g0de542c

Shawn Moore shawn at bestpractical.com
Wed May 31 19:05:41 EDT 2017


The branch, 4.4.2-releng has been created
        at  0de542ca4a0f1d3ac23bc59b23849b3f21b21f22 (commit)

- Log -----------------------------------------------------------------
commit 8641712e90547fe67624aac9b983440a46917787
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu May 18 20:40:18 2017 +0000

    Fix keyboard shortcut UI for selecting tickets on old themes
    
    Due to how ticket search tables had borders in old themes, the CSS rule
    for showing the selected row needs to be scoped to td:first-child.
    
    Fixes: I#32748

diff --git a/share/static/css/aileron/ticket-lists.css b/share/static/css/aileron/ticket-lists.css
index a62ab4d..3261ee1 100644
--- a/share/static/css/aileron/ticket-lists.css
+++ b/share/static/css/aileron/ticket-lists.css
@@ -193,3 +193,7 @@ padding-bottom: 1em;
 .chart-meta .saved-search {
     display: inline-block;
 }
+
+.ticket-list .selected-row tr td:first-child {
+    border-left: 3px solid #3858a3;
+}
diff --git a/share/static/css/ballard/ticket-lists.css b/share/static/css/ballard/ticket-lists.css
index be83108..3511f8e 100644
--- a/share/static/css/ballard/ticket-lists.css
+++ b/share/static/css/ballard/ticket-lists.css
@@ -189,3 +189,7 @@ padding-bottom: 1em;
 .chart-meta .saved-search {
     display: inline-block;
 }
+
+.ticket-list .selected-row tr td:first-child {
+    border-left: 3px solid #3858a3;
+}
diff --git a/share/static/css/web2/ticket-lists.css b/share/static/css/web2/ticket-lists.css
index be83108..afd98ce 100644
--- a/share/static/css/web2/ticket-lists.css
+++ b/share/static/css/web2/ticket-lists.css
@@ -189,3 +189,8 @@ padding-bottom: 1em;
 .chart-meta .saved-search {
     display: inline-block;
 }
+
+.ticket-list .selected-row tr td:first-child {
+    border-left: 3px solid #3858a3;
+}
+

commit 10c2343f161f8feb3fb165408768843089b11135
Author: Aaron Kondziela <aaron at bestpractical.com>
Date:   Thu Feb 9 17:19:12 2017 -0500

    Add usage examples for Overlays documentation

diff --git a/lib/RT/Base.pm b/lib/RT/Base.pm
index 679e10c..be98480 100644
--- a/lib/RT/Base.pm
+++ b/lib/RT/Base.pm
@@ -152,6 +152,14 @@ sub loc_fuzzy {
     }
 }
 
+=head2 _ImportOverlays
+
+C<_ImportOverlays> is an internal method used to modify or add functionality
+to existing RT code. For more on how to use overlays with RT, please see the
+documentation in L<RT::StyleGuide>.
+
+=cut
+
 sub _ImportOverlays {
     my $class = shift;
     my ($package,undef,undef) = caller();
diff --git a/lib/RT/StyleGuide.pod b/lib/RT/StyleGuide.pod
index 3a75562..2b7306b 100644
--- a/lib/RT/StyleGuide.pod
+++ b/lib/RT/StyleGuide.pod
@@ -804,17 +804,84 @@ but new tables are expected to be consistent.
 
 =head2 The Overlay mechanism
 
-RT's classes allow "overlay" methods to be placed into files named Filename_Vendor.pm and Filename_Local.pm
-_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+RT's classes allow "overlay" methods to be placed into files named
+F<Filename_Vendor.pm> and F<Filename_Local.pm>. _Vendor is for 3rd-party
+vendor add-ons, while _Local is for site-local customizations.
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line
+Each of these files should begin with the line:
 
    no warnings qw(redefine);
 
 so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
 
+Some common ways that overlays are used:
+
+=head3 Adding Methods
+
+Create a file named F<Classname_Local.pm> as appropriate (like F<User_Local.pm>)
+in F<rt_base_dir/local/lib/RT/>.
+
+    no warnings qw(redefine);
+
+    sub MyNewMethod {
+        my $self = shift;
+        ...
+    }
+
+    1;
+
+=head3 Modifying Methods
+
+
+Create a file with a F<_Local.pm> suffix as appropriate in the same
+F<local/lib> tree mentioned above. Copy the method you need to
+modify from the original RT version of the file and paste it into your
+local version. Then modify the code to behave the way you need it to.
+
+When changing code in this way, make note of incoming values and especially
+return values since other code likely expects the existing method to return
+values a certain way.
+
+When copying code for modification, do not place the C<_ImportOverlays> call
+from the original at the bottom of your modified file.
+
+    no warnings qw(redefine);
+
+    sub ExistingMethod {
+        ... existing RT code
+        ... my special changes
+        ... existing RT code
+    }
+
+    1;
+
+=head3 Hooking Methods
+
+Set up your local file the same way as in B<Modifying Methods> described
+above. You'll need to save the original method call before you redfine it,
+and then call it at the head or tail of your code:
+
+    no warnings qw(redefine);
+
+    # This should be the same class we are overlaying here
+    my $original_method = \&RT::Class::MethodToHook;
+
+    sub MethodToHook {
+        my $self = shift;
+        ...
+        ... my special code
+        ...
+        # Call the original method at the tail of our extra code:
+        &$original_method( $args );
+    }
+
+    1;
+
+B<Remember!> If you modify existing core RT methods, you will need to update your
+local modifications when you upgrade the base RT code so that it matches.
+
 =head1 TO DO
 
 Talk about DBIx::SearchBuilder

commit 828a611b18bc5ca25a062f0f5627d88249df9743
Merge: aeeeb1c 10c2343
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Tue May 23 15:25:54 2017 -0400

    Merge branch '4.4/overlay-docs' into 4.4-trunk


commit fc40cb6aeed6d9027769eda18641811563887e12
Merge: 828a611 8641712
Author: Brian C. Duggan <brian at bestpractical.com>
Date:   Wed May 24 16:41:47 2017 -0400

    Merge branch '4.4/keyboard-old-themes' into 4.4-trunk


commit 1b78e8281b2082516766c8d9201cc16b2635f059
Author: Dave Goehrig <dave at bestpractical.com>
Date:   Tue Mar 28 11:59:50 2017 -0400

    Add Reports menu
    
    This commit adds the three reports from 3.8 back to RT 4.4.  The reports
    are added under a new top-level Reports menu.

diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index d2951f4..1bc1023 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -598,6 +598,27 @@ my $build_main_nav = sub {
     $search->child( assets => title => loc("Assets"), path => "/Asset/Search/" )
         if $session{CurrentUser}->HasRight( Right => 'ShowAssetsMenu', Object => RT->System );
 
+    my $reports = Menu->child( reports =>
+        title       => loc('Reports'),
+        description => loc('Reports summarizing ticket resolution and status'),
+        path        => loc('/Reports'),
+    );
+    $reports->child( resolvedbyowner =>
+        title       => loc('Resolved by owner'),
+        path        => '/Reports/ResolvedByOwner.html',
+        description => loc('Examine tickets resolved in a queue, grouped by owner'),
+    );
+    $reports->child( resolvedindaterange =>
+        title       => loc('Resolved in date range'),
+        path        => '/Reports/ResolvedByDates.html',
+        description => loc('Examine tickets resolved in a queue between two dates'),
+    );
+    $reports->child( createdindaterange =>
+        title       => loc('Created in a date range'),
+        path        => '/Reports/CreatedByDates.html',
+        description => loc('Examine tickets created in a queue between two dates'),
+    );
+
     if ($session{CurrentUser}->HasRight( Right => 'ShowArticlesMenu', Object => RT->System )) {
         my $articles = Menu->child( articles => title => loc('Articles'), path => "/Articles/index.html");
         $articles->child( articles => title => loc('Overview'), path => "/Articles/index.html" );
diff --git a/share/html/Reports/CreatedByDates.html b/share/html/Reports/CreatedByDates.html
new file mode 100644
index 0000000..b9a57a3
--- /dev/null
+++ b/share/html/Reports/CreatedByDates.html
@@ -0,0 +1,100 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2017 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 }}}
+<%ARGS>
+$Queue => undef
+$CreatedBefore => ''
+$CreatedAfter => ''
+</%ARGS>
+<%INIT>
+my $title = loc("Created tickets in period, grouped by status");
+my @clauses;
+my $QueueObj = RT::Queue->new($session{CurrentUser});
+
+if ($Queue) {
+    push @clauses, "Queue = '$Queue'";
+    $QueueObj->Load($Queue);
+}
+
+if ($CreatedAfter) {
+    my $after = RT::Date->new($session{'CurrentUser'});
+    $after->Set(Format => 'unknown', Value => $CreatedAfter);
+    $CreatedAfter = $after->ISO(Timezone => 'user');
+    push @clauses, "Created > '$CreatedAfter'";
+}
+
+if ($CreatedBefore) {
+    my $before = RT::Date->new($session{'CurrentUser'});
+    $before->Set(Format => 'unknown', Value => $CreatedBefore);
+    $CreatedBefore = $before->ISO(Timezone => 'user');
+    push @clauses, "Created < '$CreatedBefore'";
+}
+
+my $query = join ' AND ', @clauses;
+</%INIT>
+<& /Elements/Header, Title => $title &>
+<& /Elements/Tabs &>
+
+<form method="post" action="CreatedByDates.html">
+
+% if ($query) {
+<& /Search/Elements/Chart, Query => $query, GroupBy => 'Status' &>
+% }
+
+<hr />
+
+<br /><&|/l&>Queue</&>:
+<& /Elements/SelectQueue, Name => 'Queue', NamedValues => 1, Default => $QueueObj->id &>
+
+<br /><&|/l&>Tickets created after</&>:
+<& /Elements/SelectDate, Name => 'CreatedAfter', Default => $CreatedAfter &>
+
+<br /><&|/l&>Tickets created before</&>:
+<& /Elements/SelectDate, Name => 'CreatedBefore', Default => $CreatedBefore &>
+
+<& /Elements/Submit &>
+</form>
diff --git a/share/html/Reports/ResolvedByDates.html b/share/html/Reports/ResolvedByDates.html
new file mode 100644
index 0000000..f9ac249
--- /dev/null
+++ b/share/html/Reports/ResolvedByDates.html
@@ -0,0 +1,100 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2017 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 }}}
+<%ARGS>
+$Queue => undef
+$ResolvedBefore => ''
+$ResolvedAfter => ''
+</%ARGS>
+<%INIT>
+my $title = loc("Resolved tickets in period, grouped by owner");
+my @clauses = "Status = 'resolved'";
+my $QueueObj = RT::Queue->new($session{CurrentUser});
+
+if ($Queue) {
+    push @clauses, "Queue = '$Queue'";
+    $QueueObj->Load($Queue);
+}
+
+if ($ResolvedAfter) {
+    my $after = RT::Date->new($session{'CurrentUser'});
+    $after->Set(Format => 'unknown', Value => $ResolvedAfter);
+    $ResolvedAfter = $after->ISO(Timezone => 'user');
+    push @clauses, "Resolved > '$ResolvedAfter'";
+}
+
+if ($ResolvedBefore) {
+    my $before = RT::Date->new($session{'CurrentUser'});
+    $before->Set(Format => 'unknown', Value => $ResolvedBefore);
+    $ResolvedBefore = $before->ISO(Timezone => 'user');
+    push @clauses, "Resolved < '$ResolvedBefore'";
+}
+
+my $query = join ' AND ', @clauses;
+</%INIT>
+<& /Elements/Header, Title => $title &>
+<& /Elements/Tabs &>
+
+<form method="post" action="ResolvedByDates.html">
+
+% if ($query) {
+<& /Search/Elements/Chart, Query => $query, GroupBy => 'Owner.Name' &>
+% }
+
+<hr />
+
+<br /><&|/l&>Queue</&>:
+<& /Elements/SelectQueue, Name => 'Queue', NamedValues => 1, Default => $QueueObj->id &>
+
+<br /><&|/l&>Tickets resolved after</&>:
+<& /Elements/SelectDate, Name => 'ResolvedAfter', Default => $ResolvedAfter &>
+
+<br /><&|/l&>Tickets resolved before</&>:
+<& /Elements/SelectDate, Name => 'ResolvedBefore', Default => $ResolvedBefore &>
+
+<& /Elements/Submit &>
+</form>
diff --git a/share/html/Reports/ResolvedByOwner.html b/share/html/Reports/ResolvedByOwner.html
new file mode 100644
index 0000000..5c3ee0c
--- /dev/null
+++ b/share/html/Reports/ResolvedByOwner.html
@@ -0,0 +1,73 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2017 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 }}}
+<%ARGS>
+$Queue => undef
+</%ARGS>
+<%INIT>
+my $title = loc("Resolved tickets, grouped by owner");
+my $QueueObj = RT::Queue->new($session{'CurrentUser'});
+$QueueObj->Load($Queue) if $Queue;
+
+my $query = $Queue ? "Status = 'resolved' AND Queue = '$Queue'" : "";
+</%INIT>
+<& /Elements/Header, Title => $title &>
+<& /Elements/Tabs &>
+
+<form method="post" action="ResolvedByOwner.html">
+
+% if ($query) {
+<& /Search/Elements/Chart, Query => $query, GroupBy => 'Owner.Name' &>
+% }
+
+<hr />
+
+<br /><&|/l&>Queue</&>:
+<& /Elements/SelectQueue, Name => 'Queue', NamedValues => 1, Default => $QueueObj->id &>
+
+<& /Elements/Submit &>
+</form>
diff --git a/share/html/Reports/index.html b/share/html/Reports/index.html
new file mode 100644
index 0000000..7de43e6
--- /dev/null
+++ b/share/html/Reports/index.html
@@ -0,0 +1,50 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2017 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 }}}
+<& /Elements/Header, Title => loc('Reports') &>
+<& /Elements/Tabs &>
+<& /Elements/ListMenu, menu => Menu()->child('reports') &>

commit 58b093489b7dc50d02ca5e1804b9a4d272ce33a0
Merge: fc40cb6 1b78e82
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu May 25 16:36:20 2017 +0000

    Merge branch '4.4/reports' into 4.4-trunk


commit e4ca6e12a8296f07a7052bfc6492269152b05ac0
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu May 4 18:40:37 2017 +0000

    Support managing groups as watchers in REST
    
    By specifying a numeric ID rather than an email address, you can now
    add/remove a group as a ticket watcher over REST 1.0
    
    Fixes: T#185062

diff --git a/share/html/REST/1.0/Forms/ticket/default b/share/html/REST/1.0/Forms/ticket/default
index d4dbd9c..2f68555 100644
--- a/share/html/REST/1.0/Forms/ticket/default
+++ b/share/html/REST/1.0/Forms/ticket/default
@@ -361,20 +361,37 @@ else {
             my ($p, @msgs);
 
             my %new  = map {$_=>1} @{ vsplit($val) };
-            my %old  = map {$_=>1} $ticket->$key->MemberEmailAddresses;
+            my %old;
+
+            my $members = $ticket->$key->MembersObj;
+            while (my $member = $members->Next) {
+                my $principal = $member->MemberObj;
+                if ($principal->IsGroup) {
+                    $old{ $principal->Id } = 1;
+                }
+                else {
+                    $old{ $principal->Object->EmailAddress } = 1;
+                }
+            }
+
             my $type = $key eq 'Requestors' ? 'Requestor' : $key;
 
             foreach $p (keys %old) {
                 unless (exists $new{$p}) {
+                    my $key = "Email";
+                    $key = "PrincipalId" if $p =~ /^\d+$/;
+
                     ($s, $n) = $ticket->DeleteWatcher(Type => $type,
-                                                      Email => $p);
+                                                      $key => $p);
                     push @msgs, [ $s, $n ];
                 }
             }
             foreach $p (keys %new) {
-                unless ($ticket->IsWatcher(Type => $type, Email => $p)) {
+                my $key = "Email";
+                $key = "PrincipalId" if $p =~ /^\d+$/;
+                unless ($ticket->IsWatcher(Type => $type, $key => $p)) {
                     ($s, $n) = $ticket->AddWatcher(Type => $type,
-                                                   Email => $p);
+                                                   $key => $p);
                     push @msgs, [ $s, $n ];
                 }
             }

commit c16979682ed2e76f221e7ce32ec357451f4b5cbb
Merge: 58b0934 e4ca6e1
Author: Brian C. Duggan <brian at bestpractical.com>
Date:   Fri May 26 08:06:49 2017 -0400

    Merge branch '4.4/rest-watcher-id' into 4.4-trunk


commit 811c1f2b62d7e99bb333861249c9109220c90a02
Author: Matt Zagrabelny <mzagrabe at d.umn.edu>
Date:   Fri May 26 13:49:35 2017 -0500

    add CreatorObj reference to ShowTransaction Default callback
    
    Use a lexical scope variable for CreatorObj and expose it to default
    callback via a reference argument.
    
    This allows admins to modify the Creator that gets displayed for the
    transaction.

diff --git a/share/html/Elements/ShowTransaction b/share/html/Elements/ShowTransaction
index f7bdaa5..bae9156 100644
--- a/share/html/Elements/ShowTransaction
+++ b/share/html/Elements/ShowTransaction
@@ -57,7 +57,7 @@
 % $m->callback( %ARGS, Transaction => $Transaction, CallbackName => 'AfterAnchor' );
     <span class="date"><% $date |n %></span>
     <span class="description">
-      <& /Elements/ShowUser, User => $Transaction->CreatorObj &> - <% $desc |n %>
+      <& /Elements/ShowUser, User => $CreatorObj &> - <% $desc |n %>
 % $m->callback( %ARGS, Transaction => $Transaction, CallbackName => 'AfterDescription' );
     </span>
 % if ( $Object->isa("RT::Ticket") and $Object->CurrentUserCanSeeTime ) {
@@ -227,6 +227,8 @@ elsif ( %$Attachments && $ShowActions ) {
     }
 }
 
+my $CreatorObj = $Transaction->CreatorObj;
+
 $m->callback(
     %ARGS,
     Transaction => $Transaction,
@@ -238,6 +240,7 @@ $m->callback(
     TimeTaken   => \$time,
     Description => \$desc,
     ShowBody    => \$ShowBody,
+    CreatorObj  => \$CreatorObj,
 );
 
 my $actions = '';

commit ecd07082d28053b3cbe578070b4c1b6a6d358573
Merge: c169796 811c1f2b
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Fri May 26 15:12:36 2017 -0400

    Merge branch '4.4/add-creator-reference-argument-to-show-transaction-callback' into 4.4-trunk


commit 055a4f9dc9ba5050d8db56ff03f2f84e036313a4
Author: Dave Goehrig <dave at bestpractical.com>
Date:   Thu Feb 23 11:51:47 2017 -0500

    Add Total Time Worked feature
    
    This adds a new recursive TotalTimeWorked method to RT::Ticket
    and adds a COLUMN_MAP entry so that it can be displayed in searches.
    
    The feature is optional and requires setting $DisplayTotalTimeWorked
    in RT_SiteConfig.pm.
    
    Before creating tickets using this, one needs to disable the 'On
    TimeWorked Change Update Parent TimeWorked' scrip which mutates the
    value of time worked on parent tickets. It should be noted running
    this on an existing database will report invalid results for all tickets
    where that scrip was active as it will double-count all of the child
    time worked in the parent tickets. This is why the feature is opt in.

diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index f2581de..2224122 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -1411,8 +1411,38 @@ sub TimeEstimatedAsString {
     return $self->_DurationAsString( $self->TimeEstimated );
 }
 
+=head2 TotalTimeWorked
 
+Returns the amount of time worked on this ticket and all child tickets
 
+=cut
+
+sub TotalTimeWorked {
+    my $self = shift;
+    my $seen = shift || {};
+    my $time = $self->TimeWorked;
+    my $links = $self->Members;
+    LINK: while (my $link = $links->Next) {
+        my $obj = $link->BaseObj;
+        next LINK unless $obj && UNIVERSAL::isa($obj,'RT::Ticket');
+        next LINK if $seen->{$obj->Id};
+        $seen->{ $obj->Id } = 1;
+        $time += $obj->TotalTimeWorked($seen);
+    }
+    return $time;
+}
+
+=head2 TotalTimeWorkedAsString
+
+Returns the amount of time worked on this ticket and all its children as a
+formatted duration string
+
+=cut
+
+sub TotalTimeWorkedAsString {
+    my $self = shift;
+    return $self->_DurationAsString( $self->TotalTimeWorked );
+}
 
 =head2 Comment
 
diff --git a/share/html/Elements/RT__Ticket/ColumnMap b/share/html/Elements/RT__Ticket/ColumnMap
index 2db1149..089ccea 100644
--- a/share/html/Elements/RT__Ticket/ColumnMap
+++ b/share/html/Elements/RT__Ticket/ColumnMap
@@ -342,6 +342,16 @@ unless (RT->Config->Get('Crypt')->{'Enable'}) {
     $COLUMN_MAP->{KeyRequestors} = $GenericMap->{Requestors};
 }
 
+if(RT->Config->Get('DisplayTotalTimeWorked')) {
+  $COLUMN_MAP->{TotalTimeWorked} = {
+        attribute => 'TotalTimeWorked',
+        title => 'Total Time Worked',
+        value => sub {
+            return $_[0]->TotalTimeWorkedAsString;
+        },
+    }
+}
+
 $m->callback( GenericMap => $GenericMap, COLUMN_MAP => $COLUMN_MAP, CallbackName => 'Once', CallbackOnce => 1 );
 return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr );
 </%init>
diff --git a/share/html/Search/Elements/BuildFormatString b/share/html/Search/Elements/BuildFormatString
index 1bf71b1..711de89 100644
--- a/share/html/Search/Elements/BuildFormatString
+++ b/share/html/Search/Elements/BuildFormatString
@@ -101,6 +101,10 @@ my @fields = qw(
     NBSP
 ); # loc_qw
 
+# Total time worked is an optional ColumnMap enabled for rolling up child
+# TimeWorked
+push @fields, 'TotalTimeWorked' if (RT->Config->Get('DisplayTotalTimeWorked'));
+
 my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
 foreach my $id (keys %queues) {
     # Gotta load up the $queue object, since queues get stored by name now.
diff --git a/share/html/Ticket/Elements/ShowBasics b/share/html/Ticket/Elements/ShowBasics
index 0a06f02..94e3814 100644
--- a/share/html/Ticket/Elements/ShowBasics
+++ b/share/html/Ticket/Elements/ShowBasics
@@ -74,6 +74,13 @@
     <td class="value"><& ShowTime, minutes => $Ticket->TimeWorked &></td>
   </tr>
 % }
+% my $totalTimeWorked = 0;
+% if (RT->Config->Get('DisplayTotalTimeWorked') && ($totalTimeWorked = $Ticket->TotalTimeWorked)) {
+  <tr class="total time worked sum">
+    <td class="label"><&|/l&>Total Time Worked</&>:</td>
+    <td class="value"><& ShowTime, minutes => $totalTimeWorked &></td>
+  </tr>
+% }
 % if ( keys %$time_worked ) {
 <tr class="time worked by-user">
   <td class="label"><&|/l&>Users</&>:</td>
diff --git a/t/api/total-time-worked.t b/t/api/total-time-worked.t
new file mode 100644
index 0000000..3724abf
--- /dev/null
+++ b/t/api/total-time-worked.t
@@ -0,0 +1,183 @@
+use strict;
+use warnings;
+use RT;
+use RT::Test tests => undef;
+
+my $scrip = RT::Scrip->new(RT->SystemUser);
+$scrip->LoadByCols(Description => 'On TimeWorked Change Update Parent TimeWorked');
+$scrip->SetDisabled(1);
+
+
+diag("Test tickets total time worked");
+{
+    my $parent = RT::Ticket->new(RT->SystemUser);
+    my ($ok1,$msg1) = $parent->Create(
+        Queue => 'general',
+        Subject => 'total time worked test parent',
+    );
+    ok($ok1,"Created parent ticket $msg1");
+
+    my $child = RT::Ticket->new(RT->SystemUser);
+    my ($ok2,$msg2) = $child->Create(
+        Queue => 'general',
+        Subject => 'total time worked test child',
+    );
+    ok($ok2,"Created child ticket $msg2");
+
+    my $grandchild = RT::Ticket->new(RT->SystemUser);
+    my ($ok3,$msg3) = $grandchild->Create(
+        Queue => 'general',
+        Subject => 'total time worked test child child',
+    );
+    ok($ok3,"Created grandchild ticket $msg3");
+
+    my ($ok4,$msg4) = $parent->AddLink(
+        Type => 'MemberOf',
+        Base => $child->id,
+    );
+    ok($ok4,"Created parent -> child link $msg4");
+
+    my ($ok5,$msg5) = $child->AddLink(
+        Type => 'MemberOf',
+        Base => $grandchild->id,
+    );
+    ok($ok5,"Created child -> grandchild link $msg5");
+
+    my $grandchild2 = RT::Ticket->new(RT->SystemUser);
+    my ($ok6,$msg6) = $grandchild2->Create(
+        Queue => 'general',
+        Subject => 'total time worked test other child child',
+    );
+    ok($ok6,"Create second grandchild $msg6");
+
+    my ($ok7,$msg7) = $child->AddLink(
+        Type => 'MemberOf',
+        Base => $grandchild2->id,
+    );
+    ok($ok7,"Create child -> second grandchild link $msg7");
+
+    $parent->SetTimeWorked(10);
+    $child->SetTimeWorked(20);
+    $grandchild->SetTimeWorked(40);
+    $grandchild2->SetTimeWorked(50);
+
+    is $grandchild2->TimeWorked, 50, 'check other child child time worked';
+    is $grandchild->TimeWorked, 40, 'check child child time worked';
+    is $child->TimeWorked, 20, 'check child time worked';
+    is $parent->TimeWorked, 10, 'check parent time worked';
+
+    is $parent->TotalTimeWorked, 120, 'check parent total time worked';
+    is $child->TotalTimeWorked, 110, 'check child total time worked';
+    is $grandchild->TotalTimeWorked, 40, 'check child child total time worked';
+    is $grandchild2->TotalTimeWorked, 50, 'check other child child total time worked';
+
+    is $parent->TotalTimeWorkedAsString, '2 hours (120 minutes)', 'check parent total time worked as string';
+    is $grandchild->TotalTimeWorkedAsString, '40 minutes', 'check child child total time workd as string';
+}
+
+diag("Test multiple inheritance total time worked");
+{
+    my $parent = RT::Ticket->new(RT->SystemUser);
+    my ($ok1,$msg1) = $parent->Create(
+        Queue => 'general',
+        Subject => 'total time worked test parent',
+    );
+    ok $ok1, "created parent ticket $msg1";
+
+    my $child = RT::Ticket->new(RT->SystemUser);
+    my ($ok2,$msg2) = $child->Create(
+        Queue => 'general',
+        Subject => 'total time worked test child',
+    );
+    ok $ok2, "created child ticket $msg2";
+
+    my $grandchild = RT::Ticket->new(RT->SystemUser);
+    my ($ok3,$msg3) = $grandchild->Create(
+        Queue => 'general',
+        Subject => 'total time worked test child child, and test child',
+    );
+    ok $ok3, "created grandchild ticket $msg3";
+
+    $parent->SetTimeWorked(10);
+    $child->SetTimeWorked(20);
+    $grandchild->SetTimeWorked(40);
+
+    my ($ok4,$msg4) = $parent->AddLink(
+        Type => 'MemberOf',
+        Base => $child->id,
+    );
+    ok $ok4, "Create parent -> child link $msg4";
+
+    my ($ok5,$msg5) = $child->AddLink(
+        Type => 'MemberOf',
+        Base => $grandchild->id,
+    );
+    ok $ok5, "Create child -> grandchild link $msg5";
+
+     my ($ok6,$msg6) = $parent->AddLink(
+        Type => 'MemberOf',
+        Base => $grandchild->id,
+    );
+    ok $ok6, "Created parent -> grandchild link $msg6";
+
+    is $parent->TotalTimeWorked, 70, 'check parent total time worked';
+    is $child->TotalTimeWorked, 60, 'check child total time worked';
+    is $grandchild->TotalTimeWorked, 40, 'check child child total time worked';
+
+}
+
+diag("Test inheritance total time worked");
+{
+    my @warnings;
+
+    my $parent = RT::Ticket->new(RT->SystemUser);
+    my ($ok1,$msg1) = $parent->Create(
+        Queue => 'general',
+        Subject => 'total time worked test parent',
+    );
+    ok $ok1, "created parent ticket $msg1";
+
+    my $child = RT::Ticket->new(RT->SystemUser);
+    my ($ok2,$msg2) = $child->Create(
+        Queue => 'general',
+        Subject => 'total time worked test child',
+    );
+    ok $ok2, "created child ticket $msg2";
+
+    {
+        local $SIG{__WARN__} = sub {
+            push @warnings, @_;
+        };
+
+        my ($ok3,$msg3) = $parent->AddLink(
+            Type => 'MemberOf',
+            Base => $child->id,
+        );
+        ok $ok3, "Created parent -> child link $msg3";
+        my ($ok4,$msg4) = $parent->AddLink(
+            Type => 'MemberOf',
+            Base => 'http://bestpractical.com',
+        );
+        ok $ok4, "Create parent -> url link $msg4";
+
+        my ($ok5,$msg5) = $child->AddLink(
+            Type => 'MemberOf',
+            Base => 'http://docs.bestpractical.com/',
+        );
+        ok $ok5, "Created child -> url link $msg5";
+
+    }
+
+    $parent->SetTimeWorked(10);
+    $child->SetTimeWorked(20);
+
+    is $parent->TotalTimeWorked, 30, 'check parent total time worked';
+    is $child->TotalTimeWorked, 20, 'check child total time worked';
+
+   TODO: {
+       local $TODO = "this warns because of the unrelated I#31399";
+       is(@warnings, 0, "no warnings");
+   }
+}
+
+done_testing;

commit 934b6f47fa0d5265a1c6618e2479f1d5a7085ccf
Merge: ecd0708 055a4f9
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed May 31 18:21:35 2017 +0000

    Merge branch '4.4/total-time-worked' into 4.4-trunk


commit 0b3989be8e6e0b77d18fe63436f503fc4b279feb
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Tue May 16 15:56:04 2017 -0400

    Calculate time per user dynamically rather than from an attribute
    
    Storing time per user in an attribute saved checking each transaction
    on ticket display, however it was not possible to update values
    if data ever got out of sync for some reason. In testing, the
    performance impact of generating the time dynamically was not
    significant, even for tickets with large histories.

diff --git a/share/html/Ticket/Elements/ShowBasics b/share/html/Ticket/Elements/ShowBasics
index 94e3814..0d741d6 100644
--- a/share/html/Ticket/Elements/ShowBasics
+++ b/share/html/Ticket/Elements/ShowBasics
@@ -133,39 +133,16 @@ $UngroupedCFs => 0
 my $time_worked;
 my $show_time_worked = $Ticket->CurrentUserCanSeeTime;
 if ( $show_time_worked && $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',
-        );
+    my $transactions = $Ticket->Transactions;
+    $transactions->Limit(
+        FIELD           => 'TimeTaken',
+        VALUE           => 0,
+        OPERATOR        => '!=',
+    );
 
-        $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 );
+    while ( my $txn = $transactions->Next ) {
+        $time_worked->{ $txn->CreatorObj->Name } += $txn->TimeTaken;
     }
 }
 

commit 5d18c6fcb9da543980327506e2f5a95073bdaff6
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed May 31 19:00:10 2017 +0000

    Display total time worked per user if $DisplayTotalTimeWorked
    
    If the DisplayTotalTimeWorked setting is true, then use that as a hint
    that "Time worked per user" should include children tickets' time.

diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index 2224122..fb86c9b 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -1444,6 +1444,57 @@ sub TotalTimeWorkedAsString {
     return $self->_DurationAsString( $self->TotalTimeWorked );
 }
 
+=head2 TimeWorkedPerUser
+
+Returns a hash of user id to the amount of time worked on this ticket for
+that user
+
+=cut
+
+sub TimeWorkedPerUser {
+    my $self = shift;
+    my %time_worked;
+
+    my $transactions = $self->Transactions;
+    $transactions->Limit(
+        FIELD           => 'TimeTaken',
+        VALUE           => 0,
+        OPERATOR        => '!=',
+    );
+
+    while ( my $txn = $transactions->Next ) {
+        $time_worked{ $txn->CreatorObj->Name } += $txn->TimeTaken;
+    }
+
+    return \%time_worked;
+}
+
+=head2 TotalTimeWorkedPerUser
+
+Returns the amount of time worked on this ticket and all child tickets
+calculated per user
+
+=cut
+
+sub TotalTimeWorkedPerUser {
+    my $self = shift;
+    my $seen = shift || {};
+    my $time = $self->TimeWorkedPerUser;
+    my $links = $self->Members;
+    LINK: while (my $link = $links->Next) {
+        my $obj = $link->BaseObj;
+        next LINK unless $obj && UNIVERSAL::isa($obj,'RT::Ticket');
+        next LINK if $seen->{$obj->Id};
+        $seen->{ $obj->Id } = 1;
+
+        my $child_time = $obj->TotalTimeWorkedPerUser($seen);
+        for my $user_id (keys %$child_time) {
+            $time->{$user_id} += $child_time->{$user_id};
+        }
+    }
+    return $time;
+}
+
 =head2 Comment
 
 Comment on this ticket.
diff --git a/share/html/Ticket/Elements/ShowBasics b/share/html/Ticket/Elements/ShowBasics
index 0d741d6..6128bfd 100644
--- a/share/html/Ticket/Elements/ShowBasics
+++ b/share/html/Ticket/Elements/ShowBasics
@@ -132,17 +132,12 @@ $UngroupedCFs => 0
 <%init>
 my $time_worked;
 my $show_time_worked = $Ticket->CurrentUserCanSeeTime;
-if ( $show_time_worked && $Ticket->TimeWorked ) {
-
-    my $transactions = $Ticket->Transactions;
-    $transactions->Limit(
-        FIELD           => 'TimeTaken',
-        VALUE           => 0,
-        OPERATOR        => '!=',
-    );
-
-    while ( my $txn = $transactions->Next ) {
-        $time_worked->{ $txn->CreatorObj->Name } += $txn->TimeTaken;
+if ( $show_time_worked ) {
+    if (RT->Config->Get('DisplayTotalTimeWorked')) {
+        $time_worked = $Ticket->TotalTimeWorkedPerUser;
+    }
+    else {
+        $time_worked = $Ticket->TimeWorkedPerUser;
     }
 }
 
diff --git a/t/web/ticket_timeworked.t b/t/web/ticket_timeworked.t
index 1fa677d..974fa53 100644
--- a/t/web/ticket_timeworked.t
+++ b/t/web/ticket_timeworked.t
@@ -1,6 +1,6 @@
 use strict;
 use warnings;
-use RT::Test tests => undef;
+use RT::Test tests => undef, config => 'Set($DisplayTotalTimeWorked, 1);';
 
 my ( $baseurl, $m ) = RT::Test->started_ok;
 ok( $m->login, "Logged in" );

commit c5e51be42edf64b88354f60edf8230c3fc760449
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Tue May 16 16:20:37 2017 -0400

    Disable user time update scrip on upgrade
    
    This scrip is no longer needed as the ticket display page now
    calculates user time on tickets dynammically.

diff --git a/etc/upgrade/4.4.2/content b/etc/upgrade/4.4.2/content
index fbf5fb5..605d2f0 100644
--- a/etc/upgrade/4.4.2/content
+++ b/etc/upgrade/4.4.2/content
@@ -56,5 +56,17 @@ our @Initial = (
             );
         }
     },
-);
 
+    # Disable scrip On TimeWorked Change Update User TimeWorked
+    sub {
+        my $scrip = RT::Scrip->new(RT->SystemUser);
+        my ($ret, $msg) = $scrip->LoadByCols( Description => 'On TimeWorked Change Update User TimeWorked' );
+
+        unless ( $ret ){
+            RT->Logger->warning("Unable to load scrip On TimeWorked Change Update User TimeWorked: $msg. If you renamed this scrip, you can manually disable it as it is no longer needed.");
+            return;
+        }
+
+        $scrip->SetDisabled(1);
+    },
+);

commit 2db761af36183889a32aabe999a1cbe81bc71c4c
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Tue May 23 14:29:05 2017 -0400

    Document upgrading details for time worked changes

diff --git a/docs/UPGRADING-4.4 b/docs/UPGRADING-4.4
index 184c250..3d8c3ba 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -117,20 +117,22 @@ F<local/plugins/RT-Extension-ExternalStorage> from your RT installation.
 
 =item *
 
-RT now has the functionality from L<RT::Extension::ParentTimeWorked> built in.
-When a child ticket's TimeWorked field is updated, its parent ticket's
-TimeWorked field will also be incremented.
+RT now has the functionality from L<RT::Extension::ParentTimeWorked> built in,
+with some modifications. In the ParentTimeWorked extension, when a child ticket's
+TimeWorked field was updated, the parent ticket's TimeWorked field was also
+incremented. Starting with RT 4.4.2, the time from child tickets is available
+in the Total Time Worked field and the Time Worked value for the parent ticket
+is independent.
 
-It also has built-in functionality for recording time worked per user.
-When a ticket's TimeWorked field is updated, the time will be attributed
-to the currently logged in user.
+RT now also has built-in functionality to show time worked per user.
 
-Both of these functions are enabled via scrips. New installs will have these
-scrips by default. Upgrades will get them too, but set as disabled so they
-don't interfere with your existing configuration. If you would like to enable
-either of these functions, enable the 'On TimeWorked Change Update User
-TimeWorked' and/or 'On TimeWorked Change Update Parent TimeWorked' scrips. New
-deployments have these scrips enabled by default.
+For RT 4.4.0 and 4.4.1, these functions were enabled via scrips.
+New installs had these scrips by default. Upgrades got them too, but set as
+disabled so they didn't interfere with your existing configuration. These
+functions are now dynamic and no longer require these scrips.
+
+See L</"UPGRADING FROM 4.4.1 AND EARLIER"> for additional details on the changes
+introduced in 4.4.2.
 
 Users who are currently using
 L<RT::Extension::ParentTimeWorked|https://metacpan.org/pod/RT::Extension::ParentTimeWorked>
@@ -517,5 +519,80 @@ AfterUpdateCustomFieldValue. It will be removed in RT 4.6.
 
 =back
 
-=cut
+=head1 UPGRADING FROM 4.4.1 AND EARLIER
+
+=over 4
+
+=item *
+
+Changes to Time Worked Handling
+
+As noted above, in RT 4.4 we pulled in some time tracking features from
+extensions, specifically adding time worked on child tickets to the
+parent and showing time worked per user. In RT 4.4.2, we made some important
+changes to this functionality.
+
+The new features were installed but disabled for upgrades, so these changes
+are most important to RT users who installed RT starting with version 4.4.0
+or 4.4.1, or who users who upgraded from a previous RT and then manually
+enabled the time tracking scrips.
+
+=over 4
+
+=item *
+
+Parent Time Worked
+
+Based on feedback from users and our own experience, we determined that
+updating the Time Worked value on the parent ticket made it difficult
+to differentiate time recorded from children tickets from time recorded
+directly on the parent ticket. To clarify the history for these time
+entries, we created a new dynamic value called Total Time Worked that
+is a sum of Time Worked from the parent and all child tickets. You can
+activate this feature with the C<$DisplayTotalTimeWorked> configuration
+option in C<RT_SiteConfig.pm>.
 
+We generate this when displaying the ticket and we no longer update Time
+Worked on the parent ticket. This change also has the advantage that Total
+Time Worked automatically updates when a child ticket is added or removed.
+Total Time Worked is also available as a column for reports generated with
+the Query Builder.
+
+B<Important:> If you were previously using the parent time worked feature,
+the Time Worked value on parent tickets already contains time from child
+tickets. Total Time Worked will therefore be incorrect since it will add
+up all children then add the parent time (which already has the
+child time). You can choose to keep using the scrip and not show Total Time
+Worked, or update Time Worked on the parent ticket to have just parent time
+worked.
+
+Contact Best Practical at contact at bestpractical.com if you have questions
+about these changes.
+
+=item *
+
+Time Worked by User
+
+For the user time summary feature, we found some odd cases where a time
+value would not get recorded (if the scrip was mistakenly disabled, for
+example). In these cases, since the data was stored internally, it was
+difficult to update the user time values. We have made this feature
+dynamic so the user time information for the current ticket is generated
+when the ticket is displayed.
+
+If you previously used this feature, the user time data stored in an
+attribute on each ticket will still be in the database. Since it is only
+visible directly in the database, you can safely ignore it. However, if
+you want to remove it, you can locate these records with the following:
+
+    SELECT id, Name FROM Attributes WHERE ObjectType = 'RT::Ticket' AND Name = 'TimeWorked';
+
+You can delete these records from the Attributes table using a DELETE
+and the same WHERE clause. Always make sure you have a known good backup
+of your database before making updates.
+
+=back
+
+=back
+
+=cut

commit e3f7fd94e41bafb9b82463536b3eee23aaca533f
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Tue May 23 14:35:26 2017 -0400

    Remove User TimeWorked scrip from install and upgrade
    
    Also remove the scrip action module since it operates on an
    attribute that was removed in f1d99b32a8.

diff --git a/etc/initialdata b/etc/initialdata
index e81f82d..b6c2708 100644
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -129,10 +129,6 @@
       Description => 'Update Parent TimeWorked',     # loc
       ExecModule  => 'UpdateParentTimeWorked',
     },
-    { Name        => 'Update User TimeWorked',       # loc
-      Description => 'Update User TimeWorked',       # loc
-      ExecModule  => 'UpdateUserTimeWorked',
-    },
 );
 
 @ScripConditions = (
@@ -842,10 +838,6 @@ Hour:         { $SubscriptionObj->SubValue('Hour') }
        ScripCondition => 'On TimeWorked Change',
        ScripAction    => 'Update Parent TimeWorked',
        Template       => 'Blank' },
-    {  Description    => 'On TimeWorked Change Update User TimeWorked',
-       ScripCondition => 'On TimeWorked Change',
-       ScripAction    => 'Update User TimeWorked',
-       Template       => 'Blank' },
 );
 
 @ACL = (
diff --git a/etc/upgrade/4.3.9/content b/etc/upgrade/4.3.9/content
index 7db3bef..38f489c 100644
--- a/etc/upgrade/4.3.9/content
+++ b/etc/upgrade/4.3.9/content
@@ -7,11 +7,6 @@ our @ScripActions = (
         Description => 'Update Parent TimeWorked',    # loc
         ExecModule  => 'UpdateParentTimeWorked',
     },
-    {
-        Name        => 'Update User TimeWorked',    # loc
-        Description => 'Update User TimeWorked',    # loc
-        ExecModule  => 'UpdateUserTimeWorked',
-    },
 );
 
 
@@ -32,13 +27,6 @@ our @Scrips = (
         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/UpdateUserTimeWorked.pm b/lib/RT/Action/UpdateUserTimeWorked.pm
deleted file mode 100644
index 76fbd82..0000000
--- a/lib/RT/Action/UpdateUserTimeWorked.pm
+++ /dev/null
@@ -1,95 +0,0 @@
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2017 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;

commit d3a2fd2ce6ee4a4ea6efed327e5ebf20f53e19fe
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Thu May 25 13:44:53 2017 -0400

    Note details on operation of parent time worked action

diff --git a/lib/RT/Action/UpdateParentTimeWorked.pm b/lib/RT/Action/UpdateParentTimeWorked.pm
index 29d4c8f..1e1b543 100644
--- a/lib/RT/Action/UpdateParentTimeWorked.pm
+++ b/lib/RT/Action/UpdateParentTimeWorked.pm
@@ -61,9 +61,42 @@ worked on a parent ticket when a child ticket's TimeWorked is added to.
 
 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.
+When it fires it finds a ticket's parent tickets and increments the Time Worked
+value on those tickets along with the built-in behavior of incrementing Time
+Worked on the current ticket.
+
+=head2 Important Notes on Operation
+
+There are some important details related to the use of this scrip for time
+tracking that you should take into account when using it.
+
+=over
+
+=item * Parent and child time entries are combined on the parent
+
+This is the intended function of the scrip, but since the parent ticket has
+only one Time Worked value, it is difficult to differentiate time recorded
+directly on the parent ticket from time added from child tickets. If you record
+time only on child tickets, this is typically not an issue. If you record time
+on the parent ticket in addition to child tickets, it can make it difficult to
+pull out the time specifically logged on the parent.
+
+=item * A ticket must be linked as a child when the time is recorded
+
+For this scrip to work properly, a ticket must be linked to the parent as
+a child before time is entered. If you link a child ticket to a parent
+and it already has time recorded, that time will not be added to the parent.
+Similarly, if you remove a child ticket link from a parent, any previously
+recorded time will remain in the parent's Time Worked value. If you want the
+time removed, you must subtract it manually.
+
+=back
+
+RT has a feature called C<$DisplayTotalTimeWorked> that you can activate in
+your C<RT_SiteConfig.pm> file. This feature dynamically calculates time
+worked on child tickets and shows it on the parent. This solves the issues
+above because it doesn't modify the parent's Time Worked value and it will
+update if children tickets are added or removed.
 
 =cut
 

commit 55f51c3f56d058dcc0b021846017cc1575da5ccb
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Thu May 25 13:47:03 2017 -0400

    Remove ParentTimeWorked from upgrades and new installs
    
    ParentTimeWorked functionality was cored from an extension
    in 4.4.0, but after more use several drawbacks to this
    approach to tracking time from child tickets were observed.
    d3a2fd2ce documented these notes on the action module.
    
    Remove the scrip and action from new installs and upgrades
    in favor of the new $DisplayTotalTimeWorked feature.

diff --git a/etc/initialdata b/etc/initialdata
index b6c2708..e650661 100644
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -125,10 +125,6 @@
        Description => 'Set the due date according to an agreement' , # loc
        ExecModule  => 'SLA_SetDue',
     },
-    { Name        => 'Update Parent TimeWorked',     # loc
-      Description => 'Update Parent TimeWorked',     # loc
-      ExecModule  => 'UpdateParentTimeWorked',
-    },
 );
 
 @ScripConditions = (
@@ -834,10 +830,6 @@ Hour:         { $SubscriptionObj->SubValue('Hour') }
        ScripCondition    => 'Require due set according to SLA',
        ScripAction       => 'Set due date according to SLA',
        Template          => 'Blank' },
-    {  Description    => 'On TimeWorked Change Update Parent TimeWorked',
-       ScripCondition => 'On TimeWorked Change',
-       ScripAction    => 'Update Parent TimeWorked',
-       Template       => 'Blank' },
 );
 
 @ACL = (
diff --git a/etc/upgrade/4.3.9/content b/etc/upgrade/4.3.9/content
index 38f489c..bcf6d69 100644
--- a/etc/upgrade/4.3.9/content
+++ b/etc/upgrade/4.3.9/content
@@ -1,15 +1,6 @@
 use warnings;
 use strict;
 
-our @ScripActions = (
-    {
-        Name        => 'Update Parent TimeWorked',    # loc
-        Description => 'Update Parent TimeWorked',    # loc
-        ExecModule  => 'UpdateParentTimeWorked',
-    },
-);
-
-
 our @ScripConditions = (
     {
         Name                 => 'On TimeWorked Change',      # loc
@@ -19,14 +10,5 @@ our @ScripConditions = (
     },
 );
 
-our @Scrips = (
-    {
-        Description    => 'On TimeWorked Change Update Parent TimeWorked',
-        ScripCondition => 'On TimeWorked Change',
-        ScripAction    => 'Update Parent TimeWorked',
-        Template       => 'Blank',
-        Disabled       => 1,
-    },
-);
 
 1;

commit dec595a0db4618549293b6d7ff0682ab7d70fbb5
Merge: 934b6f4 55f51c3
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed May 31 19:20:29 2017 +0000

    Merge branch '4.4/dynamic-user-time' into 4.4-trunk


commit 0de542ca4a0f1d3ac23bc59b23849b3f21b21f22
Merge: dec595a bce3d73
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed May 31 19:57:43 2017 +0000

    Merge branch '4.2-trunk' into 4.4-trunk

diff --cc sbin/rt-test-dependencies.in
index 4822bdd,f579b0f..ec88168
--- a/sbin/rt-test-dependencies.in
+++ b/sbin/rt-test-dependencies.in
@@@ -310,11 -375,17 +310,12 @@@ File::Dropbo
  .
  
  my %AVOID = (
 -    'DBD::Oracle' => [qw(1.23)],
 +    'DBD::Oracle'       => [qw(1.23)],
      'Devel::StackTrace' => [qw(1.28 1.29)],
-     'DateTime::Locale'  => [qw(1.00 1.01)]
+     'DateTime::Locale'  => [qw(1.00 1.01)],
+     'DBD::mysql'        => [qw(4.042)],
  );
  
 -if ($args{'download'}) {
 -    download_mods();
 -}
 -
 -
  check_perl_version();
  
  check_users();
diff --cc share/html/Ticket/Elements/UpdateCc
index acc53b3,a2902cf..ed8b79c
--- a/share/html/Ticket/Elements/UpdateCc
+++ b/share/html/Ticket/Elements/UpdateCc
@@@ -139,7 -99,5 +139,9 @@@ foreach my $addr ( keys %txn_addresses
    push @one_time_Ccs,$addr;
  }
  
+ @one_time_Ccs = sort @one_time_Ccs;
++
 +my $hide_cc_suggestions =  RT->Config->Get('HideOneTimeSuggestions', $session{CurrentUser});
 +my $show_label    = $m->interp->apply_escapes( loc("show suggestions"), 'h' );
 +my $hide_label    = $m->interp->apply_escapes( loc("hide suggestions"), 'h' );
  </%init>

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


More information about the rt-commit mailing list