[Rt-commit] rt branch, 5.0/priority-as-string, created. rt-5.0.0alpha1-17-ga76579031

? sunnavy sunnavy at bestpractical.com
Fri Feb 28 16:09:56 EST 2020


The branch, 5.0/priority-as-string has been created
        at  a7657903172a870b31fab0f860adaeb28948d24f (commit)

- Log -----------------------------------------------------------------
commit 96bf477512b9860d5bbcff95c2f5f042595b05a9
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Feb 26 22:56:09 2020 +0800

    Core RT-Extension-PriorityAsString

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 9bed2dbd1..23066ea53 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2709,6 +2709,42 @@ cut down on page clutter. Once this option is clicked the link will change to
 
 Set($HideOneTimeSuggestions, 0);
 
+=item C<%PriorityAsString>
+
+Specify a mapping between priority strings and the internal numeric
+representation
+
+=cut
+
+Set(%PriorityAsString, (Low => 0, Medium => 50, High => 100));
+
+=item C<@PriorityAsStringOrder>
+
+Fine-tuned control of the order of priorities as displayed in the drop-down
+box; usually this computed automatically and need not be set explicitly.  It
+can be used to limit the set of options presented during update, but allow a
+richer set of levels when they are adjusted automatically.
+
+=cut
+
+Set(@PriorityAsStringOrder, qw(Low Medium High));
+
+=item C<%PriorityAsStringQueues>
+
+Each key is the name of a different queue; queues which do not appear in
+this configuration will use RT's default numeric scale.  This option means
+that C<%PriorityAsString> and C<@PriorityAsStringOrder> are ignored (no
+global override, you must specify a set of priorities per queue).
+
+    Set(%PriorityAsStringQueues,
+       General => { Low => 0, Medium => 50, High => 100 },
+       Binary  => { Low => 0, High => 10 },
+    );
+
+=cut
+
+Set(%PriorityAsStringQueues, ());
+
 =back
 
 =head2 Group Summary Configuration
diff --git a/lib/RT.pm b/lib/RT.pm
index 6116333a4..b7a294b6a 100644
--- a/lib/RT.pm
+++ b/lib/RT.pm
@@ -767,6 +767,7 @@ our %CORED_PLUGINS = (
     'RT::Extension::AdminConditionsAndActions' => '4.4.2',
     'RT::Extension::RightsInspector' => '5.0',
     'RT::Extension::ConfigInDatabase' => '5.0',
+    'RT::Extension::PriorityAsString' => '5.0',
 );
 
 sub InitPlugins {
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index d0946326b..2814989ba 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -3731,6 +3731,46 @@ sub Serialize {
     return %store;
 }
 
+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;
+    my $queues = RT->Config->Get('PriorityAsStringQueues');
+    if (@_) {
+        %map = %{ shift(@_) };
+    }
+    elsif ( $queues and $queues->{ $self->QueueObj->Name } ) {
+        %map = %{ $queues->{ $self->QueueObj->Name } };
+    }
+    else {
+        %map = RT->Config->Get('PriorityAsString');
+    }
+
+    # Count from high down to low until we find one that our number is
+    # greater than or equal to.
+    foreach my $label ( sort { $map{$b} <=> $map{$a} } keys %map ) {
+        return $label if $priority >= $map{$label};
+    }
+    return "unknown";
+}
+
 RT::Base->_ImportOverlays();
 
 1;
diff --git a/share/html/Elements/RT__Ticket/ColumnMap b/share/html/Elements/RT__Ticket/ColumnMap
index 1d9c1be72..0162875d5 100644
--- a/share/html/Elements/RT__Ticket/ColumnMap
+++ b/share/html/Elements/RT__Ticket/ColumnMap
@@ -384,6 +384,45 @@ 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 =~ /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>} );
+
+    };
+    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';
+
+        my %queues = RT->Config->Get('PriorityAsStringQueues');
+        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 2889b4629..0dfec265b 100644
--- a/share/html/Elements/SelectPriority
+++ b/share/html/Elements/SelectPriority
@@ -51,5 +51,25 @@ $Name => 'Priority'
 $Default => ''
 </%ARGS>
 <%INIT>
+my %queues = RT->Config->Get('PriorityAsStringQueues');
+
+# If enabled for all queues, always show the drop-down
+return $m->comp("/Elements/SelectPriorityAsString",%ARGS)
+    unless keys %queues;
+
+# Some callsites we can easily override with callbacks with logic to
+# know when to call SelectPriorityAsString; for ticket create and queue
+# modify, we need to inspect the callstack.
+my $caller      = $m->callers(1)->path;
+my $caller_args = $m->caller_args(1);
+my $QueueObj = RT::Queue->new( $session{'CurrentUser'} );
+if ( $caller eq "/Admin/Queues/Modify.html") {
+    $QueueObj->Load( $caller_args->{id} ) || $QueueObj->Load( $caller_args->{Name} );
+} elsif ( $caller eq "/Ticket/Create.html" or $caller eq "/m/ticket/create" ) {
+    $QueueObj->Load( $caller_args->{Queue} );
+}
+return $m->comp("/Elements/SelectPriorityAsString",%ARGS, Mapping => $queues{$QueueObj->Name})
+    if $QueueObj->Id and $queues{$QueueObj->Name};
+
 $Default = '' unless defined $Default;
 </%INIT>
diff --git a/share/html/Elements/SelectPriority b/share/html/Elements/SelectPriorityAsString
similarity index 67%
copy from share/html/Elements/SelectPriority
copy to share/html/Elements/SelectPriorityAsString
index 2889b4629..c0d733075 100644
--- a/share/html/Elements/SelectPriority
+++ b/share/html/Elements/SelectPriorityAsString
@@ -45,11 +45,39 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<input name="<% $Name %>" type="text" value="<% $Default %>" size="5" class="form-control" />
+<select class="select-priority selectpicker form-control" name="<% $Name %>">
+% unless ( defined $Default ) {
+<option value="">-</option>
+% }
+<%PERL>
+foreach my $label ( @order ) {
+    my ($value, $selected);
+    if ( $label eq $default_label ) {
+        ($value, $selected) = ($Default, 'selected="selected"');
+    } else {
+        ($value, $selected) = ($map{ $label }, '');
+    }
+</%PERL>
+<option class="<% lc $label %>" value="<% $value %>" <% $selected |n %>><% loc($label) %></option>
+% }
+</select>
 <%ARGS>
 $Name => 'Priority'
-$Default => ''
+$Default => undef
+$Mapping => undef
 </%ARGS>
 <%INIT>
-$Default = '' unless defined $Default;
+
+my %map   = $Mapping ? %{ $Mapping } : RT->Config->Get('PriorityAsString');
+my @order;
+if (not $Mapping and RT->Config->Get('PriorityAsStringOrder')) {
+    @order = grep {exists $map{$_}} RT->Config->Get('PriorityAsStringOrder');
+} else {
+    @order = sort { $map{$a} <=> $map{$b} } keys %map;
+}
+
+my $default_label = '';
+if ( defined $Default && length $Default ) {
+    $default_label = RT::Ticket->_PriorityAsString( $Default, \%map ) || '';
+}
 </%INIT>
diff --git a/share/html/Search/Elements/PickBasics b/share/html/Search/Elements/PickBasics
index 124fc7d9b..3d4612cbc 100644
--- a/share/html/Search/Elements/PickBasics
+++ b/share/html/Search/Elements/PickBasics
@@ -398,6 +398,27 @@ else {
             Value => { Type => 'text', Size => 5 }
         },
     );
+
+    my %as_string = RT->Config->Get('PriorityAsStringQueues');
+    if ( %as_string && %queues && scalar(keys %queues) == grep {$as_string{$_}} keys %queues ) {
+        # Additionally, all queues in PriorityAsStringQueues must use the _same_
+        # values for each name; if "High" is mapped to 10 in one queue and 100
+        # in another, we can't use names
+        my %values;
+        my $match = 1;
+        for my $q (keys %queues) {
+            for my $priority (keys %{$as_string{$q}}) {
+                $match = 0 if exists $values{$priority} and $as_string{$q}{$priority} != $values{$priority};
+                $values{$priority} = $as_string{$q}{$priority};
+            }
+        }
+        if ( $match ) {
+            # Swap out the /Elements/SelectPriority for /Elements/SelectPriorityAsString
+            my ($priority) = grep {$_->{Name} eq "Priority"} @lines;
+            $priority->{Value}{Path} = "/Elements/SelectPriorityAsString";
+            $priority->{Value}{Arguments}{Mapping} = \%values;
+        }
+    }
 }
 
 $m->callback( Conditions => \@lines );
