[Rt-commit] rt branch, 4.6/row-colors-by-condition, created. rt-4.4.3-86-gc05339d463

Craig Kaiser craig at bestpractical.com
Wed Jan 2 17:21:11 EST 2019


The branch, 4.6/row-colors-by-condition has been created
        at  c05339d463752786bfab1baeece5e291540fcee2 (commit)

- Log -----------------------------------------------------------------
commit 81390e43fa402b9faf31807ed76810461decf3ed
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Fri Sep 7 08:44:33 2018 -0400

    Create ReplaceAttachments, ReplaceHeader and ReplaceContent Methods
    
    Quickly search on the attachments table and remove or replace matching
    data found.

diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
index 4c544301c7..5708e31428 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -743,6 +743,62 @@ sub SetHeader {
     $self->__Set( Field => 'Headers', Value => $newheader);
 }
 
+=head2 ReplaceHeaders ( Search => 'SEARCH', Replacement => 'Replacement' )
+
+Search the attachments table's Header column for the search string provided.
+When a match is found call the SetHeader() method on the header with the match,
+either set the header to empty or a replacement value.
+
+=cut
+
+sub ReplaceHeaders {
+    my $self = shift;
+    my %args = (
+        Search      => undef,
+        Replacement => '',
+        @_,
+    );
+
+    return ( 0, $self->loc('No Search string provided') ) unless $args{Search};
+
+    foreach my $header ( $self->SplitHeaders ) {
+        my ( $tag, $value ) = split /:/, $header, 2;
+        if ( $value =~ s/\Q$args{Search}\E/$args{Replacement}/ig ) {
+            my $ret = $self->SetHeader( $tag, $value );
+            RT::Logger->error("Could not set header: $tag to $value") unless $ret;
+        }
+    }
+    return ( 1, $self->loc('Headers cleared') );
+}
+
+=head2 ReplaceContent ( Search => 'SEARCH', Replacement => 'Replacement' )
+
+Search the attachments table's Content column for the search string provided.
+When a match is found either replace it with the provided replacement string or an
+empty string.
+
+=cut
+
+sub ReplaceContent {
+    my $self = shift;
+    my %args = (
+        Search      => undef,
+        Replacement => '',
+        @_,
+    );
+
+    return ( 0, $self->loc('No search string provided') ) unless $args{Search};
+
+    my $content = $self->Content;
+
+    if ( $content && $content =~ s/\Q$args{Search}\E/$args{Replacement}/ig ) {
+        my ( $ret, $msg ) = $self->_Set( Field => 'Content', Value => $content );
+        return ( $ret, 'Content replaced' );
+    }
+    return ( 1, $self->loc('No content matches found') );
+}
+
+
 sub _CanonicalizeHeaderValue {
     my $self  = shift;
     my $value = shift;
diff --git a/lib/RT/Attachments.pm b/lib/RT/Attachments.pm
index 8b06088678..a4b070e2cd 100644
--- a/lib/RT/Attachments.pm
+++ b/lib/RT/Attachments.pm
@@ -252,6 +252,81 @@ sub AddRecord {
     return $self->SUPER::AddRecord( $record );
 }
 
+=head2 ReplaceAttachments ( Search => 'SEARCH', Replacement => 'Replacement', Header => 1, Content => 1 )
+
+Provide a search string to search the attachments table for, by default the Headers and Content
+columns will both be searched for matches.
+
+=cut
+
+sub ReplaceAttachments {
+    my $self = shift;
+    my %args = (
+        Search      => undef,
+        Replacement => '',
+        Headers     => 1,
+        Content     => 1,
+        @_,
+    );
+
+    return ( 0, $self->loc('Provide a search string to search on') ) unless $args{Search};
+
+    $self->Limit(
+        ENTRYAGGREGATOR => 'OR',
+        FIELD           => 'Headers',
+        OPERATOR        => 'LIKE',
+        VALUE           => $args{Search},
+        SUBCLAUSE       => 'Attachments',
+    ) if $args{Headers};
+    $self->Limit(
+        ENTRYAGGREGATOR => 'OR',
+        FIELD           => 'Content',
+        OPERATOR        => 'LIKE',
+        VALUE           => $args{Search},
+        SUBCLAUSE       => 'Attachments',
+    ) if $args{Content};
+    $self->Limit(
+        FIELD           => 'ContentType',
+        OPERATOR        => 'IN',
+        VALUE           => ['text/plain', 'text/html'],
+    );
+    $self->Limit(
+        FIELD           => 'ContentEncoding',
+        VALUE           => 'none',
+    );
+
+    my %tickets;
+    my ($ret, $msg);
+    while (my $attachment = $self->Next) {
+        my $content_replaced;
+        if ( $args{Headers} ) {
+            ($ret, $msg) = $attachment->ReplaceHeaders(Search => $args{Search}, Replacement => $args{Replacement});
+            $content_replaced ||= $ret;
+
+            RT::Logger->error($msg) unless $ret;
+        }
+
+        if ( $args{Content} ) {
+            ($ret, $msg) = $attachment->ReplaceContent(Search => $args{Search}, Replacement => $args{Replacement});
+            $content_replaced ||= $ret;
+
+            RT::Logger->error($msg) unless $ret;
+        }
+
+        my $ticket = $attachment->TransactionObj->TicketObj;
+        $tickets{$ticket->Id} ||= $ticket if $content_replaced;
+    }
+
+    foreach my $id ( sort { $a <=> $b } keys %tickets ) {
+        (my $transaction, $msg, my $trans) = $tickets{$id}->_NewTransaction (
+            Type     => "Munge",
+        );
+        RT::Logger->error($msg) unless $transaction;
+    }
+    my $count = scalar keys %tickets;
+    return ( 1, $self->loc( "Updated [_1] ticket's attachment content", $count ) );
+}
+
 RT::Base->_ImportOverlays();
 
 1;
diff --git a/lib/RT/Transaction.pm b/lib/RT/Transaction.pm
index 9f897c307d..c88f43f9d6 100644
--- a/lib/RT/Transaction.pm
+++ b/lib/RT/Transaction.pm
@@ -1373,6 +1373,10 @@ sub _CanonicalizeRoleName {
         $principal->Load($self->Field);
         return ("Removed from group '[_1]'", $principal->Object->Name); #loc()
     },
+    Munge => sub {
+        my $self = shift;
+        return "Attachment content modified";
+    },
 );
 
 

