[Rt-commit] rt branch 5.0/smaller-filter-icon created. rt-5.0.3-347-g75f6dda557

BPS Git Server git at git.bestpractical.com
Thu Mar 30 21:43:06 UTC 2023


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt".

The branch, 5.0/smaller-filter-icon has been created
        at  75f6dda557202d30f81ebba48596f60c91f65ca2 (commit)

- Log -----------------------------------------------------------------
commit 75f6dda557202d30f81ebba48596f60c91f65ca2
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Thu Mar 30 13:51:25 2023 -0400

    Show solid funnel if the header is a filter in the search
    
    This gives a visual of which columns are being used to
    filter the current search.

diff --git a/share/html/Elements/CollectionAsTable/Header b/share/html/Elements/CollectionAsTable/Header
index 3fecec469e..b1fff04a24 100644
--- a/share/html/Elements/CollectionAsTable/Header
+++ b/share/html/Elements/CollectionAsTable/Header
@@ -233,8 +233,9 @@ foreach my $col ( @Format ) {
 
         if ( $field ) {
             my $tooltip = loc( 'Filter on [_1]', loc($field) );
+            my $icon = exists $filter_data{filter}{$field} ? 'funnel-fill' : 'funnel';
             $m->out(
-                qq{ <a href="javascript:void(0)" class="btn btn-primary button search-filter"><img src="/static/images/funnel.svg" role="img" data-toggle="tooltip" data-placement="bottom" data-original-title="$tooltip"></span></a>}
+                qq{ <a href="javascript:void(0)" class="btn btn-primary button search-filter"><img src="/static/images/$icon.svg" role="img" data-toggle="tooltip" data-placement="bottom" data-original-title="$tooltip"></span></a>}
             );
             $m->out( $m->scomp( '/Elements/SearchFilter', Attribute => $field, FilterData => \%filter_data, %ARGS ) );
         }

commit 7f87957a17550b365698742b89a342549415fa82
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Mar 31 05:26:05 2023 +0800

    Refactor code to build search filter metadata in Header instead
    
    This is to use the metadata in Header, e.g. to show different funnel
    icons based on if the field is filtered or not.

diff --git a/share/html/Elements/CollectionAsTable/Header b/share/html/Elements/CollectionAsTable/Header
index b8ba86ea0f..3fecec469e 100644
--- a/share/html/Elements/CollectionAsTable/Header
+++ b/share/html/Elements/CollectionAsTable/Header
@@ -236,10 +236,120 @@ foreach my $col ( @Format ) {
             $m->out(
                 qq{ <a href="javascript:void(0)" class="btn btn-primary button search-filter"><img src="/static/images/funnel.svg" role="img" data-toggle="tooltip" data-placement="bottom" data-original-title="$tooltip"></span></a>}
             );
-            $m->out( $m->scomp( '/Elements/SearchFilter', Attribute => $field, %ARGS ) );
+            $m->out( $m->scomp( '/Elements/SearchFilter', Attribute => $field, FilterData => \%filter_data, %ARGS ) );
         }
     }
     $m->out('</th>');
 }
 </%PERL>
 </tr>
