[Rt-commit] rt branch, 4.6/priority-as-string, created. rt-4.4.4-567-gb420bf72eb

Michel Rodriguez michel at bestpractical.com
Tue Dec 31 11:52:35 EST 2019


The branch, 4.6/priority-as-string has been created
        at  b420bf72ebbc4ed778f207bcf7f39f2749975c68 (commit)

- Log -----------------------------------------------------------------
commit e698a7d3021e086620defbe4ac04d5b5c3e20463
Author: michel <michel at bestpractical.com>
Date:   Wed Dec 18 16:02:28 2019 +0100

    Cores PriorityAsString
    
    Add PriorityAsString to core.
    
    Priority is kept as a number in the DB, but is displayed
    as a string, based on the mapping defined in the config.
    
    Everywhere a priority needs to be displayed or input, the
    Ticket object can return the string, based on the queue.
    
    Example configuration:
    
        Set($EnablePriorityAsString, 1);
    
        Set(%PriorityAsString,
        # default (for non specified queues)
        Default => { Low => 0, Medium => 50, High => 100 },
        # per queue priorities
        General    => { Low => 0, High => 1 },
        # drop-downs will display in order Low/High/Medium
        LHM        => [ Low => 0, High => 100, Medium => 50 ],
        # use numerical priorities
        NumQueue   => 0,
      );

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index b76df54f9b..a38d4265f7 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1877,6 +1877,91 @@ Set this to 1 to hide unset fields.
 
 Set($HideUnsetFieldsOnDisplay, 0);
 
+=item C<%EnablePriorityAsString>
+
+Set this to C<0> to disable string priorities and only use
+numerical ones
+
+=cut
+
+Set($EnablePriorityAsString, 1);
+
+=item C<%PriorityAsString>
+
+RT can assign a priority value to tickets. Priority is stored as an
+integer in the database, but RT supports mapping strings to numerical
+values.
+
+RT supports different priority string mappings for individual queues
+with C<%PriorityAsString>.
+
+For example, the default mapping is:
+
+    Set(%PriorityAsString,
+      Default => { Low => 0, Medium => 50, High => 100 },
+    )
+
+Use queue names as keys in this hash.
+
+Values in the hash define the mapping:
+
+=over 4
+
+=item
+
+C<0> sets the queue to use numerical priorities:
+
+    NumQueue => 0
+
+=item
+
+a hash C<< { string => numerical_value,... } >> maps each string to
+the numerical value:
+
+    QueueWith3Levels => { Low => 0, Medium => 50, High => 100 }
+
+=item
+
+an array C<< [ string => numerical_value, ... ] >> maps each string to
+the numerical value, and additionaly tells RT to display strings in the
+order they were given in drop-down menus:
+
+      Ordered => [ Low => 0, High => 100, Medium => 50 ]
+
+In this case the drop-down menus used to choose the priority in RT will
+display Low/High/Medium, in this order
+
+=back
+
+Tickets in queues that aren't defined in this hash will use the mapping
+defined for C<Default>.
+
+A complete example of per-queue priority strings:
+
+    Set(%PriorityAsString,
+      # all queues not listed here will use this mapping
+      Default    => { Low => 0, Medium => 50, High => 100 },
+      # These queues use strings,
+      # drop-downs show labels ordered by ascending priority value
+      General    => { Low => 0, High => 1 },
+      ColorQueue => { Green => 0, Yellow => 50, Red => 100 },
+      # drop-downs will display in order Low/High/Medium
+      LHM        => [ Low => 0, High => 100, Medium => 50 ],
+      # use numerical priorities
+      NumQueue   => 0,
+    );
+
+Redefine C<%PriorityAsString> in C<RT_SiteConfig.pm> to set site
+priority strings.
+
+The default is 3 levels of priority: Low, Medium and High
+
+=cut
+
+    Set(%PriorityAsString,
+      Default => { Low => 0, Medium => 50, High => 100 },
+    );
+
 =back
 
 =head2 Group Summary Configuration
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index ab42501fdf..24aae41a57 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -560,6 +560,14 @@ our %META;
         }
     },
 