commit 5bec6babaebd076cc059c63287667c1fd7269caf
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Fri Sep 7 08:45:39 2018 -0400

    Create rt-munge-attachments executable

diff --git a/.gitignore b/.gitignore
index 1159ee89cc..ff1a7110d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,6 +46,7 @@
 /sbin/rt-ldapimport
 /sbin/rt-passwd
 /sbin/standalone_httpd
+/sbin/rt-munge-attachments
 /var/mason_data/
 /autom4te.cache/
 /configure
diff --git a/Makefile.in b/Makefile.in
index cc418241f0..e704069278 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -139,6 +139,7 @@ BINARIES		=	$(RT_MAILGATE_BIN) \
 				$(RT_CRON_BIN)
 
 SYSTEM_BINARIES		=	rt-attributes-viewer \
+				rt-munge-attachments \
 				rt-clean-sessions \
 				rt-dump-metadata \
 				rt-email-dashboards \
diff --git a/configure.ac b/configure.ac
index d7685d80d1..4c6e34b6aa 100755
--- a/configure.ac
+++ b/configure.ac
@@ -486,6 +486,7 @@ AC_CONFIG_FILES([
                  sbin/rt-serializer
                  sbin/rt-importer
                  sbin/rt-passwd
+                 sbin/rt-munge-attachments
                  bin/rt-crontool
                  bin/rt-mailgate
                  bin/rt],
diff --git a/sbin/rt-munge-attachments.in b/sbin/rt-munge-attachments.in
new file mode 100644
index 0000000000..2a3eec9692
--- /dev/null
+++ b/sbin/rt-munge-attachments.in
@@ -0,0 +1,123 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2018 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+use strict;
+use warnings;
+
+# fix lib paths, some may be relative
+BEGIN {    # BEGIN RT CMD BOILERPLATE
+    require File::Spec;
+    require Cwd;
+    my @libs = ( "@RT_LIB_PATH@", "@LOCAL_LIB_PATH@" );
+    my $bin_path;
+
+    for my $lib (@libs) {
+        unless ( File::Spec->file_name_is_absolute($lib) ) {
+            $bin_path
+                ||= ( File::Spec->splitpath( Cwd::abs_path(__FILE__) ) )[1];
+            $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+        }
+        unshift @INC, $lib;
+    }
+
+}
+
+# Read in the options
+my %opts;
+use Getopt::Long;
+GetOptions( \%opts, "help|h", "search=s", "replacement=s", );
+
+if ( $opts{'help'} || !$opts{'search'} ) {
+    require Pod::Usage;
+    print Pod::Usage::pod2usage( -verbose => 2 );
+    exit;
+}
+
+use RT -init;
+
+my $replacement = $opts{'replacement'} || '';
+
+my $search = $opts{'search'};
+
+my $attachments = RT::Attachments->new( RT->SystemUser );
+my ($ret, $msg) = $attachments->ReplaceAttachments(Search => $search, Replacement => $replacement);
+
+print STDERR $msg . "\n";
+
+
+=head1 rt-munge-attachments
+
+rt-munge-attachments - Remove or replace string from attachments table.
+
+=head1 SYNOPSIS
+
+    rt-munge-attachments --search="user1 at example.com" --replace="Ex-Employee"
+
+=cut
+
+=head1 DESCRIPTION
+
+When a match is found in the Headers column, the header is deleted unless a replacement
+value was provided. If a match is found in the Content column then the matching substring
+will be replaced with a blank or provided value.
+
+=head1 OPTIONS
+
+=over 2
+
+=item --search=SEARCH
+
+Provide a string to search the header and content columns in the attachments table, if a match
+is found the content will be removed.
+
+=item --replace=REPLACEMENT
+
+Provide a string to replace strings matching the search string for the
+attachments table.
+
+=back

commit 765817052ed41f8b69f6017137b86f136bb5450e
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Fri Sep 7 08:45:59 2018 -0400

    Add tests for methods that munge the attachments table

diff --git a/t/api/attachment.t b/t/api/attachment.t
index 52e3c3f162..25d736b8bf 100644
--- a/t/api/attachment.t
+++ b/t/api/attachment.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 use RT;
-use RT::Test tests => 7;
+use RT::Test tests => undef;
 
 
 {
@@ -64,3 +64,95 @@ is ($#headers, 2, "testing a bunch of singline multiple headers" );
         'body of ContentAsMIME is original'
     );
 }
+
+diag 'Test clearing and replacing header and content in attachments table';
+{
+    my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+    ok $queue && $queue->id, 'loaded or created queue';
+
+    my $t = RT::Test->create_ticket( Queue => 'General', Subject => 'test' );
+    ok $t && $t->id, 'created a ticket';
+
+    $t->Comment( Content => 'test' );
+
+    my $attachments = RT::Attachments->new(RT->SystemUser);
+    $attachments->Limit(
+        FIELD           => 'Content',
+        OPERATOR        => 'LIKE',
+        VALUE           => 'test',
+    );
+    is $attachments->Count, 1, 'Found content with "test"';
+
+    # Replace attachment value for 'test' in Conetent col
+    my ($ret, $msg) = $attachments->ReplaceAttachments(Search => 'test', Replacement => 'new_value', Headers => 0);
+    ok $ret, $msg;
+
+    $attachments->CleanSlate;
+
+    $attachments->Limit(
+        FIELD           => 'Content',
+        OPERATOR        => 'LIKE',
+        VALUE           => 'test',
+    );
+    is $attachments->Count, 0, 'Found no content with "test"';
+
+    $attachments->Limit(
+        FIELD           => 'Content',
+        OPERATOR        => 'LIKE',
+        VALUE           => 'new_value',
+    );
+    is $attachments->Count, 1, 'Found content with "new_value"';
+
+    $attachments->CleanSlate;
+
+    $attachments->Limit(
+        FIELD           => 'Headers',
+        OPERATOR        => 'LIKE',
+        VALUE           => 'API',
+    );
+    is $attachments->Count, 1, 'Found header with content "API"';
+
+    # Replace attachment value for 'API' in Header col
+    ($ret, $msg) = $attachments->ReplaceAttachments(Search => 'API', Replacement => 'replacement', Content => 0);
+    ok $ret, $msg;
+    $attachments->CleanSlate;
+
+    $attachments->Limit(
+        FIELD           => 'Headers',
+        OPERATOR        => 'LIKE',
+        VALUE           => 'API',
+    );
+    is $attachments->Count, 0, 'Found no header with content "API"';
+    $attachments->CleanSlate;
+
+    $attachments->Limit(
+        FIELD           => 'Headers',
+        OPERATOR        => 'LIKE',
+        VALUE           => 'replacement',
+    );
+    is $attachments->Count, 1, 'Found header with content "replacement"';
+
+    ($ret, $msg) = $attachments->ReplaceAttachments(Search => 'new_value', Replacement => 'replacement', Content => 0);
+    ok $ret, $msg;
+
+    $attachments->CleanSlate;
+    $attachments->Limit(
+        FIELD           => 'Content',
+        OPERATOR        => 'LIKE',
+        VALUE           => 'new_value',
+    );
+    is $attachments->Count, 1, 'Content is not changed when flagged as false';
+
+    ($ret, $msg) = $attachments->ReplaceAttachments(Search => 'replacement', Replacement => 'new_value', Headers => 0);
+    ok $ret, $msg;
+
+    $attachments->CleanSlate;
+    $attachments->Limit(
+        FIELD           => 'Headers',
+        OPERATOR        => 'LIKE',
+        VALUE           => 'replacement',
+    );
+    is $attachments->Count, 1, 'Headers are not replaced when flagged as false';
+}
+
+done_testing();

commit 165a320c044e526c6efec19661727c8f5fbbae5a
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Thu Dec 20 08:35:09 2018 -0500

    Inital commit

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 17d912e698..db060a5f31 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -4127,4 +4127,25 @@ Set($HideOneTimeSuggestions, 0);
 
 =cut
 
+=head2 HighLightOnCondition
+
+Allow collections of RT objects to be highlighted a color based on conditions. This can be
+configured for any object and any attribute through the RT_SiteConfig.pm option "HighLightOnCondition",
+or color can be set per user based on ticket status under preferences.
+
+You can use "__CurrentUser__" under the Value key to have the attribute checked against the current user.
+Names of attributes should conincide with the method names for the object collection being targeted.
+
+The color condition that is appears first will have the prioirty if multiple conditions are met.
+
+ Set(@HighLightOnCondition,
+        { "Attribute" => "Status", "Value" => "open", "Color" => "green" },
+        { "Attribute" => "Owner", "Value" => "__CurrentUser__", "Color" => "red" },
+        { "Attribute" => "id", "Value" => "__CurrentUser__", "Color" => "blue" } #highlight user collections
+  );
+
+=cut
+
+Set(@HighLightOnCondition, undef );
+
 1;
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 17d63b548b..59bd3ee713 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -1222,6 +1222,14 @@ our %META;
     ServiceAgreements => {
         Type => 'HASH',
     },
+    HighLightOnCondition => {
+        Section         => 'User Color Preferences', #loc
+        Overridable     => 1,
+        Widget          => '/Widgets/Form/Criteria',
+        WidgetArguments => {
+            Description => 'Select conditions for row coloring' # loc
+        }
+    },
 );
 my %OPTIONS = ();
 my @LOADED_CONFIGS = ();