+
+<%INIT>
+
+my %filter_data;
+if ( $AllowFiltering && $Class eq 'RT::Tickets' && $ARGS{Query} && $ARGS{BaseQuery} ) {
+
+    my $tickets = RT::Tickets->new( $session{CurrentUser} );
+    my ($ok) = $tickets->FromSQL( $ARGS{Query} );
+    return unless $ok && ( $ARGS{BaseQuery} || $tickets->Count );
+
+    my @queues;
+
+    my $tree = RT::Interface::Web::QueryBuilder::Tree->new;
+    $tree->ParseSQL( Query => $ARGS{BaseQuery} || $ARGS{Query}, CurrentUser => $session{'CurrentUser'} );
+    my $referenced_queues = $tree->GetReferencedQueues;
+    for my $name_or_id ( keys %$referenced_queues ) {
+        my $queue = RT::Queue->new( $session{CurrentUser} );
+        $queue->Load($name_or_id);
+        if ( $queue->id ) {
+            push @queues, $queue;
+        }
+    }
+
+    my %status;
+    my @lifecycles;
+
+    if (@queues) {
+        my %lifecycle;
+        for my $queue (@queues) {
+            next if $lifecycle{ $queue->Lifecycle }++;
+            push @lifecycles, $queue->LifecycleObj;
+        }
+    }
+    else {
+        @lifecycles = map { RT::Lifecycle->Load( Type => 'ticket', Name => $_ ) } RT::Lifecycle->List('ticket');
+    }
+
+    for my $lifecycle (@lifecycles) {
+        $status{$_} = 1 for $lifecycle->Valid;
+    }
+    delete $status{deleted};
+
+    if ( !@queues ) {
+        my $queues = RT::Queues->new( $session{CurrentUser} );
+        $queues->UnLimit;
+
+        while ( my $queue = $queues->Next ) {
+            push @queues, $queue;
+            last if @queues == 10;    # TODO make a config for it
+        }
+    }
+
+    my %filter;
+
+    if ( $ARGS{BaseQuery} && $ARGS{BaseQuery} ne $ARGS{Query} ) {
+        my $query = $ARGS{Query};
+        $query =~ s!^\s*\(?\s*\Q$ARGS{BaseQuery}\E\s*\)? AND !!;
+        my $tree = RT::Interface::Web::QueryBuilder::Tree->new;
+        $tree->ParseSQL( Query => $query, CurrentUser => $session{'CurrentUser'} );
+        $tree->traverse(
+            sub {
+                my $node = shift;
+
+                return if $node->isRoot;
+                return unless $node->isLeaf;
+
+                my $clause = $node->getNodeValue();
+                if ( $clause->{Key} =~ /^Queue/ ) {
+                    my $queue = RT::Queue->new( $session{CurrentUser} );
+                    $queue->Load( $clause->{Value} );
+                    if ( $queue->id ) {
+                        $filter{ $clause->{Key} }{ $queue->id } = 1;
+                    }
+                }
+                elsif ( $clause->{Key} =~ /^(?:Status|SLA|Type)/ ) {
+                    $filter{ $clause->{Key} }{ $clause->{Value} } = 1;
+                }
+                elsif ( $clause->{Key}
+                    =~ /^(?:(?:Initial|Final)?Priority|Time(?:Worked|Estimated|Left)|id|Told|Starts|Started|Due|Resolved|Created|LastUpdated\b)/
+                    )
+                {
+                    $filter{ $clause->{Key} }{ $clause->{Op} } = $clause->{Value};
+                }
+                else {
+                    my $value = $clause->{Value};
+                    $value =~ s!\\([\\"])!$1!g;
+                    my $key = $clause->{Key};
+                    my $cf;
+                    if ( $key eq 'CustomField' ) {
+                        $key .= ".$clause->{Subkey}";
+                        my ($cf_name) = $clause->{Subkey} =~ /{(.+)}/;
+                        $cf = RT::CustomField->new( RT->SystemUser );
+                        $cf->Load($cf_name);
+                    }
+                    elsif ( $key eq 'CustomRole' ) {
+                        $key .= ".$1" if $clause->{Subkey} =~ /(\{.+?\})/;
+                    }
+                    if ( $cf && $cf->id && $cf->Type eq 'Select' ) {
+                        $filter{$key}{$value} = 1;
+                    }
+                    else {
+                        $filter{$key} = $value;
+                    }
+                }
+            }
+        );
+    }
+    %filter_data = ( status => \%status, queues => \@queues, filter => \%filter );
+}
+</%INIT>
diff --git a/share/html/Elements/SearchFilter b/share/html/Elements/SearchFilter
index 21fe0630b3..63c8c1ce7a 100644
--- a/share/html/Elements/SearchFilter
+++ b/share/html/Elements/SearchFilter
@@ -330,116 +330,9 @@
 <%INIT>
 return unless $ARGS{Query};
 
-my $key = "search-filter-$ARGS{Query}";
-my $cached = $m->notes($key);
-if ( !$cached ) {
-    my $tickets = RT::Tickets->new( $session{CurrentUser} );
-    my ($ok) = $tickets->FromSQL( $ARGS{Query} );
-    return unless $ok && ( $ARGS{BaseQuery} || $tickets->Count );
-
-    my @queues;
-
-    my $tree = RT::Interface::Web::QueryBuilder::Tree->new;
-    $tree->ParseSQL( Query => $ARGS{BaseQuery} || $ARGS{Query}, CurrentUser => $session{'CurrentUser'} );
-    my $referenced_queues = $tree->GetReferencedQueues;
-    for my $name_or_id ( keys %$referenced_queues ) {
-        my $queue = RT::Queue->new( $session{CurrentUser} );
-        $queue->Load($name_or_id);
-        if ( $queue->id ) {
-            push @queues, $queue;
-        }
-    }
-
-    my %status;
-    my @lifecycles;
-
-    if (@queues) {
-        my %lifecycle;
-        for my $queue (@queues) {
-            next if $lifecycle{ $queue->Lifecycle }++;
-            push @lifecycles, $queue->LifecycleObj;
-        }
-    }
-    else {
-        @lifecycles = map { RT::Lifecycle->Load( Type => 'ticket', Name => $_ ) } RT::Lifecycle->List('ticket');
-    }
-
-    for my $lifecycle (@lifecycles) {
-        $status{$_} = 1 for $lifecycle->Valid;
-    }
-    delete $status{deleted};
-
-    if ( !@queues ) {
-        my $queues = RT::Queues->new( $session{CurrentUser} );
-        $queues->UnLimit;
-
-        while ( my $queue = $queues->Next ) {
-            push @queues, $queue;
-            last if @queues == 10;    # TODO make a config for it
-        }
-    }
-
-    my %filter;
-
-    if ( $ARGS{BaseQuery} && $ARGS{BaseQuery} ne $ARGS{Query} ) {
-        my $query = $ARGS{Query};
-        $query =~ s!^\s*\(?\s*\Q$ARGS{BaseQuery}\E\s*\)? AND !!;
-        my $tree = RT::Interface::Web::QueryBuilder::Tree->new;
-        $tree->ParseSQL( Query => $query, CurrentUser => $session{'CurrentUser'} );
-        $tree->traverse(
-            sub {
-                my $node = shift;
-
-                return if $node->isRoot;
-                return unless $node->isLeaf;
-
-                my $clause = $node->getNodeValue();
-                if ( $clause->{Key} =~ /^Queue/ ) {
-                    my $queue = RT::Queue->new( $session{CurrentUser} );
-                    $queue->Load( $clause->{Value} );
-                    if ( $queue->id ) {
-                        $filter{ $clause->{Key} }{ $queue->id } = 1;
-                    }
-                }
-                elsif ( $clause->{Key} =~ /^(?:Status|SLA|Type)/ ) {
-                    $filter{ $clause->{Key} }{ $clause->{Value} } = 1;
-                }
-                elsif ( $clause->{Key}
-                    =~ /^(?:(?:Initial|Final)?Priority|Time(?:Worked|Estimated|Left)|id|Told|Starts|Started|Due|Resolved|Created|LastUpdated\b)/
-                    )
-                {
-                    $filter{ $clause->{Key} }{ $clause->{Op} } = $clause->{Value};
-                }
-                else {
-                    my $value = $clause->{Value};
-                    $value =~ s!\\([\\"])!$1!g;
-                    my $key = $clause->{Key};
-                    my $cf;
-                    if ( $key eq 'CustomField' ) {
-                        $key .= ".$clause->{Subkey}";
-                        my ($cf_name) = $clause->{Subkey} =~ /{(.+)}/;
-                        $cf = RT::CustomField->new( RT->SystemUser );
-                        $cf->Load($cf_name);
-                    }
-                    elsif ( $key eq 'CustomRole' ) {
-                        $key .= ".$1" if $clause->{Subkey} =~ /(\{.+?\})/;
-                    }
-                    if ( $cf && $cf->id && $cf->Type eq 'Select' ) {
-                        $filter{$key}{$value} = 1;
-                    }
-                    else {
-                        $filter{$key} = $value;
-                    }
-                }
-            }
-        );
-    }
-    $cached = $m->notes( $key, { status => \%status, queues => \@queues, filter => \%filter } );
-}
-
-my $status = $cached->{status};
-my $queues = $cached->{queues};
-my $filter = $cached->{filter};
+my $status = $FilterData{status};
+my $queues = $FilterData{queues};
+my $filter = $FilterData{filter};
 
 my $modal_class;
 if ( $Attribute =~ /^(?:Time|(?:Initial|Final)Priority|LastUpdated)/ ) {
@@ -452,4 +345,5 @@ else {
 
 <%ARGS>
 $Attribute => ''
+%FilterData => ()
 </%ARGS>

commit f41291d7c328774a2b864ac6a4ac1510dafef5ba
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Thu Mar 30 13:34:40 2023 -0400

    Replace fontawesome funnel icon with bootstrap version
    
    We wanted a version of the funnel icon with just the outline
    and no fill, and that icon is not in the fontawesome free
    collection, so switch to the MIT licensed bootstrap version
    which provides both the solid and outline funnels.
    
    The color is set directly in the svg files by changing "fill"
    from the default bootstrap version.
    
    This update also makes the icon a bit smaller.

diff --git a/devel/third-party/README b/devel/third-party/README
index e870e5c568..0a50816487 100644
--- a/devel/third-party/README
+++ b/devel/third-party/README
@@ -19,6 +19,12 @@ Description: modern select with intuitive multiselection, searching, and much mo
 Origin: https://github.com/snapappointments/bootstrap-select
 License: MIT
 
+* bootstrap-icons-1.10.3
+Description: Official open source SVG icon library for Bootstrap with over 1,900 icons
+Origin: https://github.com/twbs/icons
+License: MIT
+Note: Various individual svg icon files pulled directly into share/static/images
+
 * Chart-2.9.4
 Description: HTML5 Charts using the <canvas> tag
 Origin: https://github.com/chartjs/Chart.js
diff --git a/share/html/Elements/CollectionAsTable/Header b/share/html/Elements/CollectionAsTable/Header
index 51e7f60b0f..b8ba86ea0f 100644
--- a/share/html/Elements/CollectionAsTable/Header
+++ b/share/html/Elements/CollectionAsTable/Header
@@ -234,7 +234,7 @@ foreach my $col ( @Format ) {
         if ( $field ) {
             my $tooltip = loc( 'Filter on [_1]', loc($field) );
             $m->out(
-                qq{ <a href="javascript:void(0)" class="search-filter" class="btn btn-primary button"><span class="fas fa-filter" data-toggle="tooltip" data-placement="bottom" data-original-title="$tooltip"></span></a>}
+                qq{ <a href="javascript:void(0)" class="btn btn-primary button search-filter"><img src="/static/images/funnel.svg" role="img" data-toggle="tooltip" data-placement="bottom" data-original-title="$tooltip"></span></a>}
             );
             $m->out( $m->scomp( '/Elements/SearchFilter', Attribute => $field, %ARGS ) );
         }
diff --git a/share/static/css/elevator-light/ticket-search.css b/share/static/css/elevator-light/ticket-search.css
index 01470c1700..4354ac8aeb 100644
--- a/share/static/css/elevator-light/ticket-search.css
+++ b/share/static/css/elevator-light/ticket-search.css
@@ -9,3 +9,23 @@
 #pick-criteria .value .form-row {
     margin-top: 0;
 }
+
+/* The below mostly clears default button styles from the special
+   case filter (funnel) icons */
+
+table.collection-as-table th.collection-as-table a.search-filter {
+    font-size: .3em;
+    background-color: unset;
+    vertical-align: left;
+    padding-left: 0em;
+    color: unset;
+}
+
+a.button:hover, a.btn-primary:focus, a.button:focus {
+    background: unset;
+    border-color: unset;
+}
+
+a.btn, a.btn:hover, a.btn:focus {
+    border: unset;
+}
diff --git a/share/static/images/funnel-fill.svg b/share/static/images/funnel-fill.svg
new file mode 100644
index 0000000000..e34633496e
--- /dev/null
+++ b/share/static/images/funnel-fill.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#3858a3" class="bi bi-funnel-fill" viewBox="0 0 16 16">
+  <path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2z"/>
+</svg>
\ No newline at end of file
diff --git a/share/static/images/funnel.svg b/share/static/images/funnel.svg
new file mode 100644
index 0000000000..95a147a1bd
--- /dev/null
+++ b/share/static/images/funnel.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#3858a3" class="bi bi-funnel" viewBox="0 0 16 16">
+  <path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/>
+</svg>
\ No newline at end of file

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


hooks/post-receive
-- 
rt


More information about the rt-commit mailing list