+    EnablePriorityAsString => {
+        Widget => '/Widgets/Form/Boolean',
+    },
+    PriorityAsString => {
+        Widget => '/Widgets/Form/String',
+    },
+
+
     # User overridable locale options
     DateTimeFormat => {
         Section         => 'Locale',                       #loc
@@ -1859,6 +1867,114 @@ sub EnableExternalAuth {
     return;
 }
 
+=head2 Methods for specific options
+
+=head2 PriorityMap( <optional_queue_name> )
+
+Returns a hash ref C<< { <priority_as_string> => <priority_value>, ... } >>
+
+If no queue name is given uses the configuration for C<'Default'>.
+
+If the queue has no C<PriorityAsString> option, uses the one for  C<'Default'>.
+
+Returns C<undef> if C<EnablePriorityAsString> is set to 0, if the
+queue has C<PriorityAsString> switched off (by setting it to C<0>) or if no
+map is available.
+
+Logs an error if a PriorityAsString is found but it is neither a hash nor an
+array, nor 0.
+
+=cut
+
+sub PriorityMap
+  { my $self  = shift;
+    my $queue = shift || 'Default';
+
+    my $option = $self->PriorityAsStringForQueue( $queue );
+    return undef if ! $option;
+
+    my $map;
+    if ( ref $option eq 'HASH' ) {
+        # regular map
+        $map = $option
+    } elsif ( ref $option eq 'ARRAY' ) {
+        # order is irrelevent here, load the array as a hash
+        $map = { @$option };
+    } else {
+        # try to diagnose the problem by showing some details about the map
+        # note: this could also be done when the config is loaded
+
+        # where was the map defined
+        my $queues= $self->Get('PriorityAsString');
+        my $real_queue = defined $queues->{$queue}  ? "queue $queue"
+                       : defined $queues->{Default} ? 'Default queue'
+                       :                              'cannot happen';
+
+        # what's in the map definition
+        my $option_desc = ref $option         ? "map is a " . ref( $option )
+                        : length $option < 20 ? $option
+                        :                     substr( $option, 0, 17 ) . '...';
+
+        $RT::Logger->error( "Wrong priority map for $real_queue: $option_desc" );
+        return undef;
+    }
+    return $map;
+  }
+
+=head2 PriorityMapOrder( <optional_queue_name> )
+
+Returns an array of priority strings, in the proper order for display.
+
+The order is either given in the C<PriorityAsString> option by using an
+array for the queue, or is based on the numerical values of the options
+(lower values first).
+
+=cut
+
+sub PriorityMapOrder
+  { my $self  = shift;
+    my $queue = shift || 'Default';
+
+    my $option = $self->PriorityAsStringForQueue( $queue );
+    return undef if ! $option;
+
+    my @ordered;
+    if ( ref $option eq 'HASH' ) {
+        # hash: sort it on the value
+        @ordered = sort { $option->{$a} <=> $option->{$b} } keys %$option
+    } elsif ( ref $option eq 'ARRAY' ) {
+        # array: use the array order, extract every other element
+        foreach( my $i=0; $i<@$option; $i+=2) {
+            push @ordered, $option->[$i];
+        }
+    } else {
+        return undef;
+    }
+
+    return \@ordered;
+
+  }
+
+# internal method, PriorityAsStringForQueue( <queue name> )
+# returns a hashref { <label> => <value>, ... } or undef if no
+# priority mapping was found
+
+sub PriorityAsStringForQueue
+  { my $self  = shift;
+    my $queue = shift;
+
+    return undef if ! $self->Get( 'EnablePriorityAsString' );
+
+    my $queues = $self->Get( 'PriorityAsString' );
+    return undef if ! $queues;
+
+    my $map = defined $queues->{$queue} ? $queues->{$queue}
+            : defined $queues->{Default}? $queues->{Default}
+            :                             undef;
+
+    return $map;
+  }
+
 RT::Base->_ImportOverlays();
 
 1;
diff --git a/lib/RT/Queue.pm b/lib/RT/Queue.pm
index dc286a756b..af101f20e2 100644
--- a/lib/RT/Queue.pm
+++ b/lib/RT/Queue.pm
@@ -1230,6 +1230,11 @@ sub SetDefaultValue {
         },
     );
 
