[Bps-public-commit] rtx-calendar branch allow-custom-field-based-events-2 created. 1.05-13-gccc1561

BPS Git Server git at git.bestpractical.com
Fri Sep 1 14:40:58 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 "rtx-calendar".

The branch, allow-custom-field-based-events-2 has been created
        at  ccc15614ce49b21aacbf86bbfe601693fbfb110a (commit)

- Log -----------------------------------------------------------------
commit ccc15614ce49b21aacbf86bbfe601693fbfb110a
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Fri Sep 1 11:33:39 2023 -0300

    Only show legend on the context of current Search
    
    The legend of all event icons were shown on the calendar, even if the
    event was not part of the current search. This was confusing, as the
    legend was not related to the current search.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 56fcee3..42a5306 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -66,10 +66,16 @@ my %CalendarIcons = RT->Config->Get('CalendarIcons');
 # Sort the legend after translation
 my %CalendarIconsTranslated;
 my $LegendLabel;
+LEGEND:
 foreach my $legend (sort { lc($a) cmp lc($b) } keys %CalendarIcons) {
+  # We might have multiple labels for the same icon
+  # such as "LastUpdated, CF.{Maintenance Date}"
+  # so we need to split them and translate them individually
   my @LegendLabels = split ',', $legend;
   $LegendLabel = join ', ',
     map {
+      my $label = $_;
+      next LEGEND unless ( grep { $label eq $_ } @Dates );
       $_ =~ s/^\s+|\s+$//g;
       $_ =~ s/^CF\.\{(.*)\}/$1/;
       $_ = 'Last Updated' if $_ eq 'LastUpdated';

commit d948f4745b89f7ea68e99b0deb94091686b3efb4
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Thu Aug 31 19:47:38 2023 -0300

    Fix Filter on Status moving to current Month
    
    When viewing different month than current, when filtering on status,
    the user was taken back to the current month. Fixed by adding hidden
    inputs to the filter on status form.
    
    Also fix loosing the filter on status when changing month.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 1202e09..56fcee3 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -31,8 +31,14 @@ $FilterOnStatusClear => undef
     &>
 
   <form id="FilterOnStatusForm"
-  action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post">
+  action="<%$RT::WebPath%>/Search/Calendar.html" method="post">
   <input type="hidden" name="BaseQuery" value="<%$BaseQuery%>" />
+  <input type="hidden" name="Month" value="<%$Month%>" />
+  <input type="hidden" name="Year" value="<%$Year%>" />
+  <input type="hidden" name="Format" value="<%$Format%>" />
+  <input type="hidden" name="Order" value="<%$Order%>" />
+  <input type="hidden" name="OrderBy" value="<%$OrderBy%>" />
+  <input type="hidden" name="RowsPerPage" value="<%$RowsPerPage%>" />
   <select name="FilterOnStatus" id="FilterOnStatus"
     class="selectpicker filteronstatus mt-3 mb-3" multiple="multiple" size="6">
 % for my $Status (sort {loc($a) cmp loc($b)} @{RT->Config->Get('CalendarFilterStatuses')}) {
@@ -293,6 +299,7 @@ my $QueryString =
       $m->comp(
         '/Elements/QueryString',
         Query   => $BaseQuery,
+        FilterOnStatus => \@FilterOnStatus,
         Format  => $Format,
         Order   => $Order,
         OrderBy => $OrderBy,

commit 76cdfa4cfdfcfd134e40e2e2c8b751f832758708
Author: Jason Crome <jcrome at bestpractical.com>
Date:   Wed Aug 30 09:29:59 2023 -0400

    Update documentation for supported RT versions
    
    The new sidebar does not function with RT 4.x. Provide documentation for
    users that want to use this extension but are still on RT 4.x.

diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index e125f86..cb3b26c 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -234,7 +234,11 @@ CONFIGURATION section below for details on adding it.
 
 =head1 RT VERSION
 
-Works with RT 4.2, 4.4, 5.0
+Works with RT 5.
+
+If you need to install this for RT 4.4.x, install version 1.05:
+
+    cpanm RTx::Calendar at 1.05
 
 =head1 INSTALLATION
 

commit 88a2e79d3079b5eb597f353f4e07557cb82cc4e4
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 29 21:51:24 2023 -0300

    Improve calendar help
    
    Fixed title capitalization.
    
    Updated header level to decrease title size.
    
    Fixed some typos.
    
    WIP

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 5312065..1202e09 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -203,24 +203,24 @@ foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
 
 <&| /Widgets/TitleBox, title => loc('Help') &>
 
-<h3><&|/l&>displaying reminders</&>:</h3>
+<h4 class="mt-2"><&|/l&>Displaying reminders</&>:</h4>
 <p>
 <&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Edit.html">} . loc("Advanced") . '</a>' &>
-If you want to have reminders in a search you need to go to [_1] tab
-and add something to the Query like that:
+If you want to see reminders on the calendar, you need to go to the [_1] tab
+of your query and explicitly add the following clause to it:
 </&>
  <pre>
    AND ( Type = 'ticket' OR Type = 'reminder' )
 </pre>
 </p>
 
-<h3><&|/l&>displaying other kind of dates</&>:</h3>
+<h4><&|/l&>Displaying other kind of dates</&>:</h4>
 <p>
 <&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
-By default RTx::Calendar display Due and Starts dates. You can select other
-kind of events you want with the Display Columns section in the [_1].
-The following one will display the two latter, LastUpdated and an additional
-date from a custom field called Maintenance Date:
+By default, RTx::Calendar displays Due and Starts dates. You can select other
+date fields with the Display Columns section in the [_1].
+The following format will display the two additional date fields, LastUpdated and a
+custom field called Maintenance Date:
 </&>
 <pre>
   '<small>__Due__</small>',
@@ -230,10 +230,10 @@ date from a custom field called Maintenance Date:
 </pre>
 </p>
 
-<h3><&|/l&>changing the default query</&>:</h3>
+<h4><&|/l&>Changing the default query</&>:</h4>
 <p>
 <&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
-You can change the default Query of Calendar.html and MyCalendar
+You can change the default query used by Calendar.html and the MyCalendar
 portlet by saving a query with the name <code>calendar</code> in the [_1].
 </&>
 </p>

commit ac78e83a93af71528b4ead4d9d7b30feb0833a9a
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 29 21:05:11 2023 -0300

    Update calendar POD
    
    Refactor POD adding an extra header level to describe better the Display
    configuration options.
    
    Add sections and descriptions for the new features and options:
    Custom Field displaying, Event sorting, Custom event colors, custom
    event icons and extra filter on status on Calendar context.

diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 9e344bb..e125f86 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -280,15 +280,76 @@ C<$HomepageComponents> in F<etc/RT_SiteConfig.pm>:
 
 =head2 Display configuration
 
+=head3 Displaying the owner
+
 You can show the owner in each day box by adding this line to your
 F<etc/RT_SiteConfig.pm>:
 
     Set($CalendarDisplayOwner, 1);
 
+=head3 Choosing the fields to be displayed in the popup
+
 You can change which fields show up in the popup display when you
 mouse over a date in F<etc/RT_SiteConfig.pm>:
 
-    Set(@CalendarPopupFields, ('Status', 'OwnerObj->Name', 'DueObj->ISO'));
+    Set(@CalendarPopupFields,
+        ('Status',
+         'OwnerObj->Name',
+         'DueObj->ISO',
+         'CustomField.{Maintenance Estimated Start Date/Time - ET}'));
+
+=head3 Event sorting
+
+You can set the order that the events will presented in the day cell with
+the C<$CalendarSortEvents> setting.
+
+This setting takes a subroutine reference that will receive an array of
+L<RT::Ticket> objects and should return a sorted array of L<RT::Ticket>.
+
+The following example sorts the events by status:
+
+    Set($CalendarSortEvents, sub {
+        my @Tickets = @_;
+        my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
+        return @SortedTickets;
+    });
+
+=head3 Event colors
+
+It's also possible to change the color of the events in the calendar by
+adding the C<$CalendarStatusColorMap> setting to your F<etc/RT_SiteConfig.pm>:
+
+    Set(%CalendarStatusColorMap, (
+        'new'                                   => 'blue',
+        'open'                                  => 'blue',
+        'approved'                              => 'green',
+        'rejected'                              => 'red',
+        'resolved'                              => '#aaa',
+    ));
+
+You can use any color declaration that CSS supports, including hex codes,
+color names, and RGB values.
+
+=head3 Event filtering by status
+
+You can change the statuses available for filtering on the calendar by
+adding the C<@CalendarFilterStatuses> setting to your
+F<etc/RT_SiteConfig.pm>:
+
+    Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
+
+
+=head3 Custom icons
+
+Custom Icons can be defined for the events in the calendar by adding the
+C<$CalendarIcons> setting to your F<etc/RT_SiteConfig.pm>:
+
+    Set(%CalendarIcons, (
+        'CF.{Maintenance Estimated Start Date/Time - ET}'
+            => 'maint.png',
+    ));
+
+The images should be placed on F<local/static/images>.
 
 =head1 USAGE
 
@@ -312,7 +373,7 @@ or via the web at
 
 =head1 LICENSE AND COPYRIGHT
 
-This software is Copyright (c) 2010-2022 by Best Practical Solutions
+This software is Copyright (c) 2010-2023 by Best Practical Solutions
 
 Copyright 2007-2009 by Nicolas Chuche
 

commit 377299ebb888ee21cca0f23cd77cdd702dc75d79
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 29 20:54:29 2023 -0300

    Refactor calendar legend to show custom icons
    
    Calendar legend now shows custom icons; removed the hard coded icons
    for the default events.
    
    Moved the legend to the sidebar.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 35b8002..5312065 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -52,6 +52,39 @@ $FilterOnStatusClear => undef
 </&>
 
   <&| /Widgets/TitleBox,
+     title => loc('Event Types'),
+     &>
+
+<%perl>
+my %CalendarIcons = RT->Config->Get('CalendarIcons');
+# Sort the legend after translation
+my %CalendarIconsTranslated;
+my $LegendLabel;
+foreach my $legend (sort { lc($a) cmp lc($b) } keys %CalendarIcons) {
+  my @LegendLabels = split ',', $legend;
+  $LegendLabel = join ', ',
+    map {
+      $_ =~ s/^\s+|\s+$//g;
+      $_ =~ s/^CF\.\{(.*)\}/$1/;
+      $_ = 'Last Updated' if $_ eq 'LastUpdated';
+      loc($_)
+    } @LegendLabels;
+  $CalendarIconsTranslated{$LegendLabel} = $legend;
+}
+foreach my $TranslatedLegend (sort keys %CalendarIconsTranslated) {
+</%perl>
+  <span class="tip">
+    <span class="tipimg">
+      <img
+        src="<% $RT::WebImagesURL %>/<%
+        $CalendarIcons{$CalendarIconsTranslated{$TranslatedLegend}}|n %>" />
+    </span>
+    <span class="tiplegend">
+      <% $TranslatedLegend %>
+    </span>
+  </span>
+%     }
+</&>
 
 <&| /Widgets/TitleBox,
      title => loc('State Colors'),
@@ -166,24 +199,6 @@ $FilterOnStatusClear => undef
 </tr>
 </table>
 
-<table width="100%">
-% foreach my $legend (sort keys %legend) {
-    <tr>
-      <td align="right">
-        <img src="<%$RT::WebImagesURL%>/<%$legend%>.png" />
-      </td>
-      <td align="left">
-%       my $more = 0;
-%       foreach ( @{$legend{$legend}} ) {
-          <% $more++ ? ', ' : '' %>
-          <&|/l&><% $_ %></&>
-%       }
-      </td>
-    </tr>
-% }
-
-</table>
-
 </&>
 
 <&| /Widgets/TitleBox, title => loc('Help') &>
@@ -235,20 +250,7 @@ portlet by saving a query with the name <code>calendar</code> in the [_1].
     });
   });
 </script>