diff --git a/share/html/Ticket/Elements/EditBasics b/share/html/Ticket/Elements/EditBasics
index 7f1551288..9309da30c 100644
--- a/share/html/Ticket/Elements/EditBasics
+++ b/share/html/Ticket/Elements/EditBasics
@@ -162,6 +162,17 @@ if ($ExcludeOwner) {
 # inflate the marker for custom roles into the field specs for each one
 @fields = map { ($_->{special}||'') eq 'roles' ? @role_fields : $_ } @fields;
 
+my %as_string = RT->Config->Get('PriorityAsStringQueues');
+
+if ( keys %as_string && $TicketObj && $as_string{$TicketObj->QueueObj->Name} ) {
+    # Swap out the /Elements/SelectPriority for /Elements/SelectPriorityAsString
+    for my $field (@fields) {
+        next unless ($field->{comp}||'') eq "/Elements/SelectPriority";
+        $field->{comp} = "/Elements/SelectPriorityAsString";
+        $field->{args}{Mapping} = $as_string{$TicketObj->QueueObj->Name};
+    }
+}
+
 $m->callback( CallbackName => 'MassageFields', %ARGS, TicketObj => $TicketObj, Fields => \@fields );
 
 # Process the field list, skipping if html is provided and running the
diff --git a/share/html/Ticket/Elements/ShowPriority b/share/html/Ticket/Elements/ShowPriority
index 5fb0ad82c..8043e34f5 100644
--- a/share/html/Ticket/Elements/ShowPriority
+++ b/share/html/Ticket/Elements/ShowPriority
@@ -45,7 +45,24 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
+% if (keys %queues and not $queues{$Ticket->QueueObj->Name}) {
 <% $Ticket->Priority %>/<% $Ticket->FinalPriority || ''%>
+% } else {
+% my $current = $Ticket->PriorityAsString || '';
+% my $final = $Ticket->FinalPriorityAsString || '';
+<span class="ticket-info-priority-<% $CSSClass->(lc($current)) %>"><% loc($current) %></span>/\
+<span class="ticket-info-final-priority-<% $CSSClass->(lc($final)) %>"><% loc($final) %></span>
+% }
 <%ARGS>
 $Ticket => undef
 </%ARGS>
+<%INIT>
+my %queues = RT->Config->Get('PriorityAsStringQueues');
+
+my $CSSClass = sub {
+    my $value = shift;
+    return '' unless defined $value;
+    $value =~ s/[^A-Za-z0-9_-]/_/g;
+    return $value;
+};
+</%INIT>

commit cfa722a81a9525dbb43fb2917c1a88e3bb2df4a8
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Sat Feb 29 02:28:47 2020 +0800

    Add "priority-" prefix to css classes of priority strings
    
    CSS classes like low/medium/high could cause name conflicts easily.

diff --git a/share/html/Elements/SelectPriorityAsString b/share/html/Elements/SelectPriorityAsString
index c0d733075..1664cc3d1 100644
--- a/share/html/Elements/SelectPriorityAsString
+++ b/share/html/Elements/SelectPriorityAsString
@@ -58,7 +58,7 @@ foreach my $label ( @order ) {
         ($value, $selected) = ($map{ $label }, '');
     }
 </%PERL>
-<option class="<% lc $label %>" value="<% $value %>" <% $selected |n %>><% loc($label) %></option>
+<option class="priority-<% lc $label %>" value="<% $value %>" <% $selected |n %>><% loc($label) %></option>
 % }
 </select>
 <%ARGS>

commit 692297d580feeb8eca662950be541fa4aa5f5a39
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Feb 27 04:38:20 2020 +0800

    Drop @PriorityAsStringOrder and %PriorityAsStringQueues configs
    
    We will merge their functionality into %PriorityAsString.
    
    As an extension, PriorityAsString grew additional configuration options
    as features were added. As a core feature, we want to add as few new
    configuration options as possible while still providing the needed
    features.

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 23066ea53..1933c56f3 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2718,33 +2718,6 @@ representation
 
 Set(%PriorityAsString, (Low => 0, Medium => 50, High => 100));
 
-=item C<@PriorityAsStringOrder>
-
-Fine-tuned control of the order of priorities as displayed in the drop-down
-box; usually this computed automatically and need not be set explicitly.  It
-can be used to limit the set of options presented during update, but allow a
-richer set of levels when they are adjusted automatically.
-
-=cut
-
-Set(@PriorityAsStringOrder, qw(Low Medium High));
-
-=item C<%PriorityAsStringQueues>
-
-Each key is the name of a different queue; queues which do not appear in
-this configuration will use RT's default numeric scale.  This option means
-that C<%PriorityAsString> and C<@PriorityAsStringOrder> are ignored (no
-global override, you must specify a set of priorities per queue).
-
-    Set(%PriorityAsStringQueues,
-       General => { Low => 0, Medium => 50, High => 100 },
-       Binary  => { Low => 0, High => 10 },
-    );
-
-=cut
-
-Set(%PriorityAsStringQueues, ());
-
 =back
 
 =head2 Group Summary Configuration
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index 2814989ba..7dbdbc4ae 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -3751,17 +3751,7 @@ sub _PriorityAsString {
     my $priority = shift;
     return undef unless defined $priority && length $priority;
 
-    my %map;
-    my $queues = RT->Config->Get('PriorityAsStringQueues');
-    if (@_) {
-        %map = %{ shift(@_) };
-    }
-    elsif ( $queues and $queues->{ $self->QueueObj->Name } ) {
-        %map = %{ $queues->{ $self->QueueObj->Name } };
-    }
-    else {
-        %map = RT->Config->Get('PriorityAsString');
-    }
+    my %map = RT->Config->Get('PriorityAsString');
 
     # Count from high down to low until we find one that our number is
     # greater than or equal to.
diff --git a/share/html/Elements/RT__Ticket/ColumnMap b/share/html/Elements/RT__Ticket/ColumnMap
index 0162875d5..d1dfb0461 100644
--- a/share/html/Elements/RT__Ticket/ColumnMap
+++ b/share/html/Elements/RT__Ticket/ColumnMap
@@ -407,19 +407,9 @@ if(RT->Config->Get('DisplayTotalTimeWorked')) {
 
         my $method = $field . 'AsString';
 
-        my %queues = RT->Config->Get('PriorityAsStringQueues');
-        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;
-            };
-        }
+        $COLUMN_MAP->{$field}{'value'} = sub {
+            return $printer->( $class, $_[0]->$method() );
+        };
     }
 }
 
diff --git a/share/html/Elements/SelectPriority b/share/html/Elements/SelectPriority
index 0dfec265b..87b665c0f 100644
--- a/share/html/Elements/SelectPriority
+++ b/share/html/Elements/SelectPriority
@@ -51,25 +51,8 @@ $Name => 'Priority'
 $Default => ''
 </%ARGS>
 <%INIT>
-my %queues = RT->Config->Get('PriorityAsStringQueues');
-
-# If enabled for all queues, always show the drop-down
-return $m->comp("/Elements/SelectPriorityAsString",%ARGS)
-    unless keys %queues;
-
-# Some callsites we can easily override with callbacks with logic to
-# know when to call SelectPriorityAsString; for ticket create and queue
-# modify, we need to inspect the callstack.
-my $caller      = $m->callers(1)->path;
-my $caller_args = $m->caller_args(1);
-my $QueueObj = RT::Queue->new( $session{'CurrentUser'} );
-if ( $caller eq "/Admin/Queues/Modify.html") {
-    $QueueObj->Load( $caller_args->{id} ) || $QueueObj->Load( $caller_args->{Name} );
-} elsif ( $caller eq "/Ticket/Create.html" or $caller eq "/m/ticket/create" ) {
-    $QueueObj->Load( $caller_args->{Queue} );
-}
-return $m->comp("/Elements/SelectPriorityAsString",%ARGS, Mapping => $queues{$QueueObj->Name})
-    if $QueueObj->Id and $queues{$QueueObj->Name};
+my %config = RT->Config->Get('PriorityAsString');
+return $m->comp("/Elements/SelectPriorityAsString",%ARGS) if keys %config;
 
 $Default = '' unless defined $Default;
 </%INIT>