@@ -1522,6 +1530,7 @@ sub Get {
     }
     $res = $OPTIONS{$name}           unless defined $res;
     $res = $META{$name}->{'Default'} unless defined $res;
+
     return $self->_ReturnValue( $res, $META{$name}->{'Type'} || 'SCALAR' );
 }
 
diff --git a/lib/RT/Record.pm b/lib/RT/Record.pm
index 908cde342a..6e64c903fe 100644
--- a/lib/RT/Record.pm
+++ b/lib/RT/Record.pm
@@ -2695,6 +2695,41 @@ sub __DependsOn
     return;
 }
 
+=head2 HighLightOnCondition
+
+Add css class to collection row if user prefs criteria are met.
+
+=cut
+
+sub HighLightOnCondition {
+    my $self = shift;
+
+    my $user = RT::User->new($self->CurrentUser);
+    my ($ret, $msg) = $user->Load($self->CurrentUser);
+    RT::Logger->error($msg) unless $ret;
+
+    my $color_config = RT::Config->Get('HighLightOnCondition', $user);
+    foreach my $condition (@{$color_config}) {
+        my ($attr, $value) = ( $condition->{Attribute}, $condition->{Value} );
+        next unless $attr and $value;
+
+        if ( !$self->_CoreAccessible->{$attr} ) {
+            next;
+        } else {
+            if ( $attr eq 'Queue' ) {
+                my $queue = RT::Queue->new($self->CurrentUser);
+                $queue->Load($value);
+                return ["row-bg-color-". $condition->{Color}] if $self->QueueObj->Name eq $queue->Name;
+            } elsif ( $value eq '__CurrentUser__' ) {
+                return ["row-bg-color-". $condition->{Color}] if $self->$attr eq $self->CurrentUser->Id;
+            }
+            elsif ( $self->$attr eq $value ) {
+                return ["row-bg-color-". $condition->{Color}];
+            }
+        }
+    }
+}
+
 # implement proxy method because some RT classes
 # override Delete method
 sub __Wipeout