-<%ONCE>
-
-my %legend = (
-  'created'     => ['Created'],
-  'due'         => ['Due'],
-  'resolved'    => ['Resolved'],
-  'updated'     => ['Last Updated'],
-  'created_due' => ['Created','Due'],
-  'reminder'    => ['Reminders'],
-  'started'     => ['Started'],
-  'starts_due'  => ['Starts','Due'],
-);
 
-</%ONCE>
 <%INIT>
 if ($FilterOnStatusClear) {
   $Query = $BaseQuery if $BaseQuery;

commit 42302e44b53e3f91c700d38a00b93062f21d94bc
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 29 21:30:47 2023 -0300

    Add feature to customize icons of calendar events
    
    Since we now allow adding of custom fields to the calendar, we need to
    allow customizing of the icon of the event based on its type.
    
    If custom icon is not set to a Custom Field based event, then we will
    not show a standard icon for that.

diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
index 253bd78..d042d47 100644
--- a/etc/RTxCalendar_Config.pm
+++ b/etc/RTxCalendar_Config.pm
@@ -1,3 +1,15 @@
+Set(%CalendarIcons, (
+    'Reminder'     => 'reminder.png',
+    'Resolved'     => 'resolved.png',
+    'Starts, Due'  => 'starts_due.png',
+    'Created, Due' => 'created_due.png',
+    'Created'      => 'created.png',
+    'Due'          => 'due.png',
+    'Starts'       => 'starts.png',
+    'Started'      => 'started.png',
+    'LastUpdated'  => 'updated.png',
+));
+
 Set(%CalendarStatusColorMap, (
     'new'                                   => '#87873c',
     'open'                                  => '#5555f8',
diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index 6d3d874..b8023d9 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -6,43 +6,7 @@ $DateTypes => undef
 <div class="day">
 <small>
 
-% if ($IsReminder and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today) {
-     <img src="<%$RT::WebImagesURL%>/reminder.png" />
-
-% } elsif ($DateTypes->{Resolved}
-%           and RTx::Calendar::LocalDate($Object->ResolvedObj->Unix) eq $today) {
-         <img src="<%$RT::WebImagesURL%>/resolved.png" />
-
-% } elsif ($DateTypes->{Starts} and $DateTypes->{Due}
-%           and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $today and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today ) {
-    <img src="<%$RT::WebImagesURL%>/starts_due.png" />
-
-% } elsif ($DateTypes->{Due} and $DateTypes->{Created}
-%           and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $today ) {
-    <img src="<%$RT::WebImagesURL%>/created_due.png" />
-
-% } elsif ($DateTypes->{Starts}
-%           and RTx::Calendar::LocalDate($Object->StartsObj->Unix) eq $today) {
-    <img src="<%$RT::WebImagesURL%>/starts.png" />
-
-% } elsif ($DateTypes->{Due}
-%           and RTx::Calendar::LocalDate($Object->DueObj->Unix) eq $today) {
-    <img src="<%$RT::WebImagesURL%>/due.png" />
-
-% } elsif ($DateTypes->{Created}
-%           and RTx::Calendar::LocalDate($Object->CreatedObj->Unix) eq $today) {
-    <img src="<%$RT::WebImagesURL%>/created.png" />
-
-% } elsif ($DateTypes->{Started}
-%           and RTx::Calendar::LocalDate($Object->StartedObj->Unix) eq $today) {
-    <img src="<%$RT::WebImagesURL%>/started.png" />
-
-% } elsif ($DateTypes->{LastUpdated}
-%           and RTx::Calendar::LocalDate($Object->LastUpdatedObj->Unix) eq $today) {
-    <img src="<%$RT::WebImagesURL%>/updated.png" />
-
-% }
-
+    <% RTx::Calendar::GetEventImg($Object, $today, $DateTypes, $IsReminder)|n %>
 	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>"
 % if ( $CalendarStatusColorMap{$status} ) {
     style="color: <%$CalendarStatusColorMap{$status}%>;"
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 06b501a..9e344bb 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -160,6 +160,57 @@ sub SearchDefaultCalendar {
     }
 }
 
+sub GetEventImg {
+    my $Object      = shift;
+    my $CurrentDate = shift;
+    my $DateTypes   = shift;
+    my $IsReminder  = shift;
+    my $EventIcon;
+    my %CalendarIcons = RT->Config->Get('CalendarIcons');
+CALENDAR_ICON:
+    for my $DateField ( keys %CalendarIcons ) {
+
+      # Icon can be a combination of two dates such as Due-Created,
+      # or CF.{Date Field}-Created. It can be also single date such as Created
+        my @DatesToCompare = split( /,/, $DateField );
+    DATE_COMPARE:
+        for my $ComparedDate (@DatesToCompare) {
+            # trim spaces
+            $ComparedDate =~ s/^\s+|\s+$//g;
+            if ( $DateField eq 'Reminder' ) {
+                if ( $IsReminder
+                    && RTx::Calendar::LocalDate( $Object->DueObj->Unix ) eq
+                    $CurrentDate )
+                {
+                    $EventIcon = 'reminder.png';
+                    last CALENDAR_ICON;
+                }
+            } elsif ( $DateField =~ /^CF\./ ) {
+                my $cf = $DateField;
+                $cf =~ s/^CF\.\{(.*)\}/$1/;
+                my $DateValue = $Object->FirstCustomFieldValue($cf);
+                next CALENDAR_ICON unless $DateValue;
+                $DateValue =~ s/(.*) (.*)/$1/;
+                next CALENDAR_ICON unless $DateValue eq $CurrentDate;
+            } else {
+                my $DateObj = $ComparedDate . "Obj";
+                my $DateValue
+                    = RTx::Calendar::LocalDate( $Object->$DateObj->Unix );
+                next CALENDAR_ICON unless $DateValue eq $CurrentDate;
+            }
+
+            # If we are here, it means that all comparissons are true
+            $EventIcon = $CalendarIcons{$DateField};
+        }
+    }
+    if ($EventIcon) {
+        return '<img src="' . $RT::WebImagesURL . '/' . $EventIcon . '" />';
+    } else {
+        return '';
+    }
+}
+
+
 1;
 
 __END__

commit 4996150ceee6b5c1192535855ba3628ee5f39300
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 29 21:26:35 2023 -0300

    Add feature to customize color of events on the calendar

diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
index 0f16304..253bd78 100644
--- a/etc/RTxCalendar_Config.pm
+++ b/etc/RTxCalendar_Config.pm
@@ -1,3 +1,11 @@
+Set(%CalendarStatusColorMap, (
+    'new'                                   => '#87873c',
+    'open'                                  => '#5555f8',
+    'rejected'                              => '#FF0000',
+    'resolved'                              => '#72b872',
+    'stalled'                               => '#FF0000',
+));
+
 Set($CalendarSortEvents, sub {
     my @Tickets = @_;
     my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index 11d090d..6d3d874 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -43,7 +43,11 @@ $DateTypes => undef
 
 % }
 
-	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>">
+	<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$TicketId%>"
+% if ( $CalendarStatusColorMap{$status} ) {
+    style="color: <%$CalendarStatusColorMap{$status}%>;"
+% }
+    >
            <% $Object->QueueObj->Name %> #<% $TicketId %>
            <% $display_owner ? 'by ' . $Object->OwnerObj->Name : '' %>
            <% length($Object->Subject) > 80 ? substr($Object->Subject, 0, 77) . "..." : $Object->Subject %></a></small><br />
@@ -86,6 +90,7 @@ my $TicketId;
 my $ticket;
 my $subject;
 my $IsReminder;
+my $status;
 
 if ($Object->Type eq 'reminder') {
     $IsReminder = 1;
@@ -93,12 +98,16 @@ if ($Object->Type eq 'reminder') {
 	$ticket   = $Object->RefersTo->First->TargetObj;
 	$TicketId = $ticket->Id;
 	$subject = $Object->Subject . " (" . $ticket->Subject . ")";
+        $status = $Object->Status;
     }
 } else {
     $TicketId = $Object->Id;
     $subject = $Object->Subject;
+    $status = $Object->Status;
 }
 
+my %CalendarStatusColorMap = RT->Config->Get('CalendarStatusColorMap');
+
 my $display_owner = $RT::CalendarDisplayOwner;
 $display_owner ||= RT->Config->Get('CalendarDisplayOwner')
     if RT->can('Config');
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 33f14d2..35b8002 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -52,6 +52,14 @@ $FilterOnStatusClear => undef
 </&>
 
   <&| /Widgets/TitleBox,
+
+<&| /Widgets/TitleBox,
+     title => loc('State Colors'),
+     &>
+% my %ColorStatusMap = RT->Config->Get('CalendarStatusColorMap');
+% foreach my $Status (sort { lc($a) cmp lc($b) } keys %ColorStatusMap) {
+    <span style="color: <% $ColorStatusMap{$Status} %>"><% $Status %><span><br />
+% }
 </&>
 
 </div>

commit 5aee94074e1bd2a95d27dafbe61d43b787d9330a
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 29 20:38:07 2023 -0300

    Add link to Download Calendar events
    
    Add link on the top right of the Calendar to download the events that
    are shown in the current view as a Spreadsheet.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 3e4432b..33f14d2 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -58,6 +58,11 @@ $FilterOnStatusClear => undef
 </div>
 
 <div class="calendar-content">
+<&| /Widgets/TitleBox,
+     title => loc('Calendar for [_1] [_2]', $rtdate->GetMonth($Month), $Year),
+     titleright => loc('Download Spreadsheet'),
+     titleright_href => $RT::WebPath. "/Search/Results.tsv?". $DownloadQueryString
+     &>
 
 <table width="100%">
 <tr>
@@ -330,5 +335,14 @@ $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef =
 
 my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
 
+my $DownloadQueryString =
+      $m->comp(
+        '/Elements/QueryString',
+        Query   => $TempQuery,
+        Format  => $Format,
+        Order   => $Order,
+        OrderBy => $OrderBy,
+      );
+
 my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
 </%INIT>

commit 39fa9879e6bdcbcfef8e8a75d85df1b76fa20629
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 29 20:27:21 2023 -0300

    Add Status filter to Calendar sidebar
    
    Allows user to filter events by one or more ticket statuses.
    
    Add setting to define available statuses to filter on.

diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
index 17f12ae..0f16304 100644
--- a/etc/RTxCalendar_Config.pm
+++ b/etc/RTxCalendar_Config.pm
@@ -4,4 +4,6 @@ Set($CalendarSortEvents, sub {
     return @SortedTickets;
 });
 
+Set(@CalendarFilterStatuses, qw(new open stalled rejected resolved));
+
 1;
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 20625e2..3e4432b 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -7,6 +7,9 @@ $Order => undef
 $OrderBy => undef
 $RowsPerPage => undef
 $NewQuery => 0
+ at FilterOnStatus => undef
+$BaseQuery => undef
+$FilterOnStatusClear => undef
 </%args>
 
 <& /Elements/Header, Title => $title &>
@@ -23,6 +26,33 @@ $NewQuery => 0
 <a title="Toggle Filter" href="javascript:;" class="calendar-toggle-sidebar"></a>
 <div class="calendar-sidebar">
 <&| /Widgets/TitleBox,
+    title => loc('Filter on Status'),
+    class => 'calendar-filter-status-box',
+    &>
+
+  <form id="FilterOnStatusForm"
+  action="<%$RT::WebPath%>/Search/Calendar.html?<%$QueryString%>" method="post">
+  <input type="hidden" name="BaseQuery" value="<%$BaseQuery%>" />
+  <select name="FilterOnStatus" id="FilterOnStatus"
+    class="selectpicker filteronstatus mt-3 mb-3" multiple="multiple" size="6">
+% for my $Status (sort {loc($a) cmp loc($b)} @{RT->Config->Get('CalendarFilterStatuses')}) {
+    <option value="<% $Status %>"
+% if (@FilterOnStatus && $FilterOnStatus[0]) {
+      <% (grep { $Status eq $_ } @FilterOnStatus) ? 'selected="selected"':''%>
+% }
+      ><% loc($Status) %></option>
+% }
+  </select>
+  <div class="text-center">
+    <input type="submit" value="<% loc('Filter') %>" class="mr-2 button btn btn-primary" />
+    <button type="submit" id="FilterOnStatusClear" name="FilterOnStatusClear"
+      value="1" class="button btn btn-primary"><% loc('Clear Filter') %></button>
+  </div>
+</form>
+</&>
+
+  <&| /Widgets/TitleBox,
+</&>
 
 </div>
 </div>
@@ -207,6 +237,11 @@ my %legend = (
 
 </%ONCE>
 <%INIT>
+if ($FilterOnStatusClear) {
+  $Query = $BaseQuery if $BaseQuery;
+  @FilterOnStatus = ();
+}
+$BaseQuery ||= $Query;
 my $title = loc("Calendar");
 
 my @DateTypes = qw/Created Starts Started Due LastUpdated Resolved/;
@@ -233,10 +268,16 @@ my $set = DateTime::Set->from_recurrence(
     next => sub { $_[0]->truncate( to => 'day' )->add( days => 1 ) }
 );
 
+if ($FilterOnStatus[0]) {
+  my $StatusClause = join " OR ", map { "Status = '$_'" } @FilterOnStatus;
+  $Query .= " AND " if $Query;
+  $Query .= "($StatusClause)";
+}
+
 my $QueryString =
       $m->comp(
         '/Elements/QueryString',
-        Query   => $Query,
+        Query   => $BaseQuery,
         Format  => $Format,
         Order   => $Order,
         OrderBy => $OrderBy,
diff --git a/static/css/calendar.css b/static/css/calendar.css
index 60cc225..96e9086 100644
--- a/static/css/calendar.css
+++ b/static/css/calendar.css
@@ -128,6 +128,12 @@ a.calendar-toggle-sidebar.sidebar-off::before {
 .calendar-content.sidebar-off {
     margin-left: 20px;
 }
+.filteronstatus {
+    width: 100% !important;
+}
+.calendar-filter-status-box {
+    margin-top: 0px;
+}
 .calendar-sidebar {
     margin-right: 10px;
 }

commit abf97304b3f61f5e8a41af09cf83ec2adaf093a2
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 29 21:16:42 2023 -0300

    Add sidebar to calendar
    
    The sidebar will hold the legend, filters, and other future controls.
    
    Add a toggle to show/hide the sidebar.

diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index bc44d26..20625e2 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -19,8 +19,15 @@ $NewQuery => 0
     <& /Elements/Tabs &>
 % }
 
+<div class="calendar-sidebar-toggle-content">
+<a title="Toggle Filter" href="javascript:;" class="calendar-toggle-sidebar"></a>
+<div class="calendar-sidebar">
 <&| /Widgets/TitleBox,
-     title => loc('Calendar for [_1] [_2]', $rtdate->GetMonth($Month), $Year) &>
+
+</div>
+</div>
+
+<div class="calendar-content">
 
 <table width="100%">
 <tr>
@@ -174,7 +181,17 @@ portlet by saving a query with the name <code>calendar</code> in the [_1].
 </p>
 
 </&>
-
+</div>
+
+<script type="text/javascript">
+  jQuery(document).ready(function() {
+    jQuery('.calendar-toggle-sidebar').click(function() {
+      jQuery('.calendar-sidebar').toggle();
+      jQuery('.calendar-sidebar-toggle-content,.calendar-toggle-sidebar').toggleClass('sidebar-off');
+      jQuery('.calendar-content').toggleClass('sidebar-off');
+    });
+  });
+</script>
 <%ONCE>
 
 my %legend = (
diff --git a/static/css/calendar.css b/static/css/calendar.css
index 4adfc4a..60cc225 100644
--- a/static/css/calendar.css
+++ b/static/css/calendar.css
@@ -72,3 +72,62 @@ table.rtxcalendar td.yesterday {
 table.rtxcalendar td.aweekago {
     border-bottom: none;
 }
+
+.calendar-sidebar {
+    padding-right: 0.5rem;
+}
+
+.calendar-content {
+    width: auto;
+}
+
+.calendar-sidebar .tipimg {
+    display: table-cell;
+    width: 25px;
+}
+.calendar-sidebar .tiplegend {
+    display: table-cell;
+}
+.calendar-sidebar .tip {
+    display: table-row;
+}
+
+a.calendar-toggle-sidebar,
+a.calendar-toggle-sidebar.off {
+    background: none !important;
+    width: 0px !important;
+    position: relative;
+    float: right;
+    top: 300px;
+    margin-right: 10px;
+}
+a.calendar-toggle-sidebar::before {
+    content: '';
+    display: block;
+    width: 8px;
+    height: 8px;
+    border: solid #B0B3BC;
+    border-width: 0 2px 2px 0;
+    -webkit-transform: rotate(135deg);
+    -ms-transform: rotate(135deg);
+    transform: rotate(135deg);
+    transition: transform .25s;
+    position: absolute;
+    top: 1.2em;
+}
+a.calendar-toggle-sidebar.sidebar-off::before {
+    transform: rotate(315deg);
+}
+.calendar-sidebar-toggle-content {
+    float: left;
+    width: 230px;
+}
+.calendar-sidebar-toggle-content.sidebar-off {
+    width:unset;
+}
+.calendar-content.sidebar-off {
+    margin-left: 20px;
+}
+.calendar-sidebar {
+    margin-right: 10px;
+}

commit 2ef29150eddf6465e301f1af1f4dc5820dac50fc
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 29 19:31:43 2023 -0300

    Add setting to customize the sort order of events
    
    Add $CalendarSortEvents setting to allow customize the sort order
    of the events in a day cell of the calendar. This is a sub that takes
    the Objects list (that can be tickets or reminders) and returns a sorted
    list of the objects.

diff --git a/etc/RTxCalendar_Config.pm b/etc/RTxCalendar_Config.pm
new file mode 100644
index 0000000..17f12ae
--- /dev/null
+++ b/etc/RTxCalendar_Config.pm
@@ -0,0 +1,7 @@
+Set($CalendarSortEvents, sub {
+    my @Tickets = @_;
+    my @SortedTickets = sort { lc($a->Status) cmp lc($b->Status) } @Tickets;
+    return @SortedTickets;
+});
+
+1;
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index fcaf314..bc44d26 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -68,7 +68,7 @@ $NewQuery => 0
     <td class="<% @classes %>"><div class="inside-day">
       <div class="calendardate"><%$date->day%></div>
 
-%     for my $t ( @{ $Tickets{$date->strftime("%F")} } ) {
+%     for my $t ( $SortCalendarEvents->( @{ $Tickets{ $date->strftime("%F") } || [] } )) {
         <& /Elements/CalendarEvent, Object => $t, Date => $date, DateTypes => \%DateTypes &>
 %     }
 
@@ -272,4 +272,5 @@ $m->callback( CallbackName => 'BeforeFindTickets', ARGSRef => \%ARGS, QueryRef =
 
 my %Tickets = RTx::Calendar::FindTickets($session{'CurrentUser'}, $TempQuery, \@Dates, $date->strftime("%F"), $end->strftime("%F"));
 
+my $SortCalendarEvents = RT->Config->Get("CalendarSortEvents");
 </%INIT>

commit 5cdffd11c94d64737002de69714f37486a7d0f08
Author: Ronaldo Richieri <ronaldo at bestpractical.com>
Date:   Tue Aug 29 19:22:28 2023 -0300

    Allow Date and Date/Time Custom Fields to be used in Calendar
    
    It's possible now to add __CustomField.{Custom Field Name}__ to search
    format so it will appear on calendar.

diff --git a/html/Elements/CalendarEvent b/html/Elements/CalendarEvent
index 5b8a6c5..11d090d 100644
--- a/html/Elements/CalendarEvent
+++ b/html/Elements/CalendarEvent
@@ -60,6 +60,9 @@ $DateTypes => undef
 %
 %    if ($attr =~ /(.*)->ISO$/ and $Object->$1->Unix <= 0) {
 %        $value = '-';
+%    } elsif ($attr =~ /CustomField\.\{(.*)\}$/) {
+%        my $cf = $1;
+%        $value = $Object->FirstCustomFieldValue($cf);
 %    } else {
 %        my $method = '$Object->'.$attr.'()';
 %        $method =~ s/->ISO\(\)$/->ISO( Timezone => 'user' )/;
@@ -123,6 +126,7 @@ for my $field (@display_fields) {
     my $label = $field;
     $label =~ s'Obj-.(?:AsString|Name|ISO)''g;
     $label =~ s'-\>MemberEmailAddressesAsString''g;
+    $label =~ s/CustomField\.\{(.*)\}/$1/g;
     $label_of{$field} = $label;
 }
 
diff --git a/html/Search/Calendar.html b/html/Search/Calendar.html
index 9c5e71e..fcaf314 100644
--- a/html/Search/Calendar.html
+++ b/html/Search/Calendar.html
@@ -154,12 +154,14 @@ and add something to the Query like that:
 <&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
 By default RTx::Calendar display Due and Starts dates. You can select other
 kind of events you want with the Display Columns section in the [_1].
-The following one will display the two latter and LastUpdated dates:
+The following one will display the two latter, LastUpdated and an additional
+date from a custom field called Maintenance Date:
 </&>
 <pre>
   '<small>__Due__</small>',
   '<small>__Starts__</small>',
-  '<small>__LastUpdated__</small>'
+  '<small>__LastUpdated__</small>',
+  '<small>__CustomField.{Maintenance Date}__</small>'
 </pre>
 </p>
 
@@ -243,7 +245,23 @@ $TempQuery  = $Query  if $Query;
 $TempFormat = $Format if $Format;
 
 # we search all date types in Format string
-my @Dates = grep { $TempFormat =~ m/__${_}(Relative)?__/ } @DateTypes;
+my @CoreDates    = grep { $TempFormat =~ m/__${_}(Relative)?__/ } @DateTypes;
+my @CustomFields = ( $TempFormat =~ /__(CustomField\.\{.*\})__/g );
+my @DateCustomFields;
+
+for my $CustomField (@CustomFields) {
+    my $LintCustomField = $CustomField;
+    $LintCustomField =~ s/CustomField\.\{(.*)\}/$1/;
+    my $CustomFieldObj = RT::CustomField->new( RT->SystemUser );
+    $CustomFieldObj->LoadByName( Name => $LintCustomField );
+    push @DateCustomFields, $CustomField
+        if $CustomFieldObj->id
+        && ( $CustomFieldObj->Type eq 'Date'
+        || $CustomFieldObj->Type eq 'DateTime' );
+}
+
+my @Dates = (@CoreDates, @DateCustomFields);
+ at Dates = map { $_ =~ s/^CustomField\.(.*)$/CF.$1/; $_ } @Dates;
 
 # used to display or not a date in Element/CalendarEvent
 my %DateTypes = map { $_ => 1 } @Dates;
diff --git a/lib/RTx/Calendar.pm b/lib/RTx/Calendar.pm
index 9bb375d..06b501a 100644
--- a/lib/RTx/Calendar.pm
+++ b/lib/RTx/Calendar.pm
@@ -71,14 +71,43 @@ sub FindTickets {
 
         # How to find the LastContacted date ?
         for my $Date (@$Dates) {
-            my $DateObj = $Date . "Obj";
-            push @{ $Tickets{ LocalDate( $Ticket->$DateObj->Unix ) } },
+
+            # $dateindex is the date to use as key in the Tickets Hash
+            # in the YYYY-MM-DD format
+            # Tickets are then groupd by date in the %Tickets hash
+            my $dateindex;
+            if ($Date =~ /^CF\./){
+                my $cf = $Date;
+                $cf =~ s/^CF\.\{(.*)\}/$1/;
+
+                my $CFDateValue = $Ticket->FirstCustomFieldValue($cf);
+                next unless $CFDateValue;
+                my $CustomFieldObj = RT::CustomField->new($CurrentUser);
+                $CustomFieldObj->LoadByName( Name => $cf );
+                my $CustomFieldObjType = $CustomFieldObj->Type;
+                my $DateObj            = RT::Date->new($CurrentUser);
+                if ( $CustomFieldObjType eq 'Date' ) {
+                    $DateObj->Set(
+                        Format   => 'unknown',
+                        Value    => $CFDateValue,
+                        Timezone => 'utc'
+                    );
+                } else {
+                    $DateObj->Set( Format => 'ISO', Value => $CFDateValue );
+                }
+                $dateindex = LocalDate( $DateObj->Unix );
+            } else {
+                my $DateObj = $Date . "Obj";
+                $dateindex = LocalDate( $Ticket->$DateObj->Unix );
+            }
+
+            push @{ $Tickets{$dateindex } },
                 $Ticket
 
                 # if reminder, check it's refering to a ticket
                 unless ( $Ticket->Type eq 'reminder'
                 and not $Ticket->RefersTo->First )
-                or $AlreadySeen{ LocalDate( $Ticket->$DateObj->Unix ) }
+                or $AlreadySeen{ $dateindex }
                 {$Ticket}++;
         }
     }

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


hooks/post-receive
-- 
rtx-calendar


More information about the Bps-public-commit mailing list