From 9ea57c250680a0e68349761745b1d1bf213084e0 Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Fri, 17 Jun 2022 11:13:19 +0200 Subject: [PATCH] Bug 30982: Use the REST API for background job list view Signed-off-by: Martin Renvoize --- Koha/BackgroundJob.pm | 14 + Koha/REST/V1/BackgroundJobs.pm | 58 ++++ admin/background_jobs.pl | 33 -- .../prog/en/modules/admin/background_jobs.tt | 286 +++++++++++------- 4 files changed, 243 insertions(+), 148 deletions(-) create mode 100644 Koha/REST/V1/BackgroundJobs.pm diff --git a/Koha/BackgroundJob.pm b/Koha/BackgroundJob.pm index dcfba14659..b34ea069ca 100644 --- a/Koha/BackgroundJob.pm +++ b/Koha/BackgroundJob.pm @@ -454,6 +454,20 @@ sub plugin_types_to_classes { return $self->{_plugin_mapping}; } +=head3 to_api_mapping + +This method returns the mapping for representing a Koha::BackgroundJob object +on the API. + +=cut + +sub to_api_mapping { + return { + id => 'background_job_id', + borrowernumber => 'patron_id', + }; +} + =head3 _type =cut diff --git a/Koha/REST/V1/BackgroundJobs.pm b/Koha/REST/V1/BackgroundJobs.pm new file mode 100644 index 0000000000..e7deaa353c --- /dev/null +++ b/Koha/REST/V1/BackgroundJobs.pm @@ -0,0 +1,58 @@ +package Koha::REST::V1::BackgroundJobs; + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha 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 Koha; if not, see . + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Controller'; + +use Koha::BackgroundJobs; + +use Try::Tiny; + +=head1 API + +=head2 Methods + +=head3 list + +=cut + +sub list { + my $c = shift->openapi->valid_input or return; + + return try { + my $patron = $c->stash('koha.user'); + + my $can_manage_background_jobs = + $patron->has_permission( { parameters => 'manage_background_jobs' } ); + + my $background_jobs_set = + $can_manage_background_jobs + ? Koha::BackgroundJobs->new + : Koha::BackgroundJobs->search( + { borrowernumber => $patron->borrowernumber } ); + + my $background_jobs = $c->objects->search( $background_jobs_set ); + return $c->render( status => 200, openapi => $background_jobs ); + } + catch { + $c->unhandled_exception($_); + }; + +} + +1; diff --git a/admin/background_jobs.pl b/admin/background_jobs.pl index 761c09d069..b85281ce72 100755 --- a/admin/background_jobs.pl +++ b/admin/background_jobs.pl @@ -77,39 +77,6 @@ if ( $op eq 'cancel' ) { $op = 'list'; } - -if ( $op eq 'list' ) { - my $queued_jobs = - $can_manage_background_jobs - ? Koha::BackgroundJobs->search( { ended_on => undef }, - { order_by => { -desc => 'enqueued_on' } } ) - : Koha::BackgroundJobs->search( - { borrowernumber => $logged_in_user->borrowernumber, ended_on => undef }, - { order_by => { -desc => 'enqueued_on' } } - ); - $template->param( queued => $queued_jobs ); - - my $ended_since = dt_from_string->subtract( minutes => '60' ); - my $dtf = Koha::Database->new->schema->storage->datetime_parser; - - my $complete_jobs = - $can_manage_background_jobs - ? Koha::BackgroundJobs->search( - { - ended_on => { '>=' => $dtf->format_datetime($ended_since) } - }, - { order_by => { -desc => 'enqueued_on' } } - ) - : Koha::BackgroundJobs->search( - { - borrowernumber => $logged_in_user->borrowernumber, - ended_on => { '>=' => $dtf->format_datetime($ended_since) } - }, - { order_by => { -desc => 'enqueued_on' } } - ); - $template->param( complete => $complete_jobs ); -} - $template->param( messages => \@messages, op => $op, diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt index 5a7dce0cb7..0d2e178e6e 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt @@ -43,7 +43,6 @@ Holds queue update [% CASE %]Unknown job type '[% job_type | html %]' [% END %] - [% END %] [% INCLUDE 'doc-head-open.inc' %] @@ -156,105 +155,23 @@ <h1>Background jobs</h1> - <div id="taskstabs" class="toptabs"> - <ul class="nav nav-tabs" role="tablist"> - <li role="presentation" class="active"><a href="#queued" aria-controls="queued" role="tab" data-toggle="tab">Queued jobs</a></li> - <li role="presentation"><a href="#complete" aria-controls="complete" role="tab" data-toggle="tab">Complete jobs</a></li> - </ul> - - <div class="tab-content"> - <div role="tabpanel" class="tab-pane active" id="queued"> - [% IF queued.count %] - <table id="table_queued_jobs"> - <thead> - <tr> - <th>Job ID</th> - <th>Status</th> - <th>Progress</th> - <th>Type</th> - <th>Queued</th> - <th>Started</th> - <th class="noExport">Actions</th> - </tr> - </thead> - <tbody> - [% FOREACH job IN queued %] - <tr> - <td>[% job.id | html %]</td> - <td> - [% PROCESS show_job_status %] - </td> - <td>[% job.progress || 0 | html %] / [% job.size | html %]</td> - <td> - [% PROCESS show_job_type job_type => job.type %] - </td> - <td>[% job.enqueued_on | $KohaDates with_hours = 1 %]</td> - <td>[% job.started_on| $KohaDates with_hours = 1 %]</td> - <td class="actions"> - <a class="btn btn-default btn-xs" href="/cgi-bin/koha/admin/background_jobs.pl?op=view&id=[% job.id | html %]"><i class="fa fa-eye"></i> View</a> - [% IF job.status == 'new' || job.status == 'started' %] - <a class="btn btn-default btn-xs" href="/cgi-bin/koha/admin/background_jobs.pl?op=cancel&id=[% job.id | html %]"><i class="fa fa-trash"></i> Cancel</a> - [% END %] - </td> - </tr> - [% END %] - </tbody> - </table> - [% ELSE %] - <div class="dialog message"> - There are no queued background jobs yet. - </div> - [% END %] - </div> + <label for="include_last_hour">Only include jobs started in the last hour</label> + <input type="checkbox" id="include_last_hour" checked /> - <div role="tabpanel" class="tab-pane" id="complete"> - [% IF complete.count %] - <p>Jobs completed in the last 60 minutes.</p> - <table id="table_complete_jobs"> - <thead> - <tr> - <th>Job ID</th> - <th>Status</th> - <th>Progress</th> - <th>Type</th> - <th>Queued</th> - <th>Started</th> - <th>Ended</th> - <th class="noExport">Actions</th> - </tr> - </thead> - <tbody> - [% FOREACH job IN complete %] - <tr> - <td>[% job.id | html %]</td> - <td> - [% PROCESS show_job_status %] - </td> - <td>[% job.progress || 0 | html %] / [% job.size | html %]</td> - <td> - [% PROCESS show_job_type job_type => job.type %] - </td> - <td>[% job.enqueued_on | $KohaDates with_hours = 1 %]</td> - <td>[% job.started_on| $KohaDates with_hours = 1 %]</td> - <td>[% job.ended_on| $KohaDates with_hours = 1 %]</td> - <td class="actions"> - <a class="btn btn-default btn-xs" href="/cgi-bin/koha/admin/background_jobs.pl?op=view&id=[% job.id | html %]"><i class="fa fa-eye"></i> View</a> - [% IF job.status == 'new' || job.status == 'started' %] - <a class="btn btn-default btn-xs" href="/cgi-bin/koha/admin/background_jobs.pl?op=cancel&id=[% job.id | html %]"><i class="fa fa-trash"></i> Cancel</a> - [% END %] - </td> - </tr> - [% END %] - </tbody> - </table> - [% ELSE %] - <div class="dialog message"> - There were no completed background jobs completed in the last 60 minutes. - </div> - [% END %] - </div> - </div> - </div> + <table id="table_jobs"> + <thead> + <tr> + <th>Job ID</th> + <th data-filter="job_statuses">Status</th> + <th>Progress</th> + <th data-filter="job_types">Type</th> + <th>Queued</th> + <th>Started</th> + <th>Ended</th> + <th class="noExport">Actions</th> + </tr> + </thead> + </table> [% END %] @@ -270,26 +187,165 @@ [% MACRO jsinclude BLOCK %] [% Asset.js("js/admin-menu.js") | $raw %] + [% INCLUDE 'js-date-format.inc' %] [% INCLUDE 'datatables.inc' %] <script> + const job_statuses = [ + {'_id': 'new', '_str': _('New')}, + {'_id': 'cancelled', '_str': _('Cancelled')}, + {'_id': 'finished', '_str': _('Finished')}, + {'_id': 'started', '_str': _('Started')}, + {'_id': 'running', '_str': _('Running')}, + {'_id': 'failed', '_str': _('Failed')}, + ]; + function get_job_status (status) { + let status_lib = job_statuses.find( s => s._id == status ); + if (status_lib) { + return status_lib._str; + } + return status; + } + + const job_types = [ + { + '_id': 'batch_biblio_record_modification', + '_str': _('Batch bibliographic record modification') + }, + { + '_id': 'batch_biblio_record_deletion', + '_str': _('Batch bibliographic record record deletion') + }, + { + '_id': 'batch_authority_record_modification', + '_str': _('Batch authority record modification') + }, + { + '_id': 'batch_authority_record_deletion', + '_str': _('Batch authority record deletion') + }, + { + '_id': 'batch_item_record_modification', + '_str': _('Batch item record modification') + }, + { + '_id': 'batch_item_record_deletion', + '_str': _('Batch item record deletion') + }, + { + '_id': 'batch_hold_cancel', + '_str': _('Batch hold cancellation') + }, + { + '_id': 'update_elastic_index' + , '_str': _('Update Elasticsearch index') + }, + { + '_id': 'update_holds_queue_for_biblios', + '_str': _('Holds queue update') + } + ]; + + function get_job_type (job_type) { + let job_type_lib = job_types.find( t => t._id == job_type ); + if ( job_type_lib ) { + return job_type_lib._str; + } + return _("Unknown job type '%s'").format(job_type); + } + $(document).ready(function() { - $("#table_queued_jobs").dataTable($.extend(true, {}, dataTablesDefaults, { - "aoColumnDefs": [ - { "aTargets": [ -1, -2 ], "bSortable": false, "bSearchable": false }, - ], - "aaSorting": [[ 0, "desc" ]], - "iDisplayLength": 10, - "sPaginationType": "full_numbers" - })); + let additional_filters = { + started_on: function(){ + let now = new Date(); + if ( $("#include_last_hour").is(":checked") ) { + now.setHours(now.getHours() - 1); + return { ">": now.toISOString() }; + } else { + console.log("not checked"); + return { "<": now.toISOString() }; + } + }, + }; + let jobs_table = $("#table_jobs").kohaTable({ + "ajax": { + "url": "/api/v1/background_jobs" + }, + "order": [[ 1, "desc" ]], + "columns": [ + { + "data": "background_job_id", + "searchable": true, + "orderable": true + }, + { + "data": "status", + "searchable": true, + "orderable": true, + "render": function(data, type, row, meta) { + return get_job_status(row.status).escapeHtml(); + }, + }, + { + "data": "progress,size", + "searchable": false, + "orderable": true, + "render": function(data, type, row, meta) { + return "%s/%s".format(row.progress, row.size).escapeHtml(); + }, + }, + { + "data": "type", + "searchable": true, + "orderable": true, + "render": function(data, type, row, meta) { + return get_job_type(row.type).escapeHtml(); + }, + }, + { + "data": "enqueued_on", + "searchable": true, + "orderable": true, + "render": function(data, type, row, meta) { + return $datetime(row.enqueued_on); + }, + }, + { + "data": "started_on", + "searchable": true, + "orderable": true, + "render": function(data, type, row, meta) { + return $datetime(row.started_on); + }, + }, + + { + "data": "ended_on", + "searchable": true, + "orderable": true, + "render": function(data, type, row, meta) { + return $datetime(row.ended_on); + }, + }, + { + "data": function( row, type, val, meta ) { + var result = '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/background_jobs.pl?op=view&id='+ encodeURIComponent(row.background_job_id) +'"><i class="fa fa-eye" aria-hidden="true"></i> '+_("View")+'</a>'+"\n"; + if ( row.status == 'new' || row.status == 'started' ) { + result += '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/bakcground_jobs.pl?op=cancel&id='+ encodeURIComponent(row.background_job_id) +'"><i class="fa fa-trash" aria-hidden="true"></i> '+_("Cancel")+'</a>'; + } + return result; + + }, + "searchable": false, + "orderable": false + } + ] + }, null, 1, additional_filters); + + $("#include_last_hour").on("change", function(){ + jobs_table.DataTable().draw(); + return false; + }); - $("#table_complete_jobs").dataTable($.extend(true, {}, dataTablesDefaults, { - "aoColumnDefs": [ - { "aTargets": [ -1, -2 ], "bSortable": false, "bSearchable": false }, - ], - "aaSorting": [[ 0, "desc" ]], - "iDisplayLength": 10, - "sPaginationType": "full_numbers" - })); }); </script> [% IF op == 'view' %] -- 2.37.2