[Rt-commit] rt branch, 4.6/custom-date-ranges, created. rt-4.4.1-104-gfd018fe

Shawn Moore shawn at bestpractical.com
Thu Dec 1 16:56:43 EST 2016


The branch, 4.6/custom-date-ranges has been created
        at  fd018fe2e5387720cf5364091409984c1cda9229 (commit)

- Log -----------------------------------------------------------------
commit 0bb4ac7382ab69ce90fc4675d8e68257811b72cb
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Tue Jun 28 23:24:10 2016 +0000

    Add RT::Ticket CustomDateRanges to query builder

diff --git a/share/html/Elements/RT__Ticket/ColumnMap b/share/html/Elements/RT__Ticket/ColumnMap
index c7303d9..9dfcbc8 100644
--- a/share/html/Elements/RT__Ticket/ColumnMap
+++ b/share/html/Elements/RT__Ticket/ColumnMap
@@ -326,6 +326,18 @@ $COLUMN_MAP = {
         },
     },
 };
+
+if (my $config = RT->Config->Get('CustomDateRanges')) {
+    my %ranges = %{ $config->{'RT::Ticket'} || {} };
+    for my $name (keys %ranges) {
+        $COLUMN_MAP->{$name} = {
+            title => $name,
+            value => sub {
+                $_[0]->CustomDateRange($name, $ranges{$name});
+            },
+        };
+    }
+}
 </%ONCE>
 <%init>
 # if no encryption support, then KeyOwnerName and KeyRequestors fall back to the regular
diff --git a/share/html/Search/Elements/BuildFormatString b/share/html/Search/Elements/BuildFormatString
index 4cbcde4..b325e8c 100644
--- a/share/html/Search/Elements/BuildFormatString
+++ b/share/html/Search/Elements/BuildFormatString
@@ -128,6 +128,11 @@ while ( my $Role = $CustomRoles->Next ) {
     push @fields, "CustomRole.{" . $Role->Name . "}";
 }
 
+if (RT->Config->Get('CustomDateRanges')) {
+    my %ranges = %{ RT->Config->Get('CustomDateRanges')->{'RT::Ticket'} || {} };
+    push @fields, sort keys %ranges;
+}
+
 $m->callback( Fields => \@fields, ARGSRef => \%ARGS );
 
 my ( @seen);

commit 4f200c560fcf54435363c678a1e0a4a86fc49925
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Jun 30 21:39:10 2016 -0400

    Add Asset ColumnMap entries for CustomDateRanges

diff --git a/share/html/Elements/RT__Asset/ColumnMap b/share/html/Elements/RT__Asset/ColumnMap
index 618501d..e73ffe9 100644
--- a/share/html/Elements/RT__Asset/ColumnMap
+++ b/share/html/Elements/RT__Asset/ColumnMap
@@ -112,6 +112,18 @@ my $COLUMN_MAP = {
         }
     },
 };
+
+if (my $config = RT->Config->Get('CustomDateRanges')) {
+    my %ranges = %{ $config->{'RT::Asset'} || {} };
+    for my $name (keys %ranges) {
+        $COLUMN_MAP->{$name} = {
+            title => $name,
+            value => sub {
+                $_[0]->CustomDateRange($name, $ranges{$name});
+            },
+        };
+    }
+}
 </%ONCE>
 <%init>
 $m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'Once', CallbackOnce => 1 );

commit 73e39a56713468944406dc069cc1e49093eaf80a
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed Jun 29 00:09:10 2016 +0000

    Add config validation for CustomDateRanges

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index dfb3a61..0dd163b 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -1010,6 +1010,37 @@ our %META;
             $config->Set( CustomFieldGroupings => %$groups );
         },
     },
