[Rt-commit] rt branch, 4.4/user-time-worked, created. rt-4.4.4-535-gb16a292839
Craig Kaiser
craig at bestpractical.com
Mon Jun 14 15:10:02 EDT 2021
The branch, 4.4/user-time-worked has been created
at b16a2928394574b4eb3835127cd6deab88ad9a03 (commit)
- Log -----------------------------------------------------------------
commit 76b00a551095e79d6733571cbb1bbb504f5c2df5
Author: sunnavy <sunnavy at bestpractical.com>
Date: Fri Jun 4 01:35:11 2021 +0800
Pass term and args to ModifyUsersLimit so it can behave differently based on it
diff --git a/share/html/Helpers/Autocomplete/Users b/share/html/Helpers/Autocomplete/Users
index a41c194ef7..cb2c008340 100644
--- a/share/html/Helpers/Autocomplete/Users
+++ b/share/html/Helpers/Autocomplete/Users
@@ -107,7 +107,7 @@ $users->SimpleSearch( Privileged => $privileged,
# the returned field using that operator
$op ? ( Fields => { $return => $op } ) : (),
);
-$m->callback( CallbackName => "ModifyUsersLimit", Users => $users );
+$m->callback( CallbackName => "ModifyUsersLimit", Users => $users, Term => $term, ARGSRef => \%ARGS );
my @suggestions;
while ( my $user = $users->Next ) {
commit d2347bbbb9bbe850ff082d44905bbbf091662803
Author: sunnavy <sunnavy at bestpractical.com>
Date: Thu Jun 3 02:51:28 2021 +0800
Add ModifyGroupsLimit callback for Groups autocomplete
This is mirrored from Users autocomplete
diff --git a/share/html/Helpers/Autocomplete/Groups b/share/html/Helpers/Autocomplete/Groups
index e991aa5fd9..24b5bdf1ef 100644
--- a/share/html/Helpers/Autocomplete/Groups
+++ b/share/html/Helpers/Autocomplete/Groups
@@ -83,6 +83,7 @@ $groups->Limit(
foreach (split /\s*,\s*/, $exclude) {
$groups->Limit(FIELD => 'id', VALUE => $_, OPERATOR => '!=', ENTRYAGGREGATOR => 'AND');
}
+$m->callback( CallbackName => "ModifyGroupsLimit", Groups => $groups, Term => $term, ARGSRef => \%ARGS );
my @suggestions;
commit 8419c6807cba987948faea4419e691c18ae27ca2
Author: sunnavy <sunnavy at bestpractical.com>
Date: Thu Jun 3 02:54:43 2021 +0800
Sync callbacks of Users/Groups autcomplete to Principals
Principals autocomplete is a combination of Users/Groups autcomplete.
As we switched from Users to Principals for role inputs, respecting
existing callbacks helps back compatibility as customized code relying
on them won't need migration.
Here we respect not-confliting ones, including "ModifyUsersLimit",
"ModifyGroupsLimit" and "ModifySuggestion".
The "Initial" callback is not 100% compatible with the one in Users as
it doesn't have "$return" argument, and since it touches request
arguments which affects both users and groups, calling "Initial"
callback in Users here is not quite appropriate.
diff --git a/share/html/Helpers/Autocomplete/Principals b/share/html/Helpers/Autocomplete/Principals
index c018d7ce8c..b66e98b67d 100644
--- a/share/html/Helpers/Autocomplete/Principals
+++ b/share/html/Helpers/Autocomplete/Principals
@@ -58,6 +58,12 @@ $include_nobody => 0
$include_system => 0
</%ARGS>
<%INIT>
+$m->callback(
+ CallbackName => 'Initial',
+ TermRef => \$term,
+ DelimRef => \$delim,
+ ExcludeRef => \$exclude,
+);
$m->abort unless defined $term
and length $term;
@@ -95,6 +101,8 @@ $users->SimpleSearch( Privileged => $privileged,
Max => $max,
Exclude => \@exclude,
);
+$m->callback( CallbackName => "ModifyUsersLimit", Users => $users, Term => $term, ARGSRef => \%ARGS, CallbackPage => '/Helpers/Autocomplete/Users' );
+$m->callback( CallbackName => "ModifyUsersLimit", Users => $users, Term => $term, ARGSRef => \%ARGS );
(my $group_term = $term) =~ s/^\s*group\s*:\s*//i;
my $groups = RT::Groups->new( $CurrentUser );
@@ -111,11 +119,19 @@ $groups->Limit(
foreach (@exclude) {
$groups->Limit(FIELD => 'id', VALUE => $_, OPERATOR => '!=', ENTRYAGGREGATOR => 'AND');
}
+$m->callback( CallbackName => "ModifyGroupsLimit", Groups => $groups, Term => $term, ARGSRef => \%ARGS, CallbackPage => '/Helpers/Autocomplete/Groups' );
+$m->callback( CallbackName => "ModifyGroupsLimit", Groups => $groups, Term => $term, ARGSRef => \%ARGS );
my @suggestions;
while ( my $user = $users->Next ) {
my $suggestion = { id => $user->id, label => $user->Format, value => $user->EmailAddress || $user->Name };
+ $m->callback(
+ CallbackName => "ModifySuggestion",
+ suggestion => $suggestion,
+ user => $user,
+ CallbackPage => '/Helpers/Autocomplete/Users',
+ );
$m->callback( CallbackName => "ModifySuggestion", suggestion => $suggestion, user => $user );
push @suggestions, $suggestion;
}
@@ -128,6 +144,12 @@ while ( my $group = $groups->Next ) {
my $value = $delim && $label =~ $delim ? 'group:' . $group->id : $label;
my $suggestion = { id => $group->id, label => $label, value => $value };
+ $m->callback(
+ CallbackName => "ModifySuggestion",
+ suggestion => $suggestion,
+ group => $group,
+ CallbackPage => '/Helpers/Autocomplete/Groups',
+ );
$m->callback( CallbackName => "ModifySuggestion", suggestion => $suggestion, group => $group );
push @suggestions, $suggestion;
commit f26efe387ab30302e8a3292030031d51b366ab49
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Jul 27 12:18:16 2018 -0400
Create a standard RT Time Worked report
diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index 4b1080fd72..af227c3fab 100644
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -649,6 +649,11 @@ my $build_main_nav = sub {
path => '/Reports/ResolvedByDates.html',
description => loc('Examine tickets resolved in a queue between two dates'),
);
+ $reports->child( user_time =>
+ title => loc('User time worked'),
+ description => loc('User time worked'),
+ path => '/Reports/TimeWorkedReport.html',
+ );
$reports->child( createdindaterange =>
title => loc('Created in a date range'),
path => '/Reports/CreatedByDates.html',
diff --git a/share/html/Reports/TimeWorkedReport.html b/share/html/Reports/TimeWorkedReport.html
new file mode 100644
index 0000000000..b1fc244c78
--- /dev/null
+++ b/share/html/Reports/TimeWorkedReport.html
@@ -0,0 +1,245 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2018 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 &>
+<& /Elements/ListActions, actions => \@results &>
+
+<div class="user-timeworked-form-content">
+ <form method="POST">
+ <table>
+ <tr>
+ <td align="right">
+ <label>User:</label>
+ </td>
+ <td align="right">
+ <input style="width: 17em" class="user-time-worked-input" name="user_req"
+ data-autocomplete="Users"
+ data-autocomplete-return="Name"
+ placeholder="<% loc("Find a user...") %>" Value = <% $user_req %>
+ >
+ </td>
+ </tr>
+ <tr>
+ <td align="right">
+ <label><&|/l&>Start Date</&>:</label>
+ </td>
+ <td align="right">
+ <& /Elements/SelectDate, ShowTime => 1, Name => 'startdate', Default => $startdate &>
+ </td>
+ </tr>
+ <tr>
+ <td align="right">
+ <label><&|/l&>End Date</&>:</label>
+ </td>
+ <td align="">
+ <& /Elements/SelectDate, ShowTime => 1, Name => 'enddate', Default => $enddate &>
+ </td>
+ </tr>
+ <tr>
+ <td align="right">
+ <label><&|/l&>Sort By</&>:</label>
+ </td>
+ <td>
+ <select name="SortBy">
+ <option value="Date" <% $SortBy eq 'Date' ? 'selected="selected"' : '' |n%>><&|/l&>Date time worked</&></option>
+ <option value="User" <% $SortBy eq 'User' ? 'selected="selected"' : '' |n%>><&|/l&>By User</&></option>
+ <option value="Ticket" <% $SortBy eq 'Ticket' ? 'selected="selected"' : '' |n%>><&|/l&>By Ticket</&></option>
+ <option value="Queue" <% $SortBy eq 'Queue' ? 'selected="selected"' : '' |n%>><&|/l&>By Queue</&></option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td align="right">
+ <label><&|/l&>Queue</&>:</label>
+ </td>
+ <td>
+ <& /Elements/SelectQueue, Name => 'queue', Id => 'queue', Default => $queue &>
+ </td>
+ </tr>
+ <tr>
+ <td></td>
+ <td align="right"><button type="submit"><&|/l&>See Time</&></button></td>
+ </tr>
+ </table>
+ </form>
+</div>
+
+% if ( $data ) {
+<div class="user-time-content">
+% foreach my $delimeter (sort keys %$data) {
+ <h3><% $delimeter %></h3>
+ <table class="ticket-list collection-as-table">
+ <tr class="collection-as-table">
+ <th class="collection-as-table"><&|/l&>Id</&></th>
+ <th class="collection-as-table"><&|/l&>Subject</&></th>
+ <th class="collection-as-table"><&|/l&>Queue</&></th>
+ <th class="collection-as-table"><&|/l&>Status</&></th>
+ <th class="collection-as-table"><&|/l&>Owner</&></th>
+ <th class="collection-as-table"><&|/l&>Time Worked</&></th>
+ <th class="collection-as-table"><&|/l&>Worked By</&></th>
+ </tr>
+% my $line_type = "oddline";
+% my ($total_time_mins, $total_time_hours);
+% foreach my $time (@{$data->{$delimeter} }) {
+ <tr class="<% $line_type %>">
+ <td class="collection-as-table">
+ <a href="<% RT->Config->Get('WebBaseURL')."/Ticket/Display.html?id=$time->{Id}" %>"><% $time->{Id} %></a>
+ </td>
+ <td class="collection-as-table"><% $time->{Subject} %></td>
+ <td class="collection-as-table"><% $time->{Queue} %></td>
+ <td class="collection-as-table"><% $time->{Status} %></td>
+ <td class="collection-as-table">
+ <a href="<% RT->Config->Get('WebBaseURL')."/User/Summary.html?id=$time->{OwnerId}" %>"><% $time->{Owner} %></a>
+ </td>
+ <td class="collection-as-table"><% $time->{Time} %></td>
+ <td class="collection-as-table">
+ <a href="<% RT->Config->Get('WebBaseURL')."/User/Summary.html?id=$time->{WorkerId}" %>"><% $time->{Worker} %></a>
+ </td>
+ </tr>
+% $line_type = $line_type eq "oddline" ? "evenline" : "oddline";
+% $total_time_mins += $time->{TimeMin};
+% $total_time_hours += $time->{TimeHours};
+% }
+ </table>
+ <label>Total: <% $total_time_hours %> <&|/l&>hours</&> (<% $total_time_mins %> <&|/l&>minutes</&>)</label>
+% }
+</div>
+% }
+% else {
+ <&|/l&>No tickets found</&>
+% }
+
+<%INIT>
+my @results;
+
+# if we are just getting here and the form values are empty, we are done
+if ( $startdate && $enddate ) {
+
+ #### DATES ####
+ my $start_date = RT::Date->new( $session{'CurrentUser'} );
+ my $end_date = RT::Date->new( $session{'CurrentUser'} );
+
+ # If we have a value for start date, parse it into an RT::Date object
+ if ($startdate) {
+ $start_date->Set( Format => 'unknown', Value => $startdate, Timezone => 'User' );
+
+ # And then get it back as an ISO string for display purposes, in the form field and
+ # report header
+ $startdate = $start_date->AsString( Format => 'ISO', Timezone => 'User' );
+ }
+
+ # Same treatment for end date
+ if ($enddate) {
+ $end_date->Set( Format => 'unknown', Value => $enddate );
+ $enddate = $end_date->AsString( Format => 'ISO', Timezone => 'User' );
+ }
+
+ # Get a new transactions object to hold transaction search results for this ticket
+ my $trans = RT::Transactions->new( $session{'CurrentUser'} );
+
+ my $txns = RT::Transactions->new($session{CurrentUser});
+ $txns->Limit( FIELD => 'ObjectType', VALUE => 'RT::Ticket' );
+ if ( $user_req ) {
+ my $user = RT::User->new( $session{'CurrentUser'} );
+ my ($ret, $msg) = $user->Load( $user_req );
+ if ( $ret && $user->Id ) {
+ $txns->Limit( FIELD => 'Creator', VALUE => $user->id )
+ }
+ else {
+ push @results, "Could not load user, report not limited to user: $user_req";
+ }
+ }
+ $txns->Limit( FIELD => 'TimeTaken', VALUE => 0, OPERATOR => '!=' );
+ $txns->Limit( FIELD => 'Created', VALUE => $start_date->ISO(Timezone => 'user'), OPERATOR => '>=' );
+ $txns->Limit( FIELD => 'Created', VALUE => $end_date->ISO(Timezone => 'user'), OPERATOR => '<', ENTRYAGGREGATOR => 'AND');
+
+ my $total_time_worked = 0;
+
+ while ( my $txn = $txns->Next ) {
+ my $ticket = $txn->TicketObj;
+
+ my $worker = RT::User->new($session{'CurrentUser'});
+ my ($ret, $msg) = $worker->Load( $txn->Creator );
+ push @results, $msg unless $ret;
+
+ if ( $queue && $ticket->QueueObj ) {
+ next unless $queue eq $ticket->QueueObj->Id;
+ }
+
+ $total_time_worked = $total_time_worked + $txn->TimeTaken;
+
+ my $day_worked = $txn->CreatedObj->RFC2822( Time => 0, Timezone => 'user' );
+ my $time_hours = $txn->TimeTaken / 60;
+ $time_hours = int($time_hours * 10**2) / 10**2;
+
+ my $delimeter;
+ if ( $SortBy eq 'User' ) {
+ $delimeter = 'User: ' . $worker->Name;
+ } elsif ( $SortBy eq 'Ticket' ) {
+ $delimeter = $ticket->Id . ': ' . $ticket->Subject;
+ } elsif ( $SortBy eq 'Queue' ) {
+ $delimeter = 'Queue: ' . $ticket->QueueObj->Name;
+ } else {
+ $delimeter = $day_worked;
+ }
+
+ push @{$data->{$delimeter}}, {DayWorked => $day_worked, Id => $ticket->Id, Subject => $ticket->Subject, Queue => $ticket->QueueObj->Name,
+ Status => $ticket->Status, OwnerId => $ticket->OwnerObj->Id, Owner => $ticket->OwnerObj->Name, Time => $time_hours > 1 ? $time_hours . ' hours (' . $txn->TimeTaken . ' minutes)' : $txn->TimeTaken . ' minutes',
+ TimeMin => $txn->TimeTaken, TimeHours => $time_hours, Worker => $worker->Name, WorkerId => $worker->Id};
+ }
+}
+</%INIT>
+
+<%ARGS>
+$startdate => ''
+$enddate => ''
+$user_req => undef
+$SortBy => 'Date'
+$data => undef
+$queue => undef
+</%ARGS>
commit b16a2928394574b4eb3835127cd6deab88ad9a03
Author: Craig Kaiser <craig at bestpractical.com>
Date: Fri Jul 27 12:19:33 2018 -0400
Add docs for User Time Worked Report
diff --git a/docs/images/user-time-worked-report.png b/docs/images/user-time-worked-report.png
new file mode 100644
index 0000000000..4200110ba8
Binary files /dev/null and b/docs/images/user-time-worked-report.png differ
diff --git a/docs/reporting/user_time_worked.pod b/docs/reporting/user_time_worked.pod
new file mode 100644
index 0000000000..1c41723ac2
--- /dev/null
+++ b/docs/reporting/user_time_worked.pod
@@ -0,0 +1,17 @@
+=head1 Introduction
+
+The User Time Worked report allows for users to see time worked on tickets
+for an open ended date range. Users can limit the data returned by either
+User or Queue value. The data by default is organized by the date that the
+time was worked, but can also be sorted by User, Queue or Ticket.
+
+=cut
+
+=head2 Example
+
+=cut
+
+=for html <img alt="Example User Time Worked Report"
+src="images/user-time-worked-report.png">
+
+=cut
-----------------------------------------------------------------------
More information about the rt-commit
mailing list