diff --git a/share/html/Elements/SelectPriorityAsString b/share/html/Elements/SelectPriorityAsString
index 1664cc3d1..ddae1c702 100644
--- a/share/html/Elements/SelectPriorityAsString
+++ b/share/html/Elements/SelectPriorityAsString
@@ -64,17 +64,11 @@ foreach my $label ( @order ) {
 <%ARGS>
 $Name => 'Priority'
 $Default => undef
-$Mapping => undef
 </%ARGS>
 <%INIT>
 
-my %map   = $Mapping ? %{ $Mapping } : RT->Config->Get('PriorityAsString');
-my @order;
-if (not $Mapping and RT->Config->Get('PriorityAsStringOrder')) {
-    @order = grep {exists $map{$_}} RT->Config->Get('PriorityAsStringOrder');
-} else {
-    @order = sort { $map{$a} <=> $map{$b} } keys %map;
-}
+my %map   = RT->Config->Get('PriorityAsString');
+my @order = sort { $map{$a} <=> $map{$b} } keys %map;
 
 my $default_label = '';
 if ( defined $Default && length $Default ) {
diff --git a/share/html/Search/Elements/PickBasics b/share/html/Search/Elements/PickBasics
index 3d4612cbc..124fc7d9b 100644
--- a/share/html/Search/Elements/PickBasics
+++ b/share/html/Search/Elements/PickBasics
@@ -398,27 +398,6 @@ else {
             Value => { Type => 'text', Size => 5 }
         },
     );
-
-    my %as_string = RT->Config->Get('PriorityAsStringQueues');
-    if ( %as_string && %queues && scalar(keys %queues) == grep {$as_string{$_}} keys %queues ) {
-        # Additionally, all queues in PriorityAsStringQueues must use the _same_
-        # values for each name; if "High" is mapped to 10 in one queue and 100
-        # in another, we can't use names
-        my %values;
-        my $match = 1;
-        for my $q (keys %queues) {
-            for my $priority (keys %{$as_string{$q}}) {
-                $match = 0 if exists $values{$priority} and $as_string{$q}{$priority} != $values{$priority};
-                $values{$priority} = $as_string{$q}{$priority};
-            }
-        }
-        if ( $match ) {
-            # Swap out the /Elements/SelectPriority for /Elements/SelectPriorityAsString
-            my ($priority) = grep {$_->{Name} eq "Priority"} @lines;
-            $priority->{Value}{Path} = "/Elements/SelectPriorityAsString";
-            $priority->{Value}{Arguments}{Mapping} = \%values;
-        }
-    }
 }
 
 $m->callback( Conditions => \@lines );
