[Rt-commit] rt branch, 4.4/user-time-worked, created. rt-4.4.3-17-gfa5f41800

Craig Kaiser craig at bestpractical.com
Fri Jul 6 09:48:24 EDT 2018


The branch, 4.4/user-time-worked has been created
        at  fa5f41800f8e4eb31a94a07741f140a5f6d1fba7 (commit)

- Log -----------------------------------------------------------------
commit fa5f41800f8e4eb31a94a07741f140a5f6d1fba7
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Fri Jul 6 08:19:51 2018 -0400

    Create a RT time worked report by user
    
    Allow users to be searched over a flexible time period for tickets that
    they have worked time on. Make it simple for a user to view what other
    users are working on.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index e8a4545de..854916bb4 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -135,6 +135,7 @@ sub JSFiles {
       assets.js
       /static/RichText/ckeditor.js
       dropzone.min.js
+      user-time.js
       }, RT->Config->Get('JSFiles');
 }
 
diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index 40cb43e7c..734a81537 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -671,6 +671,12 @@ my $build_main_nav = sub {
         path        => '/Tools/MyDay.html',
     );
 
+    $tools->child( user_time =>
+        title       => loc('User Time'),
+        description => loc('See time worked by user'),
+        path        => '/Tools/UserTime.html',
+    );
+
     if ( RT->Config->Get('EnableReminders') ) {
         $tools->child( my_reminders =>
             title       => loc('My Reminders'),
diff --git a/share/html/Helpers/UserTime b/share/html/Helpers/UserTime
new file mode 100644
index 000000000..fe3897327
--- /dev/null
+++ b/share/html/Helpers/UserTime
@@ -0,0 +1,122 @@
+%# 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 }}}
+<%INIT>
+my @results;
+if ( $user_req && $start_date && $end_date ) {
+    my $user = RT::User->new( $session{CurrentUser} );
+
+    my ($ret, $msg) = $user->Load( $user_req );
+    push @results, $msg unless $ret;
+
+    my $start = RT::Date->new( $session{CurrentUser} );
+    $ret = $start->Set( Value => $start->ParseByTimeParseDate(Value => $start_date, Timezone => 'user') );
+    push @results, 'Invalid start date: ' . $start_date unless $ret;
+
+    my $end = RT::Date->new($session{CurrentUser});
+    ($ret, $msg) = $end->Set( Value => $end->ParseByTimeParseDate(Value => $end_date, Timezone => 'user') );
+    push @results, 'Invalid end date: ' . $end_date unless $ret;
+
+    my $txns = RT::Transactions->new($session{CurrentUser});
+    $txns->Limit(
+        FIELD    => 'ObjectType',
+        VALUE    => 'RT::Ticket',
+    );
+
+    $txns->Limit(
+        FIELD    => 'Creator',
+        VALUE    => $user->id,
+    );
+
+    $txns->Limit(
+        FIELD    => 'TimeTaken',
+        VALUE    => 0,
+        OPERATOR => '!=',
+    );
+
+    $txns->Limit(
+        FIELD    => 'Created',
+        VALUE    => $start->ISO(Timezone => 'user'),
+        OPERATOR => '>=',
+    );
+
+    $txns->Limit(
+        FIELD    => 'Created',
+        VALUE    => $end->ISO(Timezone => 'user'),
+        OPERATOR => '<=',
+        ENTRYAGGREGATOR => 'AND',
+    );
+
+    my %data;
+    my $total_time_worked = 0;
+    while ( my $txn = $txns->Next ) {
+        my $ticket = $txn->TicketObj;
+        $total_time_worked = $total_time_worked + $txn->TimeTaken;
+
+        my $day_worked = $txn->CreatedObj->RFC2822( Time => 0, Timezone => 'user' );
+        my $time_hours = $txn->TimeTaken / 60;
+
+        # We only want two decimal places
+        $time_hours = int($time_hours * (10**2)) / 10**2;
+
+        push @{$data{$day_worked}}, {DayWorked => $day_worked, Id => $ticket->Id, Subject => $ticket->Subject, Queue => $ticket->QueueObj->Name,
+            Status => $ticket->Status, Owner => $ticket->OwnerObj->Name, Time => $time_hours > 1 ? $time_hours . ' hours (' . $txn->TimeTaken . '  minutes)' : $txn->TimeTaken . ' (minutes)',
+                TimeMin => $txn->TimeTaken};
+    }
+
+    $r->content_type('application/json; charset=utf-8');
+    $m->print(JSON({ TotalTime => $total_time_worked, Data => \%data, Results => \@results }));
+}
+
+$m->abort();
+</%INIT>
+
+<%ARGS>
+$user_req   =>   undef
+$start_date =>   undef
+$end_date   =>   undef
+</%ARGS>
diff --git a/share/html/Tools/UserTime.html b/share/html/Tools/UserTime.html
new file mode 100644
index 000000000..de3520b7e
--- /dev/null
+++ b/share/html/Tools/UserTime.html
@@ -0,0 +1,78 @@
+%# 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("User Time Worked") &>
+<& /Elements/Tabs &>
+
+<div class="results" id="user-time-worked-results-container" style="display: none">
+    <&| /Widgets/TitleBox, title => loc('Results') &>
+    <ul class="action-results" id="user-time-worked-results">
+    </ul>
+    </&>
+</div>
+
+<div class="user-timeworked-form-content">
+    <form onsubmit="event.preventDefault(); LoadUserTimeWorked()">
+        <div class="add-user">
+            <input class="user-time-worked-input" id="user-time-user-selected"name="AddUserRoleMember"
+            data-autocomplete="Users"
+            data-autocomplete-return="Name"
+            placeholder="<% loc("Find a user...") %>"">
+            <label for="user-time-user-selected">User</label>
+        </div>
+        <div>
+            <input placeholder="Start Date" id="user-time-startdate" <& /Elements/SelectDate, ShowTime => 0, Name => 'Date' &>
+            <label for="user-time-startdate">Start Date</label>
+        </div>
+        <div>
+            <input placeholder="End Date" id="user-time-enddate" <& /Elements/SelectDate, ShowTime => 0, Name => 'Date' &>
+            <label for="user-time-enddate">End Date</label>
+        </div>
+        <button id="user-time-startdate" type="submit">See Time</button>
+    </form>
+</div>
+<div id='user-time-worked-content'></div>
diff --git a/share/static/css/base/forms.css b/share/static/css/base/forms.css
index 2584ee02b..f36469aef 100644
--- a/share/static/css/base/forms.css
+++ b/share/static/css/base/forms.css
@@ -280,3 +280,7 @@ ul.selectable a {
     font-weight: bold;
     text-decoration: underline;
 }
+
+.user-time-worked-input {
+    width: 17em;
+}
\ No newline at end of file
diff --git a/share/static/js/user-time.js b/share/static/js/user-time.js
new file mode 100644
index 000000000..75089b36a
--- /dev/null
+++ b/share/static/js/user-time.js
@@ -0,0 +1,67 @@
+function LoadUserTimeWorked () {
+    jQuery('#user-time-worked-content').empty();
+    var start_date = jQuery('#user-time-startdate').val();
+    var end_date = jQuery('#user-time-enddate').val();
+    var user_req = jQuery('#user-time-user-selected').val();
+
+    var data = {'user_req': user_req, 'start_date' : start_date, 'end_date' : end_date};
+
+    jQuery.ajax({
+        type: 'POST',
+        url: "/Helpers/UserTime",
+        dataType: "json",
+        data: data,
+        success: function( ARGS ) {
+            var data = ARGS['Data'];
+            var keysArr = Object.keys(data).sort();
+            var Content = jQuery('#user-time-worked-content');
+            var Results = jQuery('#user-time-worked-results');
+            var Results_container = jQuery('#user-time-worked-results-container');
+
+            Results.html("");
+            for (var i=0; i < ARGS['Results'].length; i++) {
+                Results.append('<li>' + ARGS['Results'][i] + '</li>');
+            }
+
+            if ( ARGS['Results'].length > 0 ) {
+                Results_container.show();
+            }
+
+            keysArr.forEach(function(key){
+                var days = data[key];
+                var daysTime = 0;
+
+                Content.append('<h3>' + key + '</h3>');
+                var table = ('<div id="user-time-worked-content">\
+                        <table id="user-timeworked-table" class="ticket-list collection-as-table">\
+                            <tr class="collection-as-table">\
+                                <th class="collection-as-table">id</th>\
+                                <th class="collection-as-table">Subject</th>\
+                                <th class="collection-as-table">Queue</th>\
+                                <th class="collection-as-table">Status</th>\
+                                <th class="collection-as-table">Owner</th>\
+                                <th class="collection-as-table">Time Worked</th>\
+                            </tr>\
+                ');
+
+                // Iterate over each day we have tickets for
+                for (var i=0; i < days.length; i++) {
+                    var args = days[i];
+                    var evenOdd = (i % 2 == 0 ) ? 'even' : 'odd';
+                    var ticketLink = '<a href=/Ticket/Display.html?id=' + args['Id'] + '>';
+
+                    table = table.concat('<tr class="' + evenOdd + 'line"><td>' + ticketLink + args['Id'] + '</a></td><td>'
+                        + ticketLink + args['Subject'] + '</a></td><td>' + args['Queue']
+                        + '</td><td>' + args['Status'] + '</td><td>' + args['Owner'] + '</td><td>' + args['Time'] + '</td></tr>');
+
+                        daysTime = daysTime + parseInt(args['TimeMin']);
+                }
+                daysTime = daysTime > 60 ? (daysTime / 60).toFixed(2) + ' hours (' + parseInt(daysTime, 10) + ' minutes)' : parseInt(daysTime, 10) + ' minutes';
+                Content.append(table);
+                Content.append('<span class="label">Time for ' + key + ':</span><span class="value">' + daysTime + '</span>');
+            });
+            var TotalTime =  ARGS['TotalTime'] / 60 > 1 ? (ARGS['TotalTime'] / 60).toFixed(2) + ' hours (' + ARGS['TotalTime'] + ' minutes)' : ARGS['TotalTime'] + ' minutes';
+            Content.append('<hr><span class="label">Total Time Worked: </span><span class="value">' + TotalTime + '</span>');
+        },
+    });
+}

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


More information about the rt-commit mailing list