+    CustomDateRanges => {
+        Type            => 'HASH',
+        PostLoadCheck   => sub {
+            my $config = shift;
+            # use scalar context intentionally to avoid not a hash error
+            my $ranges = $config->Get('CustomDateRanges') || {};
+
+            unless (ref($ranges) eq 'HASH') {
+                RT->Logger->error("Config option \%CustomDateRanges is a @{[ref $ranges]} not a HASH");
+                return;
+            }
+
+            for my $class (keys %$ranges) {
+                if (ref($ranges->{$class}) eq 'HASH') {
+                    for my $name (keys %{ $ranges->{$class} }) {
+                        my $spec = $ranges->{$class}{$name};
+                        if (!ref($spec) || ref($spec) eq 'HASH') {
+                            # this will produce error messages if parsing fails
+                            $class->require;
+                            $class->_ParseCustomDateRangeSpec($name, $spec);
+                        }
+                        else {
+                            RT->Logger->error("Config option \%CustomDateRanges{$class}{$name} is not a string or HASH");
+                        }
+                    }
+                } else {
+                    RT->Logger->error("Config option \%CustomDateRanges{$class} is not a HASH");
+                }
+            }
+        },
+    },
     ExternalStorage => {
         Type            => 'HASH',
         PostLoadCheck   => sub {

commit f6a01b4e100a1b512eb6709285800323c767be6a
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed Jun 29 00:09:26 2016 +0000

    RT_Config doc for CustomDateRanges

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 705c6c9..4d46bbb 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -972,6 +972,81 @@ For C<RT::User>: C<Identity>, C<Access control>, C<Location>, C<Phones>
 Extensions may also add their own built-in groupings, refer to the individual
 extension documentation for those.
 
+=item C<%CustomDateRanges>
+
+This option lets you declare additional date ranges to be calculated
+and displayed in search results. Durations between any two core fields,
+as well as custom fields, are supported. Each custom date range is
+added as an additional display column in the search builder.
+
+Set C<%CustomDateRanges> to a nested structure similar to the following:
+
+    Set(%CustomDateRanges,
+        'RT::Ticket' => {
+            'Resolution Time' => 'Resolved - Created',
+
+            'Downtime' => 'CF.{First Alert} - CF.Recovered',
+
+            'Time To Beta' => {
+                value => 'CF.Beta - now',
+
+                format => sub {
+                    my ($duration, $beta, $now, $ticket) = @_;
+                    my $days = int($duration / (24*60*60));
+                    if ($days < 0) {
+                        $ticket->loc('[quant,_1,day,days] ago', -$days);
+                    }
+                    else {
+                        $ticket->loc('in [quant,_1,day,days]', $days);
+                    }
+                },
+            },
+        },
+    );
+
+The first level keys are record types. Each record type's value must be a
+hash reference. Each pair in the second-level hash defines a new range. The
+key is the range's name (which is displayed to users in the UI), and its
+value describes the range and must be either a string or a hashref.
+
+Values that are plain strings simply describe the calculation to be made.
+
+Values that are hashrefs must include the key C<value> which must a string
+that describes the calculation to be made. This hashref may also include the
+key C<format>, which is a code reference that allows customization of how
+the duration is displayed to users. This code reference receives four
+parameters: the duration (a number of seconds), the end time (an L<RT::Date>
+object), the start time (another L<RT::Date>), and the record itself (which
+corresponds to the first-level key; in the example config above, it would be
+the L<RT::Ticket> object). The code reference should return the string to
+be displayed to the user.
+
+The calculation is expected to be of the format C<"field - field"> where each
+field may be:
+
+=over 4
+
+=item * a core field
+
+For example, L<RT::Ticket> supports: created, starts, started, last updated,
+told or last contact, due, resolved.
+
+=item * a custom field
+
+You may use either C<CF.Name> or C<CF.{Longer Name}> syntax.
+
+=item * the word C<now>
+
+=back
+
+Custom date range calculations are defined using typical math operators with
+a space before and after. Subtraction (-) is currently supported.
+
+If either field is unset, nothing will be displayed for that record (and the
+C<format> code reference will not be called). If you need additional control
+over how results are calculated, see
+L<docs/customizing/search_result_columns.pod>.
+
 =item C<$CanonicalizeRedirectURLs>
 
 Set C<$CanonicalizeRedirectURLs> to 1 to use C<$WebURL> when

commit c3189cdb63025d878896deef5bd96c880b26aecf
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed Jun 29 12:31:43 2016 -0400

    Switch customization doc from date range to message count
    
        Date ranges are now more easily accomplished with the new
        %CustomDateRanges config.

diff --git a/docs/customizing/search_result_columns.pod b/docs/customizing/search_result_columns.pod
index 7eef416..5b9231e 100644
--- a/docs/customizing/search_result_columns.pod
+++ b/docs/customizing/search_result_columns.pod
@@ -12,10 +12,12 @@ them you can add and remove data elements to sort by, change the sort order,
 and add and remove which columns you want to see.
 
 Although the Add Columns box has an extensive list of available columns, there
-are times when you need a value not listed. Sometimes what you want is a
-value calculated based on existing ticket values, like finding the difference
-between two date fields. RT provides a way to add this sort of customization
-using something called a Column Map.
+are times when you need a value not listed. If you want to display a custom
+date range, you can configure the L<RT_Config/%CustomDateRanges> setting.
+
+Sometimes what you want is a novel value calculated based on other ticket
+information, like the number of messages on a ticket. RT provides a way
+to add this sort of customization using something called a Column Map.
 
 =head2 Level of Difficulty
 
@@ -43,11 +45,8 @@ making upgrades much easier. As an example, we'll add a Column Map to the
 ticket display and explain the necessary callbacks. You can read more about
 callbacks in general in the L<writing_extensions/Callbacks> documentation.
 
-For our example, let's assume we want to display a response time column that
-shows the difference between when a ticket is created and when someone
-starts working on it (started date). The two initial values are already
-available on the ticket, but it would be convenient to display the
-calculated value in our search.
+For our example, let's assume we want to display the number of messages
+(comments, correspondences) on a ticket.
 
 =head2 Column Map Callback
 
@@ -76,12 +75,15 @@ where F<Once> is the name of the file where we'll put our code.
 In the F<Once> file, we'll put the following code:
 
     <%init>
-    $COLUMN_MAP->{'TimeToFirstResponse'} = {
-            title     => 'First Response', # loc
-            attribute => 'First Response',
+    $COLUMN_MAP->{'NumberOfMessages'} = {
+            title     => 'Messages', # loc
+            attribute => 'Messages',
             value     => sub {
                 my $ticket = shift;
-                return $ticket->StartedObj->DiffAsString($ticket->CreatedObj);
+                my $txns = $ticket->Transactions;
+                $txns->Limit( FIELD => 'Type', VALUE => 'Comment' );
+                $txns->Limit( FIELD => 'Type', VALUE => 'Correspond' );
+                return $txns->Count;
             }
     };
     </%init>
@@ -139,9 +141,9 @@ each row in the search results, the ticket object for that ticket is made
 available as the first parameter to our subroutine.
 
 This allows us to then call methods on the L<RT::Ticket> object to access
-and process the value. In our case, we can get the L<RT::Date> objects for
-the two dates and use the L<RT::Date/DiffAsString> method to calculate and
-return the difference.
+and process the value. In our case, we can get the L<RT::Transactions>
+collection, limit it to the types we're interested in, and then use the
+C<Count> method to return the number of messages.
 
 When writing code to calculate values, remember that it will be run for each
 row in search results. You should avoid doing things that are too time
@@ -164,7 +166,7 @@ Create the file:
 And put the following code in the F<Default> file:
 
     <%INIT>
-    push @{$Fields}, 'TimeToFirstResponse';
+    push @{$Fields}, 'NumberOfMessages';
     </%INIT>
     <%ARGS>
     $Fields => undef

commit 8326b0ac85866c6235c21a5f173824ce64ff3c4e
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed Jun 29 12:32:53 2016 -0400

    Remove "# loc" and description from example
    
        #loc comments like this are used throughout core RT when we want
        to flag the string for localization, but we cannot localize the string
        directly there. We use #loc to tell the localization generator to
        include the commented string in .po files for translation. But the
        actual call to loc() happens later.
    
        For example, we can't localize titles like "New messages" (for
        RT::Ticket's UpdateStatus) in column map because the column map is
        defined only once for performance reasons, so localizing it there
        could serve, at most, one language. We instead run the title through
        loc() at render time in CollectionAsTable/Header. However, by then,
        the original hardcoded string "New messages" has been replaced with
        the variable $title. So without the #loc comment, there would be
        nothing to indicate to the localization generator to put the string
        "New messages" into .po files for translation.
    
        This is a tool for RT's developers rather than for specific
        installations.

diff --git a/docs/customizing/search_result_columns.pod b/docs/customizing/search_result_columns.pod
index 5b9231e..045e180 100644
--- a/docs/customizing/search_result_columns.pod
+++ b/docs/customizing/search_result_columns.pod
@@ -76,7 +76,7 @@ In the F<Once> file, we'll put the following code:
 
     <%init>
     $COLUMN_MAP->{'NumberOfMessages'} = {
-            title     => 'Messages', # loc
+            title     => 'Messages',
             attribute => 'Messages',
             value     => sub {
                 my $ticket = shift;
@@ -110,8 +110,6 @@ The parameters in the hashref are as follows:
 =item title
 
 The title is what will be used in the header row to identify this value.
-The C<# loc> is some special markup that allows RT to replace the value
-with translations in other languages, if they are available.
 
 =item attribute
 

commit fd018fe2e5387720cf5364091409984c1cda9229
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed Jun 29 19:03:41 2016 +0000

    Add tests for CustomDateRanges

diff --git a/t/api/custom-date-ranges.t b/t/api/custom-date-ranges.t
new file mode 100644
index 0000000..3ced5ae
--- /dev/null
+++ b/t/api/custom-date-ranges.t
@@ -0,0 +1,56 @@
+use warnings;
+use strict;
+use Test::MockTime qw( :all );
+use RT::Test;
+
+set_fixed_time('2016-01-01T00:00:00Z');
+
+my $cf = RT::Test->load_or_create_custom_field(
+    Name => 'Beta Date',
+    Type => 'DateTime',
+    MaxValues => 1,
+    LookupType => RT::Ticket->CustomFieldLookupType,
+    Queue => 'General',
+);
+ok($cf && $cf->Id, 'created Beta Date CF');
+
+my $t = RT::Test->create_ticket(
+    Queue       => 'General',
+    Status      => 'resolved',
+    Created     => '2015-12-10 00:00:00',
+    Starts      => '2015-12-13 00:00:00',
+    Started     => '2015-12-12 12:00:00',
+    Due         => '2015-12-20 00:00:00',
+    Resolved    => '2015-12-15 18:00:00',
+);
+
+# see t/customfields/datetime.t for timezone issues
+$t->AddCustomFieldValue(Field => 'Beta Date', Value => '2015-12-13 19:00:00');
+is($t->FirstCustomFieldValue('Beta Date'), '2015-12-14 00:00:00');
+
+my @tests = (
+    'Starts - Created' => '3 days',
+    'Created   -     Starts' => '3 days prior',
+    'Started - Created' => '3 days', # uses only the most significant unit
+    'Resolved - Due' => '4 days prior',
+    'Due - Resolved' => '4 days',
+    'Due - Told' => undef, # told is unset
+    'now - LastContact' => undef, # told is unset
+    'now - Last Updated' => '0 seconds',
+    'Due - CF.{Beta Date}' => '6 days',
+    'now - CF.{Beta Date}' => '3 weeks',
+    'CF.{Beta Date} - now' => '3 weeks prior',
+);
+
+while (my ($spec, $expected) = splice @tests, 0, 2) {
+    is($t->CustomDateRange(test => $spec), $expected, $spec);
+}
+
+is($t->CustomDateRange(test => {
+    value => 'Resolved - Created',
+    format => sub {
+        my ($seconds, $end, $start, $ticket) = @_;
+        join '/', $seconds, $end->Unix, $start->Unix, $ticket->Id;
+    },
+}), '496800/1450202400/1449705600/1', 'format');
+

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


More information about the rt-commit mailing list