+    if( $args{Name}=~ /^(Initial|Final)Priority/ ) {
+        $old_value= $self->PriorityAsString( $old_value );
+        $new_value= $self->PriorityAsString( $new_value );
+    }
+
     if ( $ret ) {
         return ( $ret, $self->loc( 'Default value of [_1] changed from [_2] to [_3]', $args{Name}, $old_value, $new_value ) );
     }
@@ -1238,6 +1243,12 @@ sub SetDefaultValue {
     }
 }
 
+sub PriorityAsString {
+    my( $self, $priority )= @_;
+    my $map = RT->Config->PriorityMap( $self->Name );
+    return RT::Ticket->_PriorityAsString( $priority, $map );
+}
+
 sub SLA {
     my $self = shift;
     my $value = shift;
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index 5c400464cd..80c3a4d19e 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -2820,10 +2820,18 @@ sub _Set {
     return ( $ret, $msg ) unless $args{'RecordTransaction'};
 
     my $trans;
+
+    my $New= $args{'Value'};
+    # *Priority fields may need to be translated
+    if( $args{'Field'} =~ m{^(Initial|Final)?Priority$} ) {
+        $Old= $self->_PriorityAsString( $Old );
+        $New= $self->_PriorityAsString( $New );
+    }
+
     ( $ret, $msg, $trans ) = $self->_NewTransaction(
         Type      => $args{'TransactionType'},
         Field     => $args{'Field'},
-        NewValue  => $args{'Value'},
+        NewValue  => $New,
         OldValue  => $Old,
         TimeTaken => $args{'TimeTaken'},
     );
@@ -3348,7 +3356,9 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 Returns the current value of InitialPriority.
 (In the database, InitialPriority is stored as int(11).)
 
+=head2 InitialPriorityAsString
 
+Returns the current mapped string value of InitialPriority.
 
 =head2 SetInitialPriority VALUE
 
@@ -3366,7 +3376,9 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 Returns the current value of FinalPriority.
 (In the database, FinalPriority is stored as int(11).)
 
+=head2 FinalPriorityAsString
 
+Returns the current mapped string value of FinalPriority.
 
 =head2 SetFinalPriority VALUE
 
@@ -3384,7 +3396,9 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 Returns the current value of Priority.
 (In the database, Priority is stored as int(11).)
 
+=head2 PriorityAsString
 
+Returns the current mapped string value of Priority.
 
 =head2 SetPriority VALUE
 
@@ -3729,6 +3743,53 @@ sub Serialize {
     return %store;
 }
 
+# Returns String: Various Ticket Priorities as either a string or integer
+sub PriorityAsString {
+    my $self = shift;
+    return $self->_PriorityAsString( $self->Priority );
+}
+
+sub InitialPriorityAsString {
+    my $self = shift;
+    return $self->_PriorityAsString( $self->InitialPriority );
+}
+
+sub FinalPriorityAsString {
+    my $self=shift;
+    return $self->_PriorityAsString( $self->FinalPriority );
+}
+
+sub _PriorityAsString {
+    my $self     = shift;
+    my $priority = shift;
+    return undef unless defined $priority && length $priority;
+
+    my $map;
+    if ( @_ ) {
+        $map = shift;
+    } else {
+        my $queue_name = $self->QueueObj->Name;
+        $map = RT->Config->PriorityMap($queue_name);
+    }
+
+    return $priority if !$map;
+
+    my @orderedLabels = sort { $map->{$b} <=> $map->{$a} } keys %$map;
+
+    # priority could be '(no value)' (or a localized version)
+    $priority = 0 if $priority !~ m{^\d+$};
+
+    # return the label for the first priority <= $priority
+    foreach my $label ( @orderedLabels ) {
+        return $label if $priority >= $map->{$label};
+    }
+
+    # if we get here the priority is lower than the lowest in the map
+    # return the label associated with the lowest priority
+    return $orderedLabels[-1];
+
+}
+
 RT::Base->_ImportOverlays();
 
 1;
diff --git a/share/html/Admin/Queues/DefaultValues.html b/share/html/Admin/Queues/DefaultValues.html
index ca8e2d3dab..35e97a5e13 100644
--- a/share/html/Admin/Queues/DefaultValues.html
+++ b/share/html/Admin/Queues/DefaultValues.html
@@ -59,7 +59,11 @@
       <&|/l&>Priority</&>:
     </div>
     <div class="col-md-9 value">
-      <& /Elements/SelectPriority, Name => "InitialPriority", Default => $queue->DefaultValue('InitialPriority') &>
+      <& /Elements/SelectPriority,
+         Name => "InitialPriority",
+         Default => $queue->DefaultValue('InitialPriority'),
+         QueueObj => $queue
+      &>
     </div>
   </div>
 
@@ -68,7 +72,11 @@
       <&|/l&>Final Priority</&>:
     </div>
     <div class="col-md-9 value">
-      <& /Elements/SelectPriority, Name => "FinalPriority", Default => $queue->DefaultValue('FinalPriority') &>
+      <& /Elements/SelectPriority,
+         Name => "FinalPriority",
+         Default => $queue->DefaultValue('FinalPriority'),
+         QueueObj => $queue
+      &>
       <span><em><&|/l&>requires running rt-crontool</&></em></span>
     </div>
   </div>
diff --git a/share/html/Elements/RT__Queue/ColumnMap b/share/html/Elements/RT__Queue/ColumnMap
index 8c902fbe74..b12b3663c4 100644
--- a/share/html/Elements/RT__Queue/ColumnMap
+++ b/share/html/Elements/RT__Queue/ColumnMap
@@ -67,7 +67,10 @@ my $COLUMN_MAP = {
     },
     Priority => {
         title     => 'Priority', # loc
-        value     => sub { return join '-', $_[0]->DefaultValue('InitialPriority') || '', $_[0]->DefaultValue('FinalPriority') || '' },
+        value     => sub { return join '/',
+                               map { $_[0]->PriorityAsString( $_[0]->DefaultValue( $_ ) || 0 ) }
+                                   qw( InitialPriority FinalPriority );
+                     },
     },
     DefaultDueIn => {
         title     => 'DefaultDueIn', # loc
diff --git a/share/html/Elements/RT__Ticket/ColumnMap b/share/html/Elements/RT__Ticket/ColumnMap
index 5f5d4b6b27..c949a6a565 100644
--- a/share/html/Elements/RT__Ticket/ColumnMap
+++ b/share/html/Elements/RT__Ticket/ColumnMap
@@ -363,6 +363,43 @@ if(RT->Config->Get('DisplayTotalTimeWorked')) {
     }
 }
 
+my $printer = sub {
+    my ( $class, $string ) = @_;
+    return '' unless defined $string && length $string;
+
+    my $request_path = $HTML::Mason::Commands::r->path_info;
+    if ( $request_path and $request_path =~ /Results\.tsv/ ) {
+        return loc( $string);
+    }
+
+    my $escaped = $m->interp->apply_escapes( $string, 'h' );
+    my $loc_escaped = $m->interp->apply_escapes( loc( $string ), 'h' );
+    return \( qq{<span class="ticket-info-$class-} . lc( $escaped ) . qq{">$loc_escaped</span>} );
+};
+
+my $queues = RT->Config->Get( 'PriorityAsString' ) || {};
+
+foreach my $field ( qw( Priority InitialPriority FinalPriority ) ) {
+    $COLUMN_MAP->{ $field .'Number' } ||= $COLUMN_MAP->{ $field };
+
+    my $class = lc( $field );
+    $class =~ s/(?=<.)(?=priority)/-/;
+
+    my $method = $field . 'AsString';
+
+    if ( not keys %$queues ) {
+        $COLUMN_MAP->{ $field }{'value'} = sub {
+            return $printer->( $class, $_[0]->$method() );
+        };
+    } else {
+        $COLUMN_MAP->{ $field }{'value'} = sub {
+            return $queues->{$_[0]->QueueObj->Name}
+                ? $printer->( $class, $_[0]->$method() )
+                : $_[0]->$field;
+        };
+    }
+}
+
 $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/Elements/SelectPriority b/share/html/Elements/SelectPriority
index 2889b4629e..2166fac1a4 100644
--- a/share/html/Elements/SelectPriority
+++ b/share/html/Elements/SelectPriority
@@ -45,11 +45,48 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
+% if ( %Map ) {
+<select class="select-priority form-control" name="<% $Name %>">
+%     unless ( defined $Default ) {
+<option value="">-</option>
+%     }
+<%PERL>
+foreach my $Label ( @Order ) {
+    my ( $Value, $Selected );
+    if ( $Label eq $DefaultLabel ) {
+        ( $Value, $Selected ) = ( $Default, 'selected="selected"' );
+    } else {
+        ( $Value, $Selected ) = ( $Map{ $Label }, '' );
+    }
+</%PERL>
+<option class="<% lc $Label %>" value="<% $Value %>" <% $Selected |n %>><% loc($Label) %></option>
+% }
+</select>
+% } else {
 <input name="<% $Name %>" type="text" value="<% $Default %>" size="5" class="form-control" />
+% }
 <%ARGS>
 $Name => 'Priority'
 $Default => ''
+$QueueObj => undef
+$PriorityStringMap => undef
 </%ARGS>
 <%INIT>
+
+my @Order;
+my $DefaultLabel = '';
+my %Map = ();
+
+
+if ( $QueueObj and $QueueObj->Id ) {
+    if ( my $Map = RT->Config->PriorityMap( $QueueObj->Name ) ) {
+        %Map   = %$Map;
+        @Order = @{RT->Config->PriorityMapOrder( $QueueObj->Name )};
+        if ( defined $Default && length $Default ) {
+        $DefaultLabel = $QueueObj->PriorityAsString( $Default );
+        }
+    }
+}
+
 $Default = '' unless defined $Default;
 </%INIT>
diff --git a/share/html/Search/Elements/PickBasics b/share/html/Search/Elements/PickBasics
index 124fc7d9b3..86a7f48ceb 100644
--- a/share/html/Search/Elements/PickBasics
+++ b/share/html/Search/Elements/PickBasics
@@ -398,6 +398,18 @@ else {
             Value => { Type => 'text', Size => 5 }
         },
     );
+
+    if ( RT->Config->Get('EnablePriorityAsString') ) {
+        my ( $priority_dropdown ) = grep { $_->{Name} eq "Priority" } @lines;
+        my %priority_map = ( '-' => '' );
+        for my $queue (keys %queues) {
+            my $queue_priority_map= RT::Config->PriorityMap( $queue );
+            for my $priority ( keys %$queue_priority_map ) {
+                $priority_map{ keys %queues > 1 ? "$queue: $priority" : "$priority" } = $queue_priority_map->{$priority};
+            }
+        }
+        $priority_dropdown->{Value}{Arguments}{PriorityStringMap} = \%priority_map;
+    }
 }
 
 $m->callback( Conditions => \@lines );
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 35848d74be..a34a03bddf 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -246,7 +246,8 @@
 <div class="form-row">
   <div class="label col-md-3"><&|/l&>Priority</&>:</div>
   <div class="value col-md-9"><& /Elements/SelectPriority,
-      Name => "InitialPriority",
+    Name => "InitialPriority",
+    QueueObj => $QueueObj,
     Default => $ARGS{InitialPriority} ? $ARGS{InitialPriority} : $QueueObj->DefaultValue('InitialPriority'),
   &></div>
 </div>
@@ -255,6 +256,7 @@
   <div class="label col-md-3"><&|/l&>Final Priority</&>:</div>
   <div class="value col-md-9"><& /Elements/SelectPriority,
     Name => "FinalPriority",
+    QueueObj => $QueueObj,
     Default => $ARGS{FinalPriority} ? $ARGS{FinalPriority} : $QueueObj->DefaultValue('FinalPriority'),
   &></div>
 </div>
diff --git a/share/html/Ticket/Elements/EditBasics b/share/html/Ticket/Elements/EditBasics
index 56171cd439..c8e86a38a2 100644
--- a/share/html/Ticket/Elements/EditBasics
+++ b/share/html/Ticket/Elements/EditBasics
@@ -127,6 +127,7 @@ unless ( @fields ) {
                     comp => '/Elements/SelectPriority',
                     args => {
                         Name => $field,
+                        QueueObj => $QueueObj,
                         Default => $defaults{$field} || $TicketObj->$field,
                     }
                 }
diff --git a/share/html/Ticket/Elements/ShowPriority b/share/html/Ticket/Elements/ShowPriority
index 5fb0ad82c6..0c8670d04f 100644
--- a/share/html/Ticket/Elements/ShowPriority
+++ b/share/html/Ticket/Elements/ShowPriority
@@ -45,7 +45,7 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<% $Ticket->Priority %>/<% $Ticket->FinalPriority || ''%>
+<% $Ticket->PriorityAsString %>/<% $Ticket->FinalPriorityAsString || ''%>
 <%ARGS>
 $Ticket => undef
 </%ARGS>