diff --git a/share/html/Elements/CollectionList b/share/html/Elements/CollectionList
index a33b8e0fd9..ac9bb072cd 100644
--- a/share/html/Elements/CollectionList
+++ b/share/html/Elements/CollectionList
@@ -145,6 +145,11 @@ while ( my $record = $Collection->Next ) {
     my $warning = 0;
     my $Classes = '';
 
+    if ( $record->can('HighLightOnCondition') ) {
+        my $row_coloring = $record->HighLightOnCondition();
+        $Classes = join(' ', @{$row_coloring}) if $row_coloring;
+    }
+
     $m->callback(
         CallbackName => 'EachRow',
         Record       => $record,
diff --git a/share/html/Elements/SelectColor b/share/html/Elements/SelectColor
new file mode 100644
index 0000000000..9f33f305c7
--- /dev/null
+++ b/share/html/Elements/SelectColor
@@ -0,0 +1,64 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2018 Best Practical Solutions, LLC
+%#                                          <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<select name="<% $Name %>">
+    <option value=""<% !$Default && qq[ selected="selected"] |n %>><&|/l&>(no value)</&></option>
+%   foreach my $color (@Colors) {
+    <option value="<% $color %>"
+%      if ($Default && $Default eq $color) {
+            selected="selected"
+%      }
+    ><% $color %>
+    </option>
+%   }
+</select>
+
+<%ARGS>
+$Name    => ''
+$Default => ''
+ at Colors  => qw/blue green yellow purple grey red/
+</%ARGS>
diff --git a/share/html/Widgets/Form/Criteria b/share/html/Widgets/Form/Criteria
new file mode 100644
index 0000000000..2e588e18b2
--- /dev/null
+++ b/share/html/Widgets/Form/Criteria
@@ -0,0 +1,118 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2018 Best Practical Solutions, LLC
+%#                                          <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+<div>
+% my $i = 0;
+% foreach my $color (@colors) {
+    <div>
+        <select name="Color-Argument" class="cascade-by-optgroup">
+            <optgroup label="Status">
+%               if ( $ARGS{'CurrentValue'}->[$i]->{Attribute} ) {
+                    <option selected="selected" value="<% $ARGS{'CurrentValue'}->[$i]->{Attribute} %>"><% $ARGS{'CurrentValue'}->[$i]->{Value} %></option>
+%               }
+                <option value="<&|/l&>(no value)</&>"<% !$ARGS{'CurrentValue'}->[$i]->{Value} && qq[ selected="selected"] |n %>><&|/l&>(no value)</&></option>
+%               foreach my $status (@statuses) {
+                    <option value="<% $status %>"><% $status %></option>
+%               }
+            </optgroup>
+        </select>
+        <& /Elements/SelectColor, Name  => "Color", Default => $ARGS{'CurrentValue'}->[$i]->{Color} &>
+    </div>
+% $i++;
+% }
+</div>
+<%INIT>
+my @colors = qw/blue green yellow purple grey red/;
+
+my @lifecycles = RT::Lifecycle->new($session{CurrentUser})->ListAll;
+my @lifecycle_objs;
+
+my $LoadLifecycle = sub {
+    my $lifecycle_name = shift;
+
+    my $lifecycle = RT::Lifecycle->new($session{CurrentUser});
+    $lifecycle->Load($lifecycle_name);
+
+    return $lifecycle;
+};
+
+map { push @lifecycle_objs, $LoadLifecycle->($_) unless $_ eq 'approvals' } @lifecycles;
+
+my @statuses;
+foreach my $lifecycle (@lifecycle_objs) {
+    my @temp_statuses = $lifecycle->Valid;
+    push @statuses, @temp_statuses;
+}
+</%INIT>
+
+<%METHOD Process>
+<%INIT>
+my %args = %{$ARGS{Arguments}};
+
+my @attributes = ref $args{'Color-Argument-Groups'} eq "ARRAY" 
+    ? @{$args{'Color-Argument-Groups'}}
+    : ($args{'Color-Argument-Groups'});
+
+my @colors = ref $args{'Color'} eq "ARRAY" ? @{$args{'Color'}} : ($args{'Color'});
+my @color_arguments = ref $args{'Color-Argument'} eq 'ARRAY' ? @{$args{'Color-Argument'}}
+    : ($args{'Color-Argument'});
+
+my @HighLightOnCondition;
+my $i = 0;
+foreach my $color_argument (@color_arguments) {
+    my $value     = $color_argument;
+    my $attribute = $attributes[$i];
+    my $color     = $colors[$i];
+
+    next unless $value and $attribute and $color;
+    push @HighLightOnCondition, { Attribute => $attribute, Value => $value, Color => $color };
+    $i++;
+}
+
+return scalar @HighLightOnCondition ? \@HighLightOnCondition : undef;
+</%INIT>
+</%METHOD>
diff --git a/share/static/css/base/highlight.css b/share/static/css/base/highlight.css
new file mode 100644
index 0000000000..6bf90a03dc
--- /dev/null
+++ b/share/static/css/base/highlight.css
@@ -0,0 +1,48 @@
+/* unset even and odd line colors. */
+tr.rowhighlight {background-color:#f0f8ff;margin:0;border:0;padding:0;spacing:0 !important; } 
+
+/* bg colors from highlight. */
+tr.row-bg-color-red td {
+ background-color: rgba(255, 85, 102, 0.1) !important
+}
+
+tr.row-bg-color-blue td {
+ background-color: rgba(0, 149, 191, 0.1) !important
+}
+
+tr.row-bg-color-green td {
+ background-color: rgba(68, 187, 119, 0.1) !important
+}
+
+tr.row-bg-color-yellow td {
+ background-color: rgba(255, 170, 68, 0.1) !important
+}
+
+tr.row-bg-color-purple td {
+ background-color: rgba(170, 68, 255, 0.1) !important
+}
+
+tr.row-bg-color-grey td {
+ background-color: rgba(153, 153, 153, 0.1) !important
+}
+
+/* Status Colours */
+tr.statusnew td {
+ background-color: rgba(255, 85, 102, 0.1) !important
+}
+
+tr.statusopen td {
+ background-color: rgba(255, 170, 68, 0.1) !important
+}
+
+tr.statusstalled td {
+ background-color: rgba(170, 68, 255, 0.1) !important
+}
+
+tr.statusresolved td {
+ background-color: rgba(68, 187, 119, 0.1) !important
+}
+
+tr.statusrejected td {
+ background-color: rgba(153, 153, 153, 0.1) !important
+}
diff --git a/share/static/css/base/main.css b/share/static/css/base/main.css
index 7cc29066bf..3e4ef4ea4c 100644
--- a/share/static/css/base/main.css
+++ b/share/static/css/base/main.css
@@ -1,3 +1,4 @@
+ at import "highlight.css";
 @import "yui-fonts.css";
 @import "jquery-ui.css";
 @import "jquery-ui-timepicker-addon.css";
diff --git a/share/static/css/base/misc.css b/share/static/css/base/misc.css
index 25ec20c699..91bb95d56d 100644
--- a/share/static/css/base/misc.css
+++ b/share/static/css/base/misc.css
@@ -127,3 +127,24 @@ td.current-recipients {
     vertical-align: top;
     padding-left: 50px;
 }
+
+.inline-row {
+    margin-bottom: 10px;
+    display: inline-flex;
+    width: 100%;
+}
+
+div.inline-row div {
+    width: 175px;
+    display: block;
+}
+
+div.inline-row a {
+    text-align: center;
+    width: 85%;
+}
+
+div.inline-row i {
+    text-align: left;
+    width: 85%;
+}
diff --git a/share/static/js/cascaded.js b/share/static/js/cascaded.js
index 9f625867be..3ece97b8fa 100644
--- a/share/static/js/cascaded.js
+++ b/share/static/js/cascaded.js
@@ -33,6 +33,9 @@ function filter_cascade_by_id (id, vals) {
 }
 
 function filter_cascade_select (select, complete_select, vals) {
+    console.log(select);
+    console.log(complete_select);
+    console.log(vals);
     if ( !( vals instanceof Array ) ) {
         vals = [vals];
     }
diff --git a/share/static/js/event-registration.js b/share/static/js/event-registration.js
index eaacbdb570..2de4c15811 100644
--- a/share/static/js/event-registration.js
+++ b/share/static/js/event-registration.js
@@ -45,6 +45,8 @@ jQuery(ReplaceUserReferences);
 jQuery(function() {
     jQuery("select.cascade-by-optgroup").each(function(){
         var name = this.name;
+        console.log('here');
+        console.log(name);
         if (!name) return;
 
         // Generate elements for cascading based on the master <select> ...

commit c05339d463752786bfab1baeece5e291540fcee2
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Wed Jan 2 15:52:48 2019 -0500

    Create tests for row colors by condition

diff --git a/t/web/css.t b/t/web/css.t
index f2c1124832..c712cba035 100644
--- a/t/web/css.t
+++ b/t/web/css.t
@@ -2,7 +2,12 @@ use utf8;
 use strict;
 use warnings;
 
-use RT::Test tests => undef;
+use RT::Test tests => undef, config =>
+    'Set(@HighLightOnCondition,
+            { "Attribute" => "Owner", "Value" => "__CurrentUser__", "Color" => "red" },
+            { "Attribute" => "Status", "Value" => "open", "Color" => "blue" },
+            { "Attribute" => "Queue", "Value" => "General", "Color" => "purple" }
+    );';
 
 # Each custom field must have a corresponding class selector with invalid characters escaped
 {
@@ -34,6 +39,60 @@ use RT::Test tests => undef;
     my $res = $m->get( $baseurl . '/Ticket/Display.html?id=1' );
     my $element = $m->dom->at( ".custom-field-$cf_id" );
     like( $element->attr( 'class' ), qr/test-class-م-例-name/, 'Class selector added to custom field, invalid characters have been escaped' );
+
+    # Test highlighting rows based on a condition
+    my $ticket = RT::Ticket->new(RT->SystemUser);
+    my ($id, $tid, $msg) = $ticket->Create(Queue => 'General', Subject => 'test');
+    ok($id, $msg);
+
+    # Test our global level color configs
+    $m->get_ok( $baseurl );
+    $element = $m->dom->at('tr[class*="row-bg-color-purple"]');
+    like( $element->attr( 'class' ), qr/row-bg-color-purple/, 'Successfully apply row color class by global condition for status new');
+
+    $element = $m->dom->at('tr[class*="row-bg-color-blue"]' );
+    is ( $element, undef, "Did not apply open status color" );
+    $element = $m->dom->at('tr[class*="row-bg-color-red"]');
+    is ( $element, undef, "Did not apply CurrentUser owner status color" );
+
+    ($id, $msg) = $ticket->SetOwner( 'root' );
+    ok($id, $msg);
+
+    ok( $m->login('root', 'password'), 'logged in' );
+    $m->get_ok( $baseurl );
+
+    $element = $m->dom->at('tr[class*="row-bg-color-red"]');
+    like( $element->attr( 'class' ), qr/row-bg-color-red/, 'Successfully apply row color class by global condition for Current User');
+
+    ($id, $msg) = $ticket->SetStatus('open');
+    ok($id, $msg);
+
+    ($id, $msg) = $ticket->SetOwner( 'NoBody' );
+    ok($id, $msg);
+
+    $m->get_ok( $baseurl );
+
+    $element = $m->dom->at('tr[class*="row-bg-color-blue"]');
+    like( $element->attr( 'class' ), qr/row-bg-color-blue/, 'Successfully apply row color class by global condition for status open');
+
+    # Test overriding global row highlight options with user preferences
+    $m->get_ok( $baseurl . '/Prefs/Other.html' );
+    $m->submit_form_ok( {
+        form_name => 'ModifyPreferences',
+        fields    => {
+            "Color-Argument"        => 'open',
+            "Color-Argument-Groups" => 'Status',
+            "Color"                 => 'green',
+        },
+        button => 'Update',
+    }, 'Set user prefs for color row by condition' );
+    $m->get_ok( $baseurl );
+
+    $element = $m->dom->at('tr[class*="row-bg-color-green"]');
+    like( $element->attr( 'class' ), qr/row-bg-color-green/, 'Successfully apply row color class by user prefs for status open');
+
+    $element = $m->dom->at('tr[class*="row-bg-color-blue"]');
+    is($element, undef, 'Successfully overode global color settings')
 }
 
 done_testing();

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


More information about the rt-commit mailing list