diff --git a/share/html/Ticket/Elements/EditBasics b/share/html/Ticket/Elements/EditBasics
index 9309da30c..7f1551288 100644
--- a/share/html/Ticket/Elements/EditBasics
+++ b/share/html/Ticket/Elements/EditBasics
@@ -162,17 +162,6 @@ if ($ExcludeOwner) {
 # inflate the marker for custom roles into the field specs for each one
 @fields = map { ($_->{special}||'') eq 'roles' ? @role_fields : $_ } @fields;
 
-my %as_string = RT->Config->Get('PriorityAsStringQueues');
-
-if ( keys %as_string && $TicketObj && $as_string{$TicketObj->QueueObj->Name} ) {
-    # Swap out the /Elements/SelectPriority for /Elements/SelectPriorityAsString
-    for my $field (@fields) {
-        next unless ($field->{comp}||'') eq "/Elements/SelectPriority";
-        $field->{comp} = "/Elements/SelectPriorityAsString";
-        $field->{args}{Mapping} = $as_string{$TicketObj->QueueObj->Name};
-    }
-}
-
 $m->callback( CallbackName => 'MassageFields', %ARGS, TicketObj => $TicketObj, Fields => \@fields );
 
 # Process the field list, skipping if html is provided and running the
diff --git a/share/html/Ticket/Elements/ShowPriority b/share/html/Ticket/Elements/ShowPriority
index 8043e34f5..6e4ae1b96 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 }}}
-% if (keys %queues and not $queues{$Ticket->QueueObj->Name}) {
+% if (!keys %config) {
 <% $Ticket->Priority %>/<% $Ticket->FinalPriority || ''%>
 % } else {
 % my $current = $Ticket->PriorityAsString || '';
@@ -57,7 +57,7 @@
 $Ticket => undef
 </%ARGS>
 <%INIT>
-my %queues = RT->Config->Get('PriorityAsStringQueues');
+my %config = RT->Config->Get('PriorityAsString');
 
 my $CSSClass = sub {
     my $value = shift;

commit 1db69b7f11afc1d857eccab20a8c99483cb0c63b
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Feb 27 05:16:51 2020 +0800

    Add $EnablePriorityAsString config to globally disable PriorityAsString
    
    Technically this could also be done by setting %PriorityString to empty,
    but this separate boolean config is more friendly to RT admins.

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 1933c56f3..0c7e04601 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2709,6 +2709,16 @@ cut down on page clutter. Once this option is clicked the link will change to
 
 Set($HideOneTimeSuggestions, 0);
 
+=item C<%EnablePriorityAsString>
+
+Priorities are stored as numbers internally. This determines if we should
+convert and render them as strings instead. Set this to C<0> to render them
+as internal numbers.
+
+=cut
+
+Set($EnablePriorityAsString, 1);
+
 =item C<%PriorityAsString>
 
 Specify a mapping between priority strings and the internal numeric
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 9362e2a66..7164ecc92 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -1450,6 +1450,9 @@ our %META;
     EnableReminders => {
         Widget => '/Widgets/Form/Boolean',
     },
+    EnablePriorityAsString => {
+        Widget => '/Widgets/Form/Boolean',
+    },
     ExternalStorageDirectLink => {
         Widget => '/Widgets/Form/Boolean',
     },
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index 7dbdbc4ae..7e118eb38 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -3749,7 +3749,7 @@ sub FinalPriorityAsString {
 sub _PriorityAsString {
     my $self     = shift;
     my $priority = shift;
-    return undef unless defined $priority && length $priority;
+    return undef unless defined $priority && length $priority && RT->Config->Get('EnablePriorityAsString');
 
     my %map = RT->Config->Get('PriorityAsString');
 
diff --git a/share/html/Elements/RT__Ticket/ColumnMap b/share/html/Elements/RT__Ticket/ColumnMap
index d1dfb0461..2a4e3061f 100644
--- a/share/html/Elements/RT__Ticket/ColumnMap
+++ b/share/html/Elements/RT__Ticket/ColumnMap
@@ -384,7 +384,7 @@ if(RT->Config->Get('DisplayTotalTimeWorked')) {
     }
 }
 
-{
+if ( RT->Config->Get('EnablePriorityAsString') ) {
     my $printer = sub {
         my ( $class, $string ) = @_;
         return '' unless defined $string && length $string;
diff --git a/share/html/Elements/SelectPriority b/share/html/Elements/SelectPriority
index 87b665c0f..600b1eb38 100644
--- a/share/html/Elements/SelectPriority
+++ b/share/html/Elements/SelectPriority
@@ -51,8 +51,10 @@ $Name => 'Priority'
 $Default => ''
 </%ARGS>
 <%INIT>
-my %config = RT->Config->Get('PriorityAsString');
-return $m->comp("/Elements/SelectPriorityAsString",%ARGS) if keys %config;
+if ( RT->Config->Get('EnablePriorityAsString') ) {
+    my %config = RT->Config->Get('PriorityAsString');
+    return $m->comp( "/Elements/SelectPriorityAsString", %ARGS ) unless keys %config;
+}
 
 $Default = '' unless defined $Default;
 </%INIT>
diff --git a/share/html/Ticket/Elements/ShowPriority b/share/html/Ticket/Elements/ShowPriority
index 6e4ae1b96..7abf7be7d 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 }}}
-% if (!keys %config) {
+% if (!RT->Config->Get('EnablePriorityAsString') || !keys %config) {
 <% $Ticket->Priority %>/<% $Ticket->FinalPriority || ''%>
 % } else {
 % my $current = $Ticket->PriorityAsString || '';

commit 9126a53bfa6ab441e2f3e439cf4ef1c70cf46bdb
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Feb 27 09:05:05 2020 +0800

    Refactor %PriorityAsString format to be more flexible and powerful
    
    The new format now covers full functionality of deleted
    @PriorityAsStringOrder and %PriorityAsStringQueues

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 0c7e04601..90dcef0a5 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2722,11 +2722,40 @@ Set($EnablePriorityAsString, 1);
 =item C<%PriorityAsString>
 
 Specify a mapping between priority strings and the internal numeric
-representation
+representation. E.g.
+
+    Set(%PriorityAsString,
+        Default => { Low => 0, Medium => 50, High => 100 },
+        General => [ Medium => 50, Low => 0, High => 100 ],
+        Support => 0,
+    );
+
+Key is queue name or "Default", which is the fallback for unspecified
+queues. Values could be ArrayRef or HashRef or C<0>.
+
+=over
+
+=item ArrayRef
+
+This is the ordered String => Number map list. Pririty options will be
+rendered in the same order.
+
+=item HashRef
+
+This is the unordered String => Number map list. Priority options will be
+rendered in numerical ascending order.
+
+=item C<0>
+
+This is to render priorities as numbers.
+
+=back
 
 =cut
 
-Set(%PriorityAsString, (Low => 0, Medium => 50, High => 100));
+Set(%PriorityAsString,
+    Default => { Low => 0, Medium => 50, High => 100 },
+);
 
 =back
 
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 7164ecc92..c3fb8d925 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -1381,6 +1381,52 @@ our %META;
             $self->Set( 'ExternalInfoPriority', \@values );
         },
     },
+    PriorityAsString => {
+        Type          => 'HASH',
+        PostLoadCheck => sub {
+            my $self = shift;
+            return unless $self->Get('EnablePriorityAsString');
+            my $config = $self->Get('PriorityAsString');
+
+            my %label_value_map;
+
+            for my $name ( keys %$config ) {
+                if ( my $value = $config->{$name} ) {
+                    my @list;
+                    if ( ref $value eq 'ARRAY' ) {
+                        @list = @$value;
+                    }
+                    elsif ( ref $value eq 'HASH' ) {
+                        @list = %$value;
+                    }
+                    else {
+                        RT->Logger->error("Invalid value for $name in PriorityAsString");
+                        undef $config->{$name};
+                    }
+
+                    while ( my $label = shift @list ) {
+                        my $value = shift @list;
+
+                        if ( defined $label_value_map{$label} ) {
+                            if ( $label_value_map{$label} != $value ) {
+                                RT->Logger->debug(
+                                    "Priority $label is inconsistent: $label_value_map{$label} VS $value");
+                            }
+                        }
+                        else {
+                            $label_value_map{$label} = $value;
+                        }
+                    }
+
+                }
+            }
+
+            unless ( keys %label_value_map ) {
+                RT->Logger->debug("No valid PriorityAsString options");
+                $self->Set( 'EnablePriorityAsString', 0 );
+            }
+        },
+    },
     ServiceBusinessHours => {
         Type => 'HASH',
         PostLoadCheck   => sub {
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index 7e118eb38..e76c62a56 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -3747,11 +3747,24 @@ sub FinalPriorityAsString {
 }
 
 sub _PriorityAsString {
-    my $self     = shift;
-    my $priority = shift;
+    my $self       = shift;
+    my $priority   = shift;
+    my $queue_name = shift || $self->QueueObj->__Value('Name');    # Skip ACL check
+
     return undef unless defined $priority && length $priority && RT->Config->Get('EnablePriorityAsString');
 
-    my %map = RT->Config->Get('PriorityAsString');
+    my %config = RT->Config->Get('PriorityAsString');
+    my $value = ( exists $config{$queue_name} ? $config{$queue_name} : $config{Default} ) or return undef;
+    my %map;
+    if ( ref $value eq 'ARRAY' ) {
+        %map = @$value;
+    }
+    elsif ( ref $value eq 'HASH' ) {
+        %map = %$value;
+    }
+    else {
+        RT->Logger->warning("Invalid PriorityAsString value: $value");
+    }
 
     # Count from high down to low until we find one that our number is
     # greater than or equal to.
diff --git a/share/html/Elements/RT__Ticket/ColumnMap b/share/html/Elements/RT__Ticket/ColumnMap
index 2a4e3061f..13a5c3de0 100644
--- a/share/html/Elements/RT__Ticket/ColumnMap
+++ b/share/html/Elements/RT__Ticket/ColumnMap
@@ -160,21 +160,21 @@ $COLUMN_MAP = {
         title     => 'Priority', # loc
         attribute => 'Priority',
         value     => sub { return $_[0]->Priority },
-        edit      => sub { return \($m->scomp('/Elements/SelectPriority', Name => 'Priority', Default => $_[0]->Priority)) },
+        edit      => sub { return \($m->scomp('/Elements/SelectPriority', Name => 'Priority', Default => $_[0]->Priority, QueueObj => $_[0]->QueueObj )) },
     },
     InitialPriority => {
         title     => 'InitialPriority', # loc
         attribute => 'InitialPriority',
         name      => 'Initial Priority',
         value     => sub { return $_[0]->InitialPriority },
-        edit      => sub { return \($m->scomp('/Elements/SelectPriority', Name => 'InitialPriority', Default => $_[0]->InitialPriority)) },
+        edit      => sub { return \($m->scomp('/Elements/SelectPriority', Name => 'InitialPriority', Default => $_[0]->InitialPriority, QueueObj => $_[0]->QueueObj)) },
     },
     FinalPriority => {
         title     => 'FinalPriority', # loc
         attribute => 'FinalPriority',
         name      => 'Final Priority',
         value     => sub { return $_[0]->FinalPriority },
-        edit      => sub { return \($m->scomp('/Elements/SelectPriority', Name => 'FinalPriority', Default => $_[0]->FinalPriority)) },
+        edit      => sub { return \($m->scomp('/Elements/SelectPriority', Name => 'FinalPriority', Default => $_[0]->FinalPriority, QueueObj => $_[0]->QueueObj)) },
     },
     EffectiveId => {
         title     => 'EffectiveId', # loc
@@ -408,7 +408,8 @@ if ( RT->Config->Get('EnablePriorityAsString') ) {
         my $method = $field . 'AsString';
 
         $COLUMN_MAP->{$field}{'value'} = sub {
-            return $printer->( $class, $_[0]->$method() );
+            # Fallback to numbers when the queue disables PriorityAsString
+            return $printer->( $class, $_[0]->$method() ) || $_[0]->$field;
         };
     }
 }
diff --git a/share/html/Elements/SelectPriority b/share/html/Elements/SelectPriority
index 600b1eb38..130c7e6db 100644
--- a/share/html/Elements/SelectPriority
+++ b/share/html/Elements/SelectPriority
@@ -49,11 +49,76 @@
 <%ARGS>
 $Name => 'Priority'
 $Default => ''
+$QueueObj => undef
+%Queues => ()
 </%ARGS>
 <%INIT>
+use List::MoreUtils 'uniq';
 if ( RT->Config->Get('EnablePriorityAsString') ) {
     my %config = RT->Config->Get('PriorityAsString');
-    return $m->comp( "/Elements/SelectPriorityAsString", %ARGS ) unless keys %config;
+
+    my @names;
+    if ($QueueObj) {
+        push @names, $QueueObj->__Value('Name');    # Skip ACL check
+    }
+    elsif (%Queues) {
+        for my $id ( keys %Queues ) {
+            my $queue = RT::Queue->new( $session{'CurrentUser'} );
+            $queue->Load($id);
+            if ( $queue->Id ) {
+                push @names, $queue->__Value('Name');    # Skip ACL check
+            }
+        }
+    }
+    else {
+        @names = keys %config;
+    }
+
+    my $use_numeric;
+    my @values;
+    for my $name ( sort { lc $a cmp lc $b } uniq @names ) {
+        my $value = exists $config{$name} ? $config{$name} : $config{Default};
+        if ($value) {
+            push @values, $value;
+        }
+        else {
+            RT->Logger->debug("PriorityAsString for Queue $name is disabled, skipping");
+            $use_numeric = 1;
+            last;
+        }
+    }
+
+    my @options;
+    my %map;
+
+    # Items in @values are hashrefs/arrayrefs, we still can de-duplicate
+    # using uniq because duplicated ones are totally identical.
+    for my $value ( uniq @values ) {
+        my @list;
+        if ( ref $value eq 'ARRAY' ) {
+            @list = @$value;
+        }
+        elsif ( ref $value eq 'HASH' ) {
+            @list = map { $_ => $value->{$_} } sort { $value->{$a} <=> $value->{$b} } keys %$value;
+        }
+
+        while ( my $label = shift @list ) {
+            my $option = { Label => $label, Value => shift @list };
+            if ( defined $map{$label} ) {
+                if ( $map{$label} != $option->{Value} ) {
+                    $use_numeric = 1;
+                    last;
+                }
+            }
+            else {
+                $map{$label} = $option->{Value};
+                push @options, $option;
+            }
+        }
+    }
+
+    $use_numeric = 1 unless @options;
+    return $m->comp( "/Elements/SelectPriorityAsString", %ARGS, Options => \@options ) unless $use_numeric;
 }
 
 $Default = '' unless defined $Default;
diff --git a/share/html/Elements/SelectPriorityAsString b/share/html/Elements/SelectPriorityAsString
index ddae1c702..6bad0f534 100644
--- a/share/html/Elements/SelectPriorityAsString
+++ b/share/html/Elements/SelectPriorityAsString
@@ -50,12 +50,12 @@
 <option value="">-</option>
 % }
 <%PERL>
-foreach my $label ( @order ) {
-    my ($value, $selected);
+for my $option ( @Options ) {
+    my $label = $option->{Label};
+    my $value = $option->{Value};
+    my $selected = '';
     if ( $label eq $default_label ) {
         ($value, $selected) = ($Default, 'selected="selected"');
-    } else {
-        ($value, $selected) = ($map{ $label }, '');
     }
 </%PERL>
 <option class="priority-<% lc $label %>" value="<% $value %>" <% $selected |n %>><% loc($label) %></option>
@@ -64,14 +64,12 @@ foreach my $label ( @order ) {
 <%ARGS>
 $Name => 'Priority'
 $Default => undef
+$QueueObj => undef
+ at Options
 </%ARGS>
 <%INIT>
-
-my %map   = RT->Config->Get('PriorityAsString');
-my @order = sort { $map{$a} <=> $map{$b} } keys %map;
-
 my $default_label = '';
-if ( defined $Default && length $Default ) {
-    $default_label = RT::Ticket->_PriorityAsString( $Default, \%map ) || '';
+if ( defined $Default && length $Default && $QueueObj ) {
+    $default_label = RT::Ticket->_PriorityAsString( $Default, $QueueObj->__Value('Name') ) || '';
 }
 </%INIT>
diff --git a/share/html/Search/Elements/PickBasics b/share/html/Search/Elements/PickBasics
index 124fc7d9b..9392ef105 100644
--- a/share/html/Search/Elements/PickBasics
+++ b/share/html/Search/Elements/PickBasics
@@ -385,6 +385,7 @@ else {
             Value => {
                 Type => 'component',
                 Path => '/Elements/SelectPriority',
+                Arguments => { Queues => \%queues },
             },
         },
         {
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index e11f9fb6b..c42269639 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -249,7 +249,8 @@
   <div class="label col-md-3"><&|/l&>Priority</&>:</div>
   <div class="value col-md-9"><& /Elements/SelectPriority,
       Name => "InitialPriority",
-    Default => $ARGS{InitialPriority} ? $ARGS{InitialPriority} : $QueueObj->DefaultValue('InitialPriority'),
+      Default => $ARGS{InitialPriority} ? $ARGS{InitialPriority} : $QueueObj->DefaultValue('InitialPriority'),
+      QueueObj => $QueueObj,
   &></div>
 </div>
 
@@ -258,6 +259,7 @@
   <div class="value col-md-9"><& /Elements/SelectPriority,
     Name => "FinalPriority",
     Default => $ARGS{FinalPriority} ? $ARGS{FinalPriority} : $QueueObj->DefaultValue('FinalPriority'),
+    QueueObj => $QueueObj,
   &></div>
 </div>
 
diff --git a/share/html/Ticket/Elements/EditBasics b/share/html/Ticket/Elements/EditBasics
index 7f1551288..89f3d4ac2 100644
--- a/share/html/Ticket/Elements/EditBasics
+++ b/share/html/Ticket/Elements/EditBasics
@@ -129,6 +129,7 @@ unless ( @fields ) {
                     args => {
                         Name => $field,
                         Default => $defaults{$field} || $TicketObj->$field,
+                        QueueObj => $TicketObj->QueueObj,
                     }
                 }
             } ('Priority', 'Final Priority')
diff --git a/share/html/Ticket/Elements/ShowPriority b/share/html/Ticket/Elements/ShowPriority
index 7abf7be7d..0967326bd 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 }}}
-% if (!RT->Config->Get('EnablePriorityAsString') || !keys %config) {
+% if ($use_numeric) {
 <% $Ticket->Priority %>/<% $Ticket->FinalPriority || ''%>
 % } else {
 % my $current = $Ticket->PriorityAsString || '';
@@ -57,7 +57,14 @@
 $Ticket => undef
 </%ARGS>
 <%INIT>
-my %config = RT->Config->Get('PriorityAsString');
+my $use_numeric = 1;
+
+if ( RT->Config->Get('EnablePriorityAsString') ) {
+    my %config     = RT->Config->Get('PriorityAsString');
+    my $queue_name = $Ticket->QueueObj->__Value('Name');    # Skip ACL check
+
+    $use_numeric = 0 if exists $config{$queue_name} ? $config{$queue_name} : $config{Default};
+}
 
 my $CSSClass = sub {
     my $value = shift;

commit a8c2452c522add7868822fb49f4022b8e8e3f756
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Feb 28 05:05:35 2020 +0800

    Show string version of priorities to end users when possible
    
    This is to obey the rule of least surprise. If PriorityAsString is
    enabled, we should show priorities as string consistently.
    
    E.g. when user updated Priority from "Low" to "Medium", the successful
    message and corresponding transaction should show:
    
        Priority changed from 'Low' to 'Medium'
    
    instead of:
    
        Priority changed from '0' to '50'
    
    Another example: in search builder, when user selected "Priority" "less
    than" "High", we should add
    
        Priority < 'High'
    
    instead of:
    
        Priority < 100
    
    But when configs conflict somehow like:
    
        General => { Low => 0, Medium => 50, High => 100 },
        Support => { Low => 20, Medium => 70, High => 200 },
    
    or
    
        General => { Low => 0, Medium => 50, High => 100 },
        Support => { VeryLow => 0, Medium => 50, VeryHigh => 100 },
    
    Since there is no one-to-one map of Number <=> String, to avoid
    confusion, we have to show priorities as numbers(e.g. in search builder
    when invovled queues conflict). I believe it's a rare case.

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index c3fb8d925..5346e59c1 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -1389,6 +1389,7 @@ our %META;
             my $config = $self->Get('PriorityAsString');
 
             my %label_value_map;
+            my %value_label_map;
 
             for my $name ( keys %$config ) {
                 if ( my $value = $config->{$name} ) {
@@ -1412,9 +1413,15 @@ our %META;
                                 RT->Logger->debug(
                                     "Priority $label is inconsistent: $label_value_map{$label} VS $value");
                             }
+
+                            if ( $value_label_map{$value} ne $label ) {
+                                RT->Logger->debug(
+                                    "Priority $value is inconsistent: $value_label_map{$value} VS $label");
+                            }
                         }
                         else {
                             $label_value_map{$label} = $value;
+                            $value_label_map{$value} = $label;
                         }
                     }
 
diff --git a/lib/RT/Queue.pm b/lib/RT/Queue.pm
index 14d66885f..2349383e8 100644
--- a/lib/RT/Queue.pm
+++ b/lib/RT/Queue.pm
@@ -1231,6 +1231,17 @@ sub SetDefaultValue {
         },
     );
 
+    if ( $args{Name} =~ /Priority/ && RT->Config->Get('EnablePriorityAsString') ) {
+        if ( $old_value ne $self->loc('(no value)') ) {
+            my $str = RT::Ticket->_PriorityAsString( $old_value, $self->Name );
+            $old_value = $self->loc($str) if $str;
+        }
+        if ( $new_value ne $self->loc('(no value)') ) {
+            my $str = RT::Ticket->_PriorityAsString( $new_value, $self->Name );
+            $new_value = $self->loc($str) if $str;
+        }
+    }
+
     if ( $ret ) {
         return ( $ret, $self->loc( 'Default value of [_1] changed from [_2] to [_3]', $args{Name}, $old_value, $new_value ) );
     }
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index e094c63ef..383564e42 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -3133,6 +3133,45 @@ sub _parser {
         }
     );
 
+    if ( RT->Config->Get('EnablePriorityAsString') ) {
+        my $queues = $tree->GetReferencedQueues;
+        my %config = RT->Config->Get('PriorityAsString');
+        my @names;
+        if (%$queues) {
+            for my $id ( keys %$queues ) {
+                my $queue = RT::Queue->new( $self->CurrentUser );
+                $queue->Load($id);
+                if ( $queue->Id ) {
+                    push @names, $queue->__Value('Name');    # Skip ACL check
+                }
+            }
+        }
+        else {
+            @names = keys %config;
+        }
+
+        my %map;
+        for my $name (@names) {
+            if ( my $value = exists $config{$name} ? $config{$name} : $config{Default} ) {
+                my %hash = ref $value eq 'ARRAY' ? @$value : %$value;
+                for my $label ( keys %hash ) {
+                    $map{lc $label} //= $hash{$label};
+                }
+            }
+        }
+
+        $tree->traverse(
+            sub {
+                my $node = shift;
+                return unless $node->isLeaf;
+                my $value = $node->getNodeValue;
+                if ( $value->{Key} =~ /^(?:Initial|Final)?Priority$/i ) {
+                    $value->{Value} = $map{ lc $value->{Value} } if defined $map{ lc $value->{Value} };
+                }
+            }
+        );
+    }
+
     # Perform an optimization pass looking for watcher bundling
     $tree->traverse(
         sub {
diff --git a/lib/RT/Transaction.pm b/lib/RT/Transaction.pm
index 914fe51ca..a4b007de4 100644
--- a/lib/RT/Transaction.pm
+++ b/lib/RT/Transaction.pm
@@ -1261,6 +1261,20 @@ sub _CanonicalizeRoleName {
                 }
             }
         }
+        elsif ( $self->Field =~ /Priority/ && RT->Config->Get('EnablePriorityAsString') ) {
+            my $object = $self->Object;
+            my ( $old_value, $new_value );
+            if ( $object->isa('RT::Ticket') ) {
+                $old_value = $object->_PriorityAsString( $self->OldValue );
+                $new_value = $object->_PriorityAsString( $self->NewValue );
+                $old_value = $self->loc($old_value) if $old_value;
+                $new_value = $self->loc($new_value) if $new_value;
+            }
+            $old_value //= $self->OldValue;
+            $new_value //= $self->NewValue;
+
+            return ( "[_1] changed from [_2] to [_3]", $self->loc( $self->Field ), "'$old_value'", "'$new_value'" );    #loc()
+        }
         else {
             return ( "[_1] changed from [_2] to [_3]",
                     $self->loc($self->Field),
diff --git a/lib/RT/Transactions.pm b/lib/RT/Transactions.pm
index 948037d12..2b71ade9f 100644
--- a/lib/RT/Transactions.pm
+++ b/lib/RT/Transactions.pm
@@ -1001,6 +1001,45 @@ sub _parser {
         }
     );
 
+    if ( RT->Config->Get('EnablePriorityAsString') ) {
+        my $queues = $tree->GetReferencedQueues;
+        my %config = RT->Config->Get('PriorityAsString');
+        my @names;
+        if (%$queues) {
+            for my $id ( keys %$queues ) {
+                my $queue = RT::Queue->new( $self->CurrentUser );
+                $queue->Load($id);
+                if ( $queue->Id ) {
+                    push @names, $queue->__Value('Name');    # Skip ACL check
+                }
+            }
+        }
+        else {
+            @names = keys %config;
+        }
+
+        my %map;
+        for my $name (@names) {
+            if ( my $value = exists $config{$name} ? $config{$name} : $config{Default} ) {
+                my %hash = ref $value eq 'ARRAY' ? @$value : %$value;
+                for my $label ( keys %hash ) {
+                    $map{lc $label} //= $hash{$label};
+                }
+            }
+        }
+
+        $tree->traverse(
+            sub {
+                my $node = shift;
+                return unless $node->isLeaf;
+                my $value = $node->getNodeValue;
+                if ( $value->{Key} =~ /^Ticket(?:Initial|Final)?Priority$/i ) {
+                    $value->{Value} = $map{ lc $value->{Value} } if defined $map{ lc $value->{Value} };
+                }
+            }
+        );
+    }
+
     my $ea = '';
     $tree->traverse(
         sub {
diff --git a/share/html/Admin/Queues/DefaultValues.html b/share/html/Admin/Queues/DefaultValues.html
index a060a1bb3..aa540ab0a 100644
--- a/share/html/Admin/Queues/DefaultValues.html
+++ b/share/html/Admin/Queues/DefaultValues.html
@@ -59,7 +59,7 @@
       <&|/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 +68,7 @@
       <&|/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/SelectPriority b/share/html/Elements/SelectPriority
index 130c7e6db..ed8801944 100644
--- a/share/html/Elements/SelectPriority
+++ b/share/html/Elements/SelectPriority
@@ -51,6 +51,7 @@ $Name => 'Priority'
 $Default => ''
 $QueueObj => undef
 %Queues => ()
+$ValueAsString => undef
 </%ARGS>
 <%INIT>
 use List::MoreUtils 'uniq';
@@ -89,7 +90,8 @@ if ( RT->Config->Get('EnablePriorityAsString') ) {
     }
 
     my @options;
-    my %map;
+    my %label_value_map;
+    my %value_label_map;
 
     # Items in @values are hashrefs/arrayrefs, we still can de-duplicate
     # using uniq because duplicated ones are totally identical.
@@ -103,20 +105,33 @@ if ( RT->Config->Get('EnablePriorityAsString') ) {
         }
 
         while ( my $label = shift @list ) {
-            my $option = { Label => $label, Value => shift @list };
-            if ( defined $map{$label} ) {
-                if ( $map{$label} != $option->{Value} ) {
+            my $value = shift @list;
+            my $option = { Label => $label, Value => $value };
+
+            if ( defined $label_value_map{$label} ) {
+                if ( $label_value_map{$label} != $value ) {
                     $use_numeric = 1;
                     last;
                 }
+
+                if ( $ValueAsString && $value_label_map{$value} ne $label ) {
+                    $ValueAsString = 0;
+                }
             }
             else {
-                $map{$label} = $option->{Value};
+                $label_value_map{$label} = $value;
+                $value_label_map{$value} = $label;
                 push @options, $option;
             }
         }
     }
 
+    if ($ValueAsString) {
+        for my $option (@options) {
+            $option->{Value} = $option->{Label};
+        }
+    }
+
     $use_numeric = 1 unless @options;
     return $m->comp( "/Elements/SelectPriorityAsString", %ARGS, Options => \@options ) unless $use_numeric;
 }
diff --git a/share/html/Search/Bulk.html b/share/html/Search/Bulk.html
index 389334181..884d8a328 100644
--- a/share/html/Search/Bulk.html
+++ b/share/html/Search/Bulk.html
@@ -209,7 +209,7 @@
           <&|/l&>Make priority</&>:
         </div>
         <div class="col-md-9 value">
-          <& /Elements/SelectPriority, Name => "Priority", Default => $ARGS{Priority} &>
+          <& /Elements/SelectPriority, Name => "Priority", Default => $ARGS{Priority}, Queues => $seen_queues &>
         </div>
       </div>
 
diff --git a/share/html/Search/Elements/PickBasics b/share/html/Search/Elements/PickBasics
index 9392ef105..e9d43aca5 100644
--- a/share/html/Search/Elements/PickBasics
+++ b/share/html/Search/Elements/PickBasics
@@ -385,7 +385,7 @@ else {
             Value => {
                 Type => 'component',
                 Path => '/Elements/SelectPriority',
-                Arguments => { Queues => \%queues },
+                Arguments => { Queues => \%queues, ValueAsString => 1 },
             },
         },
         {
diff --git a/share/html/Search/Elements/PickTickets b/share/html/Search/Elements/PickTickets
index 2ecdaddf0..b0fe2f6de 100644
--- a/share/html/Search/Elements/PickTickets
+++ b/share/html/Search/Elements/PickTickets
@@ -181,6 +181,7 @@ my @lines = (
         Value => {
             Type => 'component',
             Path => '/Elements/SelectPriority',
+            Arguments => { Queues => \%queues, ValueAsString => 1 },
         },
     },
 );

commit b9833fe12571fe0469d0dab3d07d99157e6fac22
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Feb 28 05:33:34 2020 +0800

    Update tests as PriorityAsString is enabled by default

diff --git a/t/web/download_user_info.t b/t/web/download_user_info.t
index 44155d49a..06ade8e62 100644
--- a/t/web/download_user_info.t
+++ b/t/web/download_user_info.t
@@ -76,7 +76,7 @@ EOF
 
     my $ticket_info_tsv = <<EOF;
 id\tSubject\tStatus\tQueueName\tOwner\tPriority\tRequestors
-1\tTest\topen\tGeneral\tNobody in particular\t0\troot (Enoch Root)
+1\tTest\topen\tGeneral\tNobody in particular\tLow\troot (Enoch Root)
 EOF
 
     is $agent->content, $ticket_info_tsv, "User tickets downloaded correctly";
diff --git a/t/web/mobile.t b/t/web/mobile.t
index 3f32e49e6..831bf6652 100644
--- a/t/web/mobile.t
+++ b/t/web/mobile.t
@@ -82,7 +82,7 @@ $m->content_contains( 'ticket1', 'subject' );
 $m->content_contains( 'open', 'status' );
 $m->content_contains( 'cc at example.com', 'cc' );
 $m->content_contains( 'admincc at example.com', 'admincc' );
-$m->content_contains( '13/93', 'priority' );
+$m->text_contains( 'Low/Medium', 'priority' );
 $m->content_contains( '2 hour', 'time estimates' );
 $m->content_contains( '30 min', 'time worked' );
 $m->content_contains( '60 min', 'time left' );

commit a7657903172a870b31fab0f860adaeb28948d24f
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Sat Feb 29 04:02:31 2020 +0800

    Test the PriorityAsString feature

diff --git a/t/ticket/priority.t b/t/ticket/priority.t
new file mode 100644
index 000000000..ee76e46cb
--- /dev/null
+++ b/t/ticket/priority.t
@@ -0,0 +1,105 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef;
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+
+my $ticket = RT::Test->create_ticket( Queue => $queue->Id, );
+
+diag "Default PriorityAsString";
+
+for my $field (qw/Priority InitialPriority FinalPriority/) {
+    is( $ticket->$field, 0, "$field is 0" );
+    my $string_method = $field . 'AsString';
+    is( $ticket->$string_method, 'Low', "$string_method is Low" );
+}
+
+diag "Disable PriorityAsString";
+
+RT->Config->Set( 'EnablePriorityAsString', 0 );
+for my $field (qw/Priority InitialPriority FinalPriority/) {
+    my $string_method = $field . 'AsString';
+    is( $ticket->$string_method, undef, "$string_method is undef" );
+}
+
+diag "Disable PriorityAsString at queue level";
+
+RT->Config->Set( 'EnablePriorityAsString', 1 );
+RT->Config->Set( 'PriorityAsString', General => 0 );
+for my $field (qw/Priority InitialPriority FinalPriority/) {
+    my $string_method = $field . 'AsString';
+    is( $ticket->$string_method, undef, "$string_method is undef" );
+}
+
+diag "Specific PriorityAsString config at queue level";
+
+RT->Config->Set(
+    'PriorityAsString',
+    Default => { Low     => 0, Medium => 50, High   => 100 },
+    General => { VeryLow => 0, Low    => 20, Medium => 50, High => 100, VeryHigh => 200 },
+);
+for my $field (qw/Priority InitialPriority FinalPriority/) {
+    my $string_method = $field . 'AsString';
+    is( $ticket->$string_method, 'VeryLow', "$string_method is updated" );
+}
+
+diag "Update Priorities";
+
+my ( $ret, $msg ) = $ticket->SetPriority(50);
+ok( $ret, "Priority is updated" );
+is( $msg, "Priority changed from 'VeryLow' to 'Medium'", 'Priority updated message' );
+
+( $ret, $msg ) = $ticket->SetFinalPriority(100);
+ok( $ret, "FinalPriority is updated" );
+is( $msg, "FinalPriority changed from 'VeryLow' to 'High'", 'FinalPriority updated message' );
+
+diag "Queue default priorities";
+
+( $ret, $msg ) = $queue->SetDefaultValue( Name => 'InitialPriority', Value => 20 );
+ok( $ret, "InitialPriority defaulted to Low" );
+is( $msg, 'Default value of InitialPriority changed from (no value) to Low', "InitialPriority updated message" );
+
+( $ret, $msg ) = $queue->SetDefaultValue( Name => 'FinalPriority', Value => 100 );
+ok( $ret, "FinalPriority defaulted to High" );
+is( $msg, 'Default value of FinalPriority changed from (no value) to High', "FinalPriority updated message" );
+
+$ticket = RT::Test->create_ticket( Queue => $queue->Id, );
+is( $ticket->PriorityAsString,        'Low',  'PriorityAsString is correct' );
+is( $ticket->InitialPriorityAsString, 'Low',  'InitialPriorityAsString is correct' );
+is( $ticket->FinalPriorityAsString,   'High', 'FinalPriorityAsString is correct' );
+
+diag "Explicitly set priorities on create";
+
+$ticket = RT::Test->create_ticket( Queue => $queue->Id, InitialPriority => '50', FinalPriority => 200 );
+is( $ticket->PriorityAsString,        'Medium',   'PriorityAsString is correct' );
+is( $ticket->InitialPriorityAsString, 'Medium',   'InitialPriorityAsString is correct' );
+is( $ticket->FinalPriorityAsString,   'VeryHigh', 'FinalPriorityAsString is correct' );
+
+diag "Ticket/Transaction search";
+
+for my $field (qw/Priority InitialPriority FinalPriority/) {
+    my $tickets = RT::Tickets->new( RT->SystemUser );
+    $tickets->FromSQL("Queue = 'General' AND $field = 'Low'");
+    like( $tickets->BuildSelectQuery, qr/$field = '20'/, "$field is translated properly" );
+
+    my $txns = RT::Transactions->new( RT->SystemUser );
+    $txns->FromSQL("TicketQueue = 'General' AND Ticket$field = 'Low'");
+    like( $txns->BuildSelectQuery, qr/$field = '20'/, "Ticket$field is translated properly" );
+}
+
+my $tickets = RT::Tickets->new( RT->SystemUser );
+$tickets->FromSQL("Queue = 'General' AND Priority = 'Medium'");
+is( $tickets->Count, 2, 'Found 2 tickets' );
+while ( my $ticket = $tickets->Next ) {
+    is( $ticket->PriorityAsString, 'Medium', 'Priority is correct' );
+}
+
+my $txns = RT::Transactions->new( RT->SystemUser );
+$txns->FromSQL("TicketQueue = 'General' AND TicketPriority = 'Medium' AND Field = 'Priority'");
+is( $txns->Count, 1, 'Found 1 txn' );
+my $txn = $txns->First;
+is( $txn->OldValue, 0,  'OldValue is correct' );
+is( $txn->NewValue, 50, 'NewValue is correct' );
+
+done_testing;
diff --git a/t/web/priority.t b/t/web/priority.t
new file mode 100644
index 000000000..71dd0f0c1
--- /dev/null
+++ b/t/web/priority.t
@@ -0,0 +1,186 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef;
+
+my ( $baseurl, $m ) = RT::Test->started_ok;
+ok( $m->login, 'Log in' );
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+
+diag "Default PriorityAsString";
+
+$m->goto_create_ticket( $queue->Id );
+my $form = $m->form_name('TicketCreate');
+
+for my $field (qw/InitialPriority FinalPriority/) {
+    my $priority_input = $form->find_input($field);
+    is( $priority_input->type, 'option', "$field input is a select" );
+    is_deeply( [ $priority_input->possible_values ], [ '', 0, 50, 100 ], "$field options" );
+    is( $form->value($field), '', "$field default value" );
+}
+
+$m->submit_form_ok( { fields => { Subject => 'Test PriorityAsString', InitialPriority => 50 } }, 'Create ticket' );
+$m->text_like( qr{Priority:\s*Medium/Low}, 'Priority/FinalPriority on display' );
+
+$m->follow_link_ok( { text => 'Basics' } );
+$form = $m->form_name('TicketModify');
+
+for my $field (qw/Priority FinalPriority/) {
+    my $priority_input = $form->find_input($field);
+    is( $priority_input->type, 'option', "$field input is a select" );
+    is_deeply( [ $priority_input->possible_values ], [ 0, 50, 100 ], "$field options" );
+    is( $form->value($field), $field eq 'Priority' ? 50 : 0, "$field default value" );
+}
+
+$m->submit_form_ok( { fields => { Priority => 100, FinalPriority => 100 } }, 'Update Priority' );
+$m->text_contains( qq{Priority changed from 'Medium' to 'High'},   'Priority is updated' );
+$m->text_contains( qq{FinalPriority changed from 'Low' to 'High'}, 'FinalPriority is updated' );
+
+diag "Disable PriorityAsString";
+
+my $config = RT::Configuration->new( RT->SystemUser );
+my ( $ret, $msg ) = $config->Create( Name => 'EnablePriorityAsString', Content => 0 );
+ok( $ret, 'Updated config' );
+
+$m->goto_create_ticket( $queue->Id );
+$form = $m->form_name('TicketCreate');
+for my $field (qw/InitialPriority FinalPriority/) {
+    my $priority_input = $form->find_input($field);
+    is( $priority_input->type, 'text', "$field input is a text" );
+    is( $form->value($field),  '',     "$field default value" );
+}
+
+$config->Load('EnablePriorityAsString');
+( $ret, $msg ) = $config->SetContent(1);
+ok( $ret, 'Updated config' );
+
+diag "Set PriorityAsString config of General to a hashref";
+
+# config cache refreshes at most once in one second, so wait a bit.
+sleep 1;
+
+$config = RT::Configuration->new( RT->SystemUser );
+( $ret, $msg ) = $config->Create(
+    Name    => 'PriorityAsString',
+    Content => {
+        Default => { Low     => 0, Medium => 50, High   => 100 },
+        General => { VeryLow => 0, Low    => 20, Medium => 50, High => 100, VeryHigh => 200 },
+    },
+);
+ok( $ret, 'Updated config' );
+
+$m->goto_create_ticket( $queue->Id );
+$form = $m->form_name('TicketCreate');
+for my $field (qw/InitialPriority FinalPriority/) {
+    my $priority_input = $form->find_input($field);
+    is( $priority_input->type, 'option', "$field input is a select" );
+    is_deeply( [ $priority_input->possible_values ], [ '', 0, 20, 50, 100, 200 ], "$field options" );
+    is( $form->value($field), '', "$field default value" );
+}
+
+diag "Disable PriorityAsString for General";
+
+sleep 1;
+( $ret, $msg ) = $config->SetContent(
+    {   Default => { Low => 0, Medium => 50, High => 100 },
+        General => 0,
+    }
+);
+ok( $ret, 'Updated config' );
+$m->goto_create_ticket( $queue->Id );
+$form = $m->form_name('TicketCreate');
+for my $field (qw/InitialPriority FinalPriority/) {
+    my $priority_input = $form->find_input($field);
+    is( $priority_input->type, 'text', "$field input is a text" );
+    is( $form->value($field),  '',     "$field default value" );
+}
+
+diag "Set PriorityAsString config of General to an arrayref";
+
+sleep 1;
+( $ret, $msg ) = $config->SetContent(
+    {   Default => { Low    => 0,  Medium  => 50, High => 100 },
+        General => [ Medium => 50, VeryLow => 0,  Low  => 20, High => 100, VeryHigh => 200 ],
+    }
+);
+ok( $ret, 'Updated config' );
+
+$m->goto_create_ticket( $queue->Id );
+$form = $m->form_name('TicketCreate');
+for my $field (qw/InitialPriority FinalPriority/) {
+    my $priority_input = $form->find_input($field);
+    is( $priority_input->type, 'option', "$field input is a select" );
+    is_deeply( [ $priority_input->possible_values ], [ '', 50, 0, 20, 100, 200 ], "$field options" );
+    is( $form->value($field), '', "$field default value" );
+}
+
+diag "Queue default values";
+
+$m->get_ok('/Admin/Queues/DefaultValues.html?id=1');
+$form = $m->form_name('ModifyDefaultValues');
+for my $field (qw/InitialPriority FinalPriority/) {
+    my $priority_input = $form->find_input($field);
+    is( $priority_input->type, 'option', 'Priority input is a select' );
+    is_deeply( [ $priority_input->possible_values ], [ '', 50, 0, 20, 100, 200 ], 'Priority options' );
+}
+$m->submit_form_ok( { fields => { InitialPriority => 50, FinalPriority => 100 }, button => 'Update' },
+    'Update default values' );
+$m->text_contains( 'Default value of InitialPriority changed from (no value) to Medium', 'InitialPriority is updated' );
+$m->text_contains( 'Default value of FinalPriority changed from (no value) to High',     'FinalPriority is updated' );
+
+$m->goto_create_ticket( $queue->Id );
+$form = $m->form_name('TicketCreate');
+is( $form->value('InitialPriority'), 50,  'InitialPriority default value' );
+is( $form->value('FinalPriority'),   100, 'FinalPriority default value' );
+
+diag "Search builder";
+
+$m->follow_link_ok( { text => 'Tickets' }, 'Ticket search builder' );
+$form = $m->form_name('BuildQuery');
+my $priority_input = $form->find_input('ValueOfPriority');
+is( $priority_input->type, 'text', "ValueOfPriority input is a text" );
+
+$m->submit_form_ok( { fields => { ValueOfQueue => 'General' }, button => 'AddClause' }, 'Limit queue' );
+$form           = $m->form_name('BuildQuery');
+$priority_input = $form->find_input('ValueOfPriority');
+is( $priority_input->type, 'option', "ValueOfPriority input is a select" );
+is_deeply(
+    [ $priority_input->possible_values ],
+    [ '', 'Medium', 'VeryLow', 'Low', 'High', 'VeryHigh' ],
+    "ValueOfPriority options"
+);
+
+$m->submit_form_ok( { fields => { PriorityOp => '=', ValueOfPriority => 'High' }, button => 'DoSearch' },
+    'Limit priority' );
+$m->title_is('Found 1 ticket');
+$m->text_contains('Test PriorityAsString');
+
+$m->follow_link_ok( { text => 'Advanced' } );
+$m->submit_form_ok(
+    { form_name => 'BuildQueryAdvanced', fields => { Query => qq{Queue = 'General' AND Priority = 'Low'} } },
+    'Search tickets with LowPriority' );
+$m->submit_form_ok( { form_name => 'BuildQuery', button => 'DoSearch' } );
+$m->title_is('Found 0 tickets');
+
+$m->follow_link_ok( { text => 'Transactions' }, 'Transaction search builder' );
+$form           = $m->form_name('BuildQuery');
+$priority_input = $form->find_input('ValueOfTicketPriority');
+is( $priority_input->type, 'text', "ValueOfTicketPriority input is a text" );
+
+$m->submit_form_ok( { fields => { ValueOfTicketQueue => 'General' }, button => 'AddClause' }, 'Limit queue' );
+$form           = $m->form_name('BuildQuery');
+$priority_input = $form->find_input('ValueOfTicketPriority');
+is( $priority_input->type, 'option', "ValueOfTicketPriority input is a select" );
+is_deeply(
+    [ $priority_input->possible_values ],
+    [ '', 'Medium', 'VeryLow', 'Low', 'High', 'VeryHigh' ],
+    "ValueOfTicketPriority options"
+);
+
+$m->submit_form_ok( { fields => { TicketPriorityOp => '=', ValueOfTicketPriority => 'High' }, button => 'DoSearch' },
+    'Limit priority' );
+$m->title_is('Found 4 transactions');
+$m->text_contains('Test PriorityAsString');
+
+done_testing;

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


More information about the rt-commit mailing list