diff --git a/t/web/mobile.t b/t/web/mobile.t
index 3f32e49e66..2a1b847330 100644
--- a/t/web/mobile.t
+++ b/t/web/mobile.t
@@ -2,6 +2,9 @@ use strict;
 use warnings;
 use RT::Test tests => 170;
 
+# we want to check the numerical priorities
+RT::Config->Set( EnablePriorityAsString => 0 );
+
 my ( $url, $m ) = RT::Test->started_ok;
 my $root = RT::Test->load_or_create_user( Name => 'root' );
 

commit b420bf72ebbc4ed778f207bcf7f39f2749975c68
Author: michel <michel at bestpractical.com>
Date:   Wed Dec 18 16:45:44 2019 +0100

    Test PriorityAsString.

diff --git a/t/web/queue_create.t b/t/web/queue_create.t
index 40f7b3b8b8..680a2ddd9c 100644
--- a/t/web/queue_create.t
+++ b/t/web/queue_create.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 13;
+use RT::Test tests => 16;
 
 my ( $baseurl, $m ) = RT::Test->started_ok;
 ok $m->login, 'logged in as root';
@@ -69,5 +69,15 @@ diag "Create a queue";
 
     $queue_id = $m->form_name('ModifyQueue')->value('id');
     ok $queue_id, "found id of the queue in the form, it's #$queue_id";
-}
 
+    # Test setting priority
+    $m->follow_link(id => 'page-default-values');
+    $m->content_contains('Final Priority');
+    $m->form_name('ModifyDefaultValues');
+    $m->click_button(value => 'Save Changes');
+    $m->content_contains('Default value of InitialPriority changed');
+    $m->form_name('ModifyDefaultValues');
+    $m->set_fields( FinalPriority => 50);
+    $m->click_button(value => 'Save Changes');
+    $m->content_contains('Default value of FinalPriority changed from Low to Medium');
+}

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


More information about the rt-commit mailing list