[Rt-commit] rt branch 5.0/create-rt-crontool-admin-interface created. rt-5.0.4-230-g121d09075d
BPS Git Server
git at git.bestpractical.com
Sun Sep 24 22:07:27 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/create-rt-crontool-admin-interface has been created
at 121d09075d286293b2bd6163ceef0fc7e2c30d9d (commit)
- Log -----------------------------------------------------------------
commit 121d09075d286293b2bd6163ceef0fc7e2c30d9d
Author: Brad Embree <brad at bestpractical.com>
Date: Sun Sep 24 14:18:35 2023 -0700
Add menu option for new Crontool list page
diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm
index 19d0e68920..c8e1eec4ae 100644
--- a/lib/RT/Interface/Web/MenuBuilder.pm
+++ b/lib/RT/Interface/Web/MenuBuilder.pm
@@ -1372,6 +1372,15 @@ sub _BuildAdminMenu {
path => '/Admin/Tools/Shortener.html',
);
+ if ( $current_user->HasRight( Right => 'SuperUser', Object => RT->System ) ) {
+ $admin_tools->child( 'crontool' => title => loc('Crontool Jobs'), path => "/Admin/Tools/Crontool.html" );
+
+ if ( $request_path =~ m{^/Admin/Tools/Crontool} ) {
+ $page->child( 'list' => title => loc('Select'), path => "/Admin/Tools/Crontool.html" );
+ $page->child( 'job' => title => loc('Create'), path => "/Admin/Tools/CrontoolJob.html" );
+ }
+ }
+
if ( $request_path =~ m{^/Admin/(Queues|Users|Groups|CustomFields|CustomRoles)} ) {
my $type = $1;
commit 3cac811729e87d6f20aa3a11f1b9b338a5cd6bde
Author: Brad Embree <brad at bestpractical.com>
Date: Sun Sep 24 14:17:19 2023 -0700
Add script to run scheduled crontool jobs
diff --git a/.gitignore b/.gitignore
index f07ed8350a..fbbe16bf98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
/bin/rt-crontool
/bin/rt-mailgate
/bin/rt
+/bin/rt-run-scheduled-crontool
/etc/RT_Config.pm
/etc/upgrade/3.8-ical-extension
/etc/upgrade/4.0-customfield-checkbox-extension
diff --git a/bin/rt-run-scheduled-crontool.in b/bin/rt-run-scheduled-crontool.in
new file mode 100644
index 0000000000..18e86b7741
--- /dev/null
+++ b/bin/rt-run-scheduled-crontool.in
@@ -0,0 +1,349 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2023 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;
+
+use POSIX 'tzset';
+
+# fix lib paths, some may be relative
+my $bin_path;
+BEGIN { # BEGIN RT CMD BOILERPLATE
+ require File::Spec;
+ require Cwd;
+ my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_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", "dryrun", "time=i", "all", "log=s"
+);
+
+if ( $opts{'help'} ) {
+ require Pod::Usage;
+ print Pod::Usage::pod2usage( -verbose => 2 );
+ exit;
+}
+
+$opts{dryrun} = 1
+ if $opts{all};
+
+require RT;
+require RT::Interface::CLI;
+
+# Load the config file
+RT::LoadConfig();
+
+# adjust logging to the screen according to options
+RT->Config->Set( LogToSTDERR => $opts{log} ) if $opts{log};
+
+RT::Init();
+
+$opts{time} ||= time;
+
+my $CrontoolJobs = RT::Attributes->new( RT->SystemUser );
+$CrontoolJobs->LimitToObject( RT->SystemUser );
+$CrontoolJobs->Limit(
+ FIELD => 'Name',
+ VALUE => 'Crontool',
+ OPERATOR => '=',
+ ENTRYAGGREGATOR => 'AND',
+);
+
+my ( $minute, $hour, $dow, $dom ) = MinuteHourDowDomIn( $opts{time}, RT->Config->Get('Timezone') );
+$hour .= ':00';
+$RT::Logger->debug( "Checking crontool jobs: minute $minute, hour $hour, dow $dow, dom $dom" );
+
+while ( my $job = $CrontoolJobs->Next ) {
+ next unless IsCrontoolReady(
+ %opts,
+ Crontool => $job,
+ LocalTime => [ $minute, $hour, $dow, $dom ],
+ );
+
+ my $crontool_success = RunCrontoolJob(
+ %opts,
+ Crontool => $job,
+ );
+
+ if ( $crontool_success ) {
+ my $counter = $job->SubValue('Counter') || 0;
+ $job->SetSubValues( Counter => $counter + 1 )
+ unless $opts{dryrun};
+ }
+}
+
+sub IsCrontoolReady {
+ my %args = (
+ all => 0,
+ Crontool => undef,
+ LocalTime => [0, 0, 0, 0],
+ @_,
+ );
+
+ my $crontool = $args{Crontool};
+
+ return 1 if $args{all};
+
+ my $counter = $crontool->SubValue('Counter') || 0;
+ my $sub_frequency = $crontool->SubValue('Frequency');
+ my $sub_minute = $crontool->SubValue('Minute');
+ my $sub_hour = $crontool->SubValue('Hour');
+ my $sub_dow = $crontool->SubValue('Dow');
+ my $sub_dom = $crontool->SubValue('Dom');
+ my $sub_fow = $crontool->SubValue('Fow') || 1;
+
+ my $log_frequency = $sub_frequency;
+ if ( $log_frequency eq 'daily' ) {
+ my $days
+ = join ' ',
+ grep { $crontool->SubValue($_) }
+ qw/Monday Tuesday Wednesday Thursday Friday Saturday Sunday/;
+ $log_frequency = "$log_frequency ($days)";
+ }
+
+ my ( $minute, $hour, $dow, $dom ) = @{ $args{LocalTime} };
+
+ $RT::Logger->debug( "Checking crontool job " . $crontool->Id . " with frequency $log_frequency, minute $sub_minute, hour $sub_hour, dow $sub_dow, dom $sub_dom, fow $sub_fow, counter $counter" );
+
+ return 0 if $sub_frequency eq 'never';
+
+ # correct minute?
+ return 0 if $sub_minute ne $minute;
+
+ # correct hour?
+ return 0 if $sub_hour ne $hour;
+
+ if ( $sub_frequency eq 'daily' ) {
+ return $crontool->SubValue($dow) ? 1 : 0;
+ }
+
+ if ( $sub_frequency eq 'weekly' ) {
+ # correct day of week?
+ return 0 if $sub_dow ne $dow;
+
+ # does it match the "every N weeks" clause?
+ return 1 if $counter % $sub_fow == 0;
+
+ $crontool->SetSubValues( Counter => $counter + 1 )
+ unless $args{dryrun};
+
+ return 0;
+ }
+
+ # if monthly, correct day of month?
+ if ( $sub_frequency eq 'monthly' ) {
+ return $sub_dom == $dom;
+ }
+
+ $RT::Logger->debug( "Invalid frequency $sub_frequency for crontool job: " . $crontool->Id . ' - ' . $crontool->SubValue('Description') );
+
+ # unknown frequency type, bail out
+ return 0;
+}
+
+sub RunCrontoolJob {
+ my %args = (
+ Crontool => undef,
+ dryrun => 0,
+ @_,
+ );
+
+ my $crontool = $args{Crontool};
+ my $content = $crontool->Content;
+
+ $RT::Logger->debug( "running crontool job: " . $crontool->Id );
+
+ # build command line
+ my $cmd = "${bin_path}rt-crontool ";
+
+ if ( my $search_module = $content->{SearchModule} ) {
+ $cmd .= "--search RT::Search::$search_module ";
+ $cmd .= '--search-arg "' . $content->{SearchModuleArg} . '" '
+ if $content->{SearchModuleArg};
+ }
+ if ( my $condition_module = $content->{ConditionModule} ) {
+ $cmd .= "--condition RT::Condition::$condition_module ";
+ $cmd .= '--condition-arg "' . $content->{ConditionModuleArg} . '" '
+ if $content->{ConditionModuleArg};
+ }
+ if ( my $action_module = $content->{ActionModule} ) {
+ $cmd .= "--action RT::Action::$action_module ";
+ $cmd .= '--action-arg "' . $content->{ActionModuleArg} . '" '
+ if $content->{ActionModuleArg};
+ }
+ if ( my $template = $content->{Template} ) {
+ $cmd .= "--template '$template' ";
+ }
+ $cmd .= "--transaction '" . $content->{Transaction} . "' ";
+
+ if ( $content->{TransactionTypes} ne 'all' ) {
+ $cmd .= "--transaction-type '" . $content->{TransactionTypes} . "' ";
+ }
+
+ $cmd .= "--reload-ticket "
+ if $content->{ReloadTicket};
+
+ if ( $args{dryrun} ) {
+ print "dryrun: $cmd\n";
+
+ return;
+ }
+
+ # run command line and capture return
+ my $return = `$cmd`;
+
+ $RT::Logger->debug( "crontool job output: $return\n" );
+
+ return $return ? 0 : 1;
+}
+
+{
+ my %cache;
+
+ sub MinuteHourDowDomIn {
+ my $now = shift;
+ my $tz = shift;
+
+ my $key = "$now $tz";
+ return @{ $cache{$key} } if exists $cache{$key};
+
+ my ( $minute, $hour, $dow, $dom );
+
+ {
+ local $ENV{'TZ'} = $tz;
+ ## Using POSIX::tzset fixes a bug where the TZ environment variable
+ ## is cached.
+ tzset();
+ ( undef, $minute, $hour, $dom, undef, undef, $dow ) = localtime($now);
+ }
+ tzset(); # return back previous value
+
+ $minute = "0$minute"
+ if length($minute) == 1;
+ $hour = "0$hour"
+ if length($hour) == 1;
+ $dow = (qw/Sunday Monday Tuesday Wednesday Thursday Friday Saturday/)[$dow];
+
+ return @{ $cache{$key} } = ( $minute, $hour, $dow, $dom) ;
+ }
+}
+
+=head1 NAME
+
+rt-run-scheduled-crontool - Check for scheduled crontool jobs and run
+them
+
+=head1 SYNOPSIS
+
+ rt-run-scheduled-crontool [options]
+
+=head1 DESCRIPTION
+
+This tool will find any crontool jobs that are scheduled and run them.
+
+Each crontool job has a minute and hour, and possibly day of week or day
+of month. These are taken to be in the RT server timezone.
+
+=head1 SETUP
+
+You'll need to have cron run this script every 15 minutes. Here's an
+example crontab entry to do this.
+
+ */15 * * * * @RT_SBIN_PATH_R@/rt-run-scheduled-crontool
+
+This will run the script every 15 minutes, every hour. This may need
+some further tweaking to be run as the correct user.
+
+=head1 OPTIONS
+
+This tool supports a few options. Most are for debugging.
+
+=over 8
+
+=item -h
+
+=item --help
+
+Display this documentation
+
+=item --dryrun
+
+Figure out which crontool jobs would be run, but don't actually run them
+
+=item --time SECONDS
+
+Instead of using the current time to figure out which crontool jobs
+should be run, use SECONDS (usually since midnight Jan 1st, 1970, so
+C<1192216018> would be Oct 12 19:06:58 GMT 2007).
+
+=item --all
+
+Ignore crontool job frequency when considering each crontool job
+(--dryrun is implied when using --all)
+
+=item --log LEVEL
+
+Adjust LogToSTDERR config option
+
+=back
+
+=cut
+
diff --git a/configure.ac b/configure.ac
index dd34674364..2b7f46f154 100755
--- a/configure.ac
+++ b/configure.ac
@@ -493,6 +493,7 @@ AC_CONFIG_FILES([
sbin/rt-passwd
sbin/rt-munge-attachments
bin/rt-crontool
+ bin/rt-run-scheduled-crontool
bin/rt-mailgate
bin/rt],
[chmod ug+x $ac_file]
commit fbf4a2eab06db0c0a829a8dfc97308ef46f9d9b5
Author: Brad Embree <brad at bestpractical.com>
Date: Sun Sep 24 14:16:39 2023 -0700
Add Crontool list page
diff --git a/share/html/Admin/Tools/Crontool.html b/share/html/Admin/Tools/Crontool.html
new file mode 100644
index 0000000000..6f0196fcc3
--- /dev/null
+++ b/share/html/Admin/Tools/Crontool.html
@@ -0,0 +1,129 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2023 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 }}}
+<& /Elements/Header, Title => 'Crontool Jobs' &>
+<& /Elements/Tabs &>
+
+<&| /Widgets/TitleBox, title => loc('Crontool Jobs') &>
+<div class="table-responsive">
+ <table cellspacing="0" class="table collection collection-as-table">
+ <colgroup>
+ <col>
+ <col>
+ <col>
+ </colgroup>
+ <tr class="collection-as-table">
+ <th class="collection-as-table">
+ <span class="title">Description</span>
+ </th>
+ <th class="collection-as-table">
+ <span class="title">Frequency</span>
+ </th>
+ <th class="collection-as-table" style="text-align: right">
+ <span class="title"></span>
+ </th>
+ </tr>
+% my $rowcount = 1;
+% foreach my $cronjob ( @cronjobs ) {
+ <tbody class="list-item">
+ <tr class="<% $rowcount % 2 ? 'oddline' : 'evenline' %>" >
+ <td class="collection-as-table" ><% $cronjob->{description} %></td>
+ <td class="collection-as-table" ><% $cronjob->{frequency} %></td>
+ <td class="collection-as-table" align="right"><a href="/Admin/Tools/CrontoolJob.html?id=<% $cronjob->{id} %>">Edit</a></td>
+ </tr>
+ </tbody>
+% $rowcount++;
+% }
+ </table>
+</div>
+</&>
+
+<%INIT>
+my $CrontoolJobs = RT::Attributes->new( RT->SystemUser );
+$CrontoolJobs->LimitToObject( RT->SystemUser );
+$CrontoolJobs->Limit(
+ FIELD => 'Name',
+ VALUE => 'Crontool',
+ OPERATOR => '=',
+ ENTRYAGGREGATOR => 'AND',
+);
+
+my @cronjobs;
+while ( my $cronjob = $CrontoolJobs->Next ) {
+ my $frequency = $cronjob->SubValue('Frequency');
+ my $hour = substr( $cronjob->SubValue('Hour'), 0, 3 );
+ my $minute = $cronjob->SubValue('Minute');
+ my $timezone = RT->Config->Get('Timezone');
+
+ my $frequency_details = '';
+ if ( $frequency eq 'daily' ) {
+ my @days;
+ foreach my $day ( qw( Monday Tuesday Wednesday Thursday Friday Saturday Sunday ) ) {
+ if ( $cronjob->SubValue($day) ) {
+ push @days, $day;
+ }
+ }
+ $frequency_details = ' on ' . join ', ', map { substr( $_, 0, 2 ) } @days;
+ }
+ elsif ( $frequency eq 'weekly' ) {
+ $frequency_details = 'on ' . $cronjob->SubValue('Dow') . ' every ' . $cronjob->SubValue('Fow') . ' ' . ( $cronjob->SubValue('Fow') == 1 ? 'week' : 'weeks' );
+ }
+ elsif ( $frequency eq 'monthly' ) {
+ $frequency_details = 'on day ' . $cronjob->SubValue('Dom');
+ }
+ $frequency_details .= " at $hour$minute $timezone"
+ if $frequency_details;
+
+ push @cronjobs, {
+ description => $cronjob->SubValue('Description'),
+ frequency => "$frequency $frequency_details",
+ id => $cronjob->id,
+ };
+}
+</%INIT>
+<%ARGS>
+</%ARGS>
commit ae363619a753908e22f2026d449ca26389005b58
Author: Brad Embree <brad at bestpractical.com>
Date: Sun Sep 24 14:15:52 2023 -0700
Add Crontool Job Create/Edit page
diff --git a/share/html/Admin/Tools/CrontoolJob.html b/share/html/Admin/Tools/CrontoolJob.html
new file mode 100644
index 0000000000..f77cddb59d
--- /dev/null
+++ b/share/html/Admin/Tools/CrontoolJob.html
@@ -0,0 +1,565 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2023 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 }}}
+<& /Elements/Header, Title => 'Crontool Jobs' &>
+<& /Elements/Tabs &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+<&| /Widgets/TitleBox, title => $CrontoolObj ? loc( 'Edit Crontool Job - [_1]', $fields{Description} ) : loc('Create New Crontool Job') &>
+
+<form action="<% RT->Config->Get('WebPath') %>/Admin/Tools/CrontoolJob.html?id=<% $fields{'CrontoolId'} %>" method="post" enctype="multipart/form-data" name="AddCrontool">
+
+<&| /Widgets/TitleBox, title => loc('Details') &>
+
+<&| /Elements/LabeledValue, Label => loc('Description'), Class => 'edit-custom-field' &>
+<div class="row">
+ <div class="col-6">
+ <input name="Description" type="text" value="<% $fields{'Description'} || '' %>" size="50" class="form-control" />
+ </div>
+</div>
+</&>
+
+<&| /Elements/LabeledValue, Label => loc('Search'), Class => 'edit-custom-field' &>
+<div class="row">
+ <div class="col-3">
+ <select name="SearchModule" class="form-control selectpicker">
+ <option value="" >-</option>
+% for my $module ( sort keys %{ $META{Search} } ) {
+% my $selected = $module eq $fields{'SearchModule'}
+% ? 'selected="selected"'
+% : '';
+ <option value="<% $module %>" <%$selected|n %>><% $module %></option>
+% }
+ </select>
+ </div>
+ <div class="col-3">
+ <input name="SearchModuleArg" type="text" value="<% $fields{'SearchModuleArg'} || '' %>" size="20" class="form-control" />
+ </div>
+ <div class="col-3 <% $fields{'SearchModule'} ? '' : 'hidden' %>">
+ <a id="search-module-docs" target="_blank" href="<% $fields{'SearchModule'} ? 'https://docs.bestpractical.com/rt/latest/RT/Search/' . $fields{'SearchModule'} . '.html' : '' %>"><% loc('Documentation') %></a>
+ </div>
+ </div>
+</&>
+
+<&| /Elements/LabeledValue, Label => loc('Condition'), Class => 'edit-custom-field' &>
+<div class="row">
+ <div class="col-3">
+ <select name="ConditionModule" class="form-control selectpicker">
+ <option value="" >-</option>
+% for my $module ( sort keys %{ $META{Condition} } ) {
+% my $selected = $module eq $fields{'ConditionModule'}
+% ? 'selected="selected"'
+% : '';
+ <option value="<% $module %>" <%$selected|n %>><% $module %></option>
+% }
+ </select>
+ </div>
+ <div class="col-3">
+ <input name="ConditionModuleArg" type="text" value="<% $fields{'ConditionModuleArg'} || '' %>" size="20" class="form-control" />
+ </div>
+ <div class="col-3 <% $fields{'ConditionModule'} ? '' : 'hidden' %>">
+ <a id="condition-module-docs" target="_blank" href="<% $fields{'ConditionModule'} ? 'https://docs.bestpractical.com/rt/latest/RT/Condition/' . $fields{'ConditionModule'} . '.html' : '' %>"><% loc('Documentation') %></a>
+ </div>
+ </div>
+</&>
+
+<&| /Elements/LabeledValue, Label => loc('Action Module'), Class => 'edit-custom-field' &>
+<div class="row">
+ <div class="col-3">
+ <select name="ActionModule" class="form-control selectpicker">
+ <option value="" >-</option>
+% for my $module ( sort keys %{ $META{Action} } ) {
+% my $selected = $module eq $fields{'ActionModule'}
+% ? 'selected="selected"'
+% : '';
+ <option value="<% $module %>" <%$selected|n %>><% $module %></option>
+% }
+ </select>
+ </div>
+ <div class="col-3">
+ <input name="ActionModuleArg" type="text" value="<% $fields{'ActionModuleArg'} || '' %>" size="20" class="form-control" />
+ </div>
+ <div class="col-3 <% $fields{'ActionModule'} ? '' : 'hidden' %>">
+ <a id="action-module-docs" target="_blank" href="<% $fields{'ActionModule'} ? 'https://docs.bestpractical.com/rt/latest/RT/Action/' . $fields{'ActionModule'} . '.html' : '' %>"><% loc('Documentation') %></a>
+ </div>
+ </div>
+</&>
+
+<&| /Elements/LabeledValue, Label => loc('Template'), Class => 'edit-custom-field' &>
+<& /Admin/Scrips/Elements/SelectTemplate, Default => $fields{'Template'}, Class => 'col-6' &>
+</&>
+
+<&| /Elements/LabeledValue, Label => loc('Transaction'), Class => 'edit-custom-field cfrendertype-Checkbox' &>
+<div class="form-row">
+ <div class="col-6">
+ <div class="custom-control custom-radio">
+ <input type="radio" id="Transaction-first" name="Transaction" value="first" <% $fields{'Transaction'} eq 'first' ? 'checked="checked"' : "" |n %> class="custom-control-input">
+ <label class="custom-control-label" for="Transaction-first"><&|/l&>First Transaction</&></label>
+ </div>
+ <div class="custom-control custom-radio">
+ <input type="radio" id="Transaction-last" name="Transaction" value="last" <% $fields{'Transaction'} eq 'last' ? 'checked="checked"' : "" |n %> class="custom-control-input">
+ <label class="custom-control-label" for="Transaction-last"><&|/l&>Last Transaction</&></label>
+ </div>
+ <div class="custom-control custom-radio">
+ <input type="radio" id="Transaction-all" name="Transaction" value="all" <% $fields{'Transaction'} eq 'all' ? 'checked="checked"' : "" |n %> class="custom-control-input">
+ <label class="custom-control-label" for="Transaction-all"><&|/l&>All Transactions</&></label>
+ </div>
+ </div>
+</div>
+</&>
+
+<&| /Elements/LabeledValue, Label => loc('Transaction Types'), Class => 'edit-custom-field cfrendertype-Checkbox' &>
+<div class="form-row" id="transaction-types-all">
+ <div class="col-3">
+ <div class="custom-control custom-checkbox">
+ <input type="checkbox" id="Transaction-Type-ALL" name="Transaction-Type-ALL" class="custom-control-input" value="ALL" <% $fields{TransactionTypes} eq 'all' ? 'checked="checked"' : '' %>>
+ <label class="custom-control-label" for="Transaction-Type-ALL"><% loc('All Types') %></label>
+ </div>
+ </div>
+</div>
+<div class="form-row <% $fields{TransactionTypes} eq 'all' ? 'hidden' : '' %>" id="transaction-types-list">
+% for my $type ( sort keys %RT::Transaction::_BriefDescriptions ) {
+ <div class="col-3">
+ <div class="custom-control custom-checkbox">
+ <input type="checkbox" id="Transaction-Type-<% $type %>" name="Transaction-Type-<% $type %>" class="custom-control-input" value="<% $type %>" <% $fields{TransactionTypes} eq 'all' || $transaction_types{$type} ? 'checked="checked"' : '' %>>
+ <label class="custom-control-label" for="Transaction-Type-<% $type %>"><% loc($type) %></label>
+ </div>
+ </div>
+% }
+</div>
+</&>
+
+<&| /Elements/LabeledValue, Label => loc('Reload Ticket'), Class => 'edit-custom-field cfrendertype-Checkbox' &>
+ <div class="custom-control custom-checkbox edit-custom-field">
+ <input type="checkbox" id="ReloadTicket" name="ReloadTicket" class="custom-control-input" value="1" <% $fields{ReloadTicket} ? 'checked="checked"' : '' %>>
+ <label class="custom-control-label" for="ReloadTicket"><% loc('Reload ticket before processing in tickets iteration') %></label>
+ </div>
+</&>
+
+</&>
+
+<&| /Widgets/TitleBox, title => loc('Schedule') &>
+<&| /Elements/LabeledValue, Label => loc('Frequency'), Class => 'edit-custom-field cfrendertype-Checkbox' &>
+ <div class="form-row">
+ <div class="col-auto">
+ <div class="custom-control custom-radio">
+ <input type="radio" id="Frequency-daily" name="Frequency" value="daily" <% $fields{'Frequency'} eq 'daily' ? 'checked="checked"' : "" |n %> class="custom-control-input">
+ <label class="custom-control-label" for="Frequency-daily"><&|/l&>daily, on</&></label>
+ </div>
+ </div>
+% for my $day ( qw/Monday Tuesday Wednesday Thursday Friday Saturday Sunday/ ) {
+ <div class="col-auto">
+ <input type="hidden" class="hidden" name="<% $day %>-Magic" value="1" />
+ <div class="custom-control custom-checkbox">
+ <input type="checkbox" id="Frequency-daily-<% $day %>" name="<% $day %>" class="custom-control-input" value="1" <% $fields{$day} ? 'checked="checked"' : '' %>>
+ <label class="custom-control-label" for="Frequency-daily-<% $day %>"><% loc($day) %></label>
+ </div>
+ </div>
+% }
+ </div>
+ <div class="form-row">
+ <div class="col-auto">
+ <span class="current-value form-control">
+ <div class="custom-control custom-radio">
+ <input type="radio" id="Frequency-weekly" name="Frequency" value="weekly" <% $fields{'Frequency'} eq 'weekly' ? 'checked="checked"' : "" |n %> class="custom-control-input">
+ <label class="custom-control-label" for="Frequency-weekly"><&|/l&>weekly</&>, <&|/l&>on</&></label>
+ </div>
+ </span>
+ </div>
+ <div class="col-auto">
+ <select name="Dow" class="form-control selectpicker">
+ <option value="Monday" <% $fields{'Dow'} eq 'Monday' ? 'selected="selected"' : '' |n %>><&|/l&>Monday</&></option>
+ <option value="Tuesday" <% $fields{'Dow'} eq 'Tuesday' ? 'selected="selected"' : '' |n %>><&|/l&>Tuesday</&></option>
+ <option value="Wednesday" <% $fields{'Dow'} eq 'Wednesday' ? 'selected="selected"' : '' |n %>><&|/l&>Wednesday</&></option>
+ <option value="Thursday" <% $fields{'Dow'} eq 'Thursday' ? 'selected="selected"' : '' |n %>><&|/l&>Thursday</&></option>
+ <option value="Friday" <% $fields{'Dow'} eq 'Friday' ? 'selected="selected"' : '' |n %>><&|/l&>Friday</&></option>
+ <option value="Saturday" <% $fields{'Dow'} eq 'Saturday' ? 'selected="selected"' : '' |n %>><&|/l&>Saturday</&></option>
+ <option value="Sunday" <% $fields{'Dow'} eq 'Sunday' ? 'selected="selected"' : '' |n %>><&|/l&>Sunday</&></option>
+ </select>
+ </div>
+ <div class="col-auto">
+ <span class="current-value form-control"><&|/l&>every</&></span>
+ </div>
+ <div class="col-auto">
+ <select name="Fow" class="form-control selectpicker">
+% for my $f ( qw/1 2 3 4/ ) {
+ <option value="<%$f%>" <% $fields{'Fow'} == $f ? 'selected="selected"' : '' |n %>><% $f %></option>
+% }
+ </select>
+ </div>
+ <div class="col-auto">
+ <span class="current-value form-control"><&|/l&>weeks</&></span>
+ </div>
+ </div>
+ <div class="form-row">
+ <div class="col-auto">
+ <span class="current-value form-control">
+ <div class="custom-control custom-radio">
+ <input type="radio" id="Frequency-monthly" name="Frequency" value="monthly" <% $fields{'Frequency'} eq 'monthly' ? 'checked="checked"' : "" |n %> class="custom-control-input">
+ <label class="custom-control-label" for="Frequency-monthly"><&|/l&>monthly</&>, <&|/l&>on day</&></label>
+ </div>
+ </span>
+ </div>
+ <div class="col-auto">
+ <select name="Dom" class="form-control selectpicker">
+% for my $dom (1..31) {
+ <option value="<% $dom %>" <% $fields{'Dom'} == $dom ? 'selected="selected"' : '' |n %>><% loc($dom) %></option>
+% }
+ </select>
+ </div>
+ </div>
+ <div class="form-row">
+ <div class="col-auto">
+ <span class="current-value form-control">
+ <div class="custom-control custom-radio">
+ <input type="radio" id="Frequency-never" name="Frequency" value="never" <% $fields{'Frequency'} eq 'never' ? 'checked="checked"' : "" |n %> class="custom-control-input">
+ <label class="custom-control-label" for="Frequency-never"><&|/l&>never</&></label>
+ </div>
+ </span>
+ </div>
+ </div>
+ </&>
+
+ <&| /Elements/LabeledValue, Label => loc('Hour'), Class => 'edit-custom-field' &>
+ <div class="row">
+ <div class="col-auto">
+ <select name="Hour" class="form-control selectpicker">
+% my $formatter = RT::Date->new(RT->SystemUser)->LocaleObj;
+% my $dt = DateTime->now;
+% $dt->set_minute(0);
+% $dt->set_second(0);
+
+% for my $hour (0..23) {
+% $dt->set_hour($hour);
+% my $formatted = $dt->format_cldr($formatter->time_format_short);
+
+% my $value = sprintf '%02d:00', $hour;
+% my $selected = $value eq $fields{'Hour'}
+% ? 'selected="selected"'
+% : '';
+
+ <option value="<% $value %>" <%$selected|n %>><% $formatted %></option>
+% }
+ </select>
+ </div>
+ <div class="col-auto">
+ <span class="current-value form-control">(<%$timezone%>)</span>
+ </div>
+ </div>
+ </&>
+
+ <&| /Elements/LabeledValue, Label => loc('Minute'), Class => 'edit-custom-field' &>
+ <div class="row">
+ <div class="col-auto">
+ <select name="Minute" class="form-control selectpicker">
+% for my $minutes ( qw( 00 15 30 45 ) ) {
+% my $selected = $minutes eq $fields{'Minute'}
+% ? 'selected="selected"'
+% : '';
+ <option value="<% $minutes %>" <%$selected|n %>><% $minutes %></option>
+% }
+ </select>
+ </div>
+ </div>
+ </&>
+</&>
+
+<div class="form-row">
+ <div class="col-12">
+% if ($CrontoolObj) {
+ <& /Elements/Submit, Name => "Save", Label => loc('Save Changes') &>
+% } else {
+ <& /Elements/Submit, Name => "Save", Label => loc('Create') &>
+% }
+ </div>
+ </div>
+
+</form>
+</&>
+<script type="text/javascript">
+ jQuery( function () {
+ jQuery('div#transaction-types-all input[name=Transaction-Type-ALL]').change( function () {
+ console.log('foobar');
+ if ( jQuery(this).is(':checked') ) {
+ jQuery('div#transaction-types-list').addClass('hidden');
+ }
+ else {
+ jQuery('div#transaction-types-list').removeClass('hidden');
+ }
+ } );
+
+ jQuery('select[name=SearchModule]').change( function () {
+ var val = jQuery(this).val();
+ jQuery('a#search-module-docs').attr( 'href', 'https://docs.bestpractical.com/rt/latest/RT/Search/' + val + '.html' );
+ if ( val == "" ) {
+ jQuery('a#search-module-docs').parent().addClass('hidden');
+ }
+ else {
+ jQuery('a#search-module-docs').parent().removeClass('hidden');
+ }
+ } );
+ jQuery('select[name=ConditionModule]').change( function () {
+ var val = jQuery(this).val();
+ jQuery('a#condition-module-docs').attr( 'href', 'https://docs.bestpractical.com/rt/latest/RT/Condition/' + val + '.html' );
+ if ( val == "" ) {
+ jQuery('a#condition-module-docs').parent().addClass('hidden');
+ }
+ else {
+ jQuery('a#condition-module-docs').parent().removeClass('hidden');
+ }
+ } );
+ jQuery('select[name=ActionModule]').change( function () {
+ var val = jQuery(this).val();
+ jQuery('a#action-module-docs').attr( 'href', 'https://docs.bestpractical.com/rt/latest/RT/Action/' + val + '.html' );
+ if ( val == "" ) {
+ jQuery('a#action-module-docs').parent().addClass('hidden');
+ }
+ else {
+ jQuery('a#action-module-docs').parent().removeClass('hidden');
+ }
+ } );
+ });
+</script>
+<%INIT>
+my %META = (
+ Search => {
+ ActiveTicketsInQueue => { argument => 1 },
+ FromSQL => { argument => 1 },
+ Simple => { argument => 1 },
+ },
+ Condition => {
+ BeforeDue => { argument => 1 },
+ CloseTicket => { argument => 0 },
+ Overdue => { argument => 0 },
+ OwnerChange => { argument => 0 },
+ PriorityChange => { argument => 0 },
+ PriorityExceeds => { argument => 1 },
+ QueueChange => { argument => 0 },
+ ReopenTicket => { argument => 0 },
+ SLA => { argument => 0 },
+ SLA_RequireDueSet => { argument => 0 },
+ SLA_RequireStartsSet => { argument => 0 },
+ StatusChange => { argument => 1 },
+ TimeWorkedChange => { argument => 0 },
+ ViaInterface => { argument => 1 },
+ },
+ Action => {
+ AddPriority => { argument => 1 },
+ AutoOpen => { argument => 0 },
+ AutoOpenInactive => { argument => 0 },
+ Autoreply => { argument => 0 },
+ ClearCustomFieldValues => { argument => 1 },
+ CreateTickets => { argument => 0 },
+ EscalatePriority => { argument => 1 },
+ ExtractSubjectTag => { argument => 0 },
+ LinearEscalate => { argument => 1 },
+ Notify => { argument => 1 },
+ NotifyAsComment => { argument => 1 },
+ NotifyGroup => { argument => 1 },
+ NotifyGroupAsComment => { argument => 1 },
+ NotifyOwnerOrAdminCc => { argument => 1 },
+ OpenOnStarted => { argument => 0 },
+ RecordComment => { argument => 0 },
+ RecordCorrespondence => { argument => 0 },
+ SLA_SetDue => { argument => 0 },
+ SLA_SetStarts => { argument => 0 },
+ SendForward => { argument => 1 },
+ SetSetCustomFieldToNow => { argument => 1 },
+ SetPriority => { argument => 1 },
+ SetStatus => { argument => 1 },
+ UpdateParentTimeWorked => { argument => 0 },
+ },
+);
+
+my @results = ();
+my %fields = (
+ CrontoolId => $id,
+ Description => '',
+ SearchModule => '',
+ SearchModuleArg => '',
+ ConditionModule => '',
+ ConditionModuleArg => '',
+ ActionModule => '',
+ ActionModuleArg => '',
+ Frequency => 'daily',
+ Monday => 1,
+ Tuesday => 1,
+ Wednesday => 1,
+ Thursday => 1,
+ Friday => 1,
+ Saturday => 0,
+ Sunday => 0,
+ Hour => '06:00',
+ Minute => '0',
+ Dow => 'Monday',
+ Dom => 1,
+ Fow => 1,
+ Counter => 0,
+ Transaction => 'first',
+ TransactionTypes => 'all',
+ Template => '',
+ ReloadTicket => 0,
+);
+my $timezone = RT->Config->Get('Timezone');
+
+my $CrontoolJobs = RT::Attributes->new( RT->SystemUser );
+$CrontoolJobs->LimitToObject( RT->SystemUser );
+$CrontoolJobs->Limit(
+ FIELD => 'Name',
+ VALUE => 'Crontool',
+ OPERATOR => '=',
+ ENTRYAGGREGATOR => 'AND',
+);
+
+my @cronjobs;
+while ( my $cronjob = $CrontoolJobs->Next ) {
+ my $frequency = $cronjob->SubValue('Frequency');
+ my $hour = substr( $cronjob->SubValue('Hour'), 0, 3 );
+ my $minute = $cronjob->SubValue('Minute');
+ my $timezone = RT->Config->Get('Timezone');
+
+ my $frequency_details = '';
+ if ( $frequency eq 'daily' ) {
+ my @days;
+ foreach my $day ( qw( Monday Tuesday Wednesday Thursday Friday Saturday Sunday ) ) {
+ if ( $cronjob->SubValue($day) ) {
+ push @days, $day;
+ }
+ }
+ $frequency_details = join ', ', map { substr( $_, 0, 2 ) } @days;
+ }
+ elsif ( $frequency eq 'weekly' ) {
+ $frequency_details = 'on ' . $cronjob->SubValue('Dow') . ' every ' . $cronjob->SubValue('Fow') . ' ' . ( $cronjob->SubValue('Fow') == 1 ? 'week' : 'weeks' );
+ }
+ elsif ( $frequency eq 'monthly' ) {
+ $frequency_details = 'on day ' . $cronjob->SubValue('Dom');
+ }
+ push @cronjobs, {
+ description => $cronjob->Description,
+ frequency => $frequency_details,
+ id => $cronjob->id,
+ };
+}
+
+my $CrontoolObj;
+if ( $id ) {
+ $CrontoolObj = RT::Attribute->new( RT->SystemUser );
+ my ( $ok, $msg ) = $CrontoolObj->LoadById($id);
+
+ if ( $ok ) {
+ $fields{CrontoolId} = $id;
+ for my $field ( keys %fields ) {
+ $fields{$field} = $CrontoolObj->SubValue($field)
+ if defined $CrontoolObj->SubValue($field);
+ }
+ }
+ else {
+ RT->Logger->warning("Could not load Crontool Attribute $id: $msg");
+ }
+}
+
+# this'll be defined on submit
+if ( defined $ARGS{Save} ) {
+ # update fields with arguments passed in by the user
+ if ( $ARGS{'Transaction-Type-ALL'} ) {
+ $fields{TransactionTypes} = 'all';
+ }
+ else {
+ $fields{TransactionTypes} = join ',', map { $ARGS{$_} } grep { $_ =~ /^Transaction-Type-/ } keys %ARGS;
+ }
+
+ for my $field ( keys %fields ) {
+ next if $field eq 'CrontoolId'; # immutable
+ next if $field eq 'TransactionTypes'; # handled above
+ $fields{$field} = $ARGS{$field}
+ if defined($ARGS{$field}) || $ARGS{$field.'-Magic'};
+ }
+
+ # update
+ $id = delete $fields{'CrontoolId'}; # immutable
+ if ( $CrontoolObj ) {
+ my ( $ok, $msg ) = $CrontoolObj->SetSubValues(%fields);
+ ( $ok, $msg ) = $CrontoolObj->SetDescription( $fields{Description} )
+ if $ok && $CrontoolObj->SubValue('Description') ne $fields{Description};
+
+ $msg = loc("Crontool updated") if $ok;
+ push @results, $msg;
+ }
+ # create
+ else {
+ $CrontoolObj = RT::Attribute->new( RT->SystemUser );
+ my ( $ok, $msg ) = $CrontoolObj->Create(
+ Name => 'Crontool',
+ Description => $fields{Description},
+ ContentType => 'storable',
+ Object => RT->SystemUser,
+ Content => \%fields,
+ );
+ if ( $ok ) {
+ push @results, loc("Crontool created");
+ }
+ else {
+ push @results, loc('Crontool could not be created: [_1]', $msg);
+ }
+ }
+ $fields{'CrontoolId'} = $id;
+}
+
+my %transaction_types;
+foreach my $type ( split ',', $fields{TransactionTypes} ) {
+ $transaction_types{$type} = 1;
+}
+</%INIT>
+<%ARGS>
+$id => undef
+$Frequency => undef
+$Hour => undef
+$Dow => undef
+$Dom => undef
+</%ARGS>
+
-----------------------------------------------------------------------
hooks/post-receive
--
rt
More information about the rt-commit
mailing list