Bugzilla – Attachment 142859 Details for
Bug 30982
Use the REST API for background job list view
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 30982: [22.05.x] Use the REST API for background job list view
Bug-30982-2205x-Use-the-REST-API-for-background-jo.patch (text/plain), 45.46 KB, created by
Nick Clemens (kidclamp)
on 2022-11-01 11:36:01 UTC
(
hide
)
Description:
Bug 30982: [22.05.x] Use the REST API for background job list view
Filename:
MIME Type:
Creator:
Nick Clemens (kidclamp)
Created:
2022-11-01 11:36:01 UTC
Size:
45.46 KB
patch
obsolete
>From 5a5538fbc608f4882366c97a9c72f116564d9384 Mon Sep 17 00:00:00 2001 >From: Jonathan Druart <jonathan.druart@bugs.koha-community.org> >Date: Fri, 17 Jun 2022 09:21:39 +0200 >Subject: [PATCH] Bug 30982: [22.05.x] Use the REST API for background job list > view > >Bug 30982: REST API specs > >Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: (QA follow-up) Double quoting and console.log > >This patch fixes the issues highlighted by the QA script; We use double >quotes for translatable strings in JS and remove an errant console.log >call. > >Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: Add tests and implement GET /background_jobs/$id > >Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: Make code more re-usable > >Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: Add 'context' to the REST API specs > >context has been added by bug 30889 > >Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: Add Koha::BackgroundJobs->filter_by_current > >Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io> >Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: API tweaks > >This patch makes the following changes to the 'background_jobs' API: > >* We now call them 'jobs' >* Removed deprecated query parameter definitions >* Added only_current query parameter >* Controller gets adapted to use $rs->filter_by_current when > only_current is passed > >Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io> >Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: Adapt table to new API spec > >Disclaimer: this patch is highly opinionated :-D > >When I started looking at this patch I felt like the two tables >(current/past jobs) implemented on bug 30462 was the way to go. > >In order to make this patches apply after it I had to redo all the >things. Or most of them. > >But I decided to keep the idea of filtering out completed tasks, not >just having the option to display 'the last hour' tasks. For the task I >added some required helper methods and the relevant tests as well. So a >behavior change. > >Hope you all agree with it. > >Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io> >Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: Rename 'Background Jobs' => 'Jobs' > >Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io> >Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: (QA follow-up) Resolve pod warning > >Empty section in previous paragraph at line 32 in file Koha/BackgroundJobs.pm > >Test plan: >Run podchecker again on this module. > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: (QA follow-up) Spelling > >[1] Correct: BackgrounJob >[2] If should filter out not current jobs >=> Had a hard time reading that one until I replaced if by it. >=> Decided to rephrase it in a more positive way. > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: (QA follow-up) Remove redundancy from template > >The template now contains two lists for both status and type: >a TT list and a JS list. The type list already proves that >redundancy leads to bugs. We miss three types at one side: > Unknown job type 'stage_marc_for_import' > Unknown job type 'marc_import_commit_batch' > Unknown job type 'marc_import_revert_batch' > >This patch removes the TT list. And gets the status and type >via an additional js call. For that reason I hide the fieldset >until document ready. This can be improved later when needed. > >Test plan: >Look at status and type on both job list and detail view. > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> > >Bug 30982: (QA follow-up) No userenv, no jobs > >+ # Assume permission if context has no user >+ my $can_manage_background_jobs = 1; >=> This felt a bit unsafe. > >Test plan: >Try interface for jobs. Call API with cookie. Call API with OAuth. >Run t/db_dependent/Koha/BackgroundJobs.t > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> >--- > Koha/BackgroundJob.pm | 44 ++- > Koha/BackgroundJobs.pm | 49 ++- > Koha/REST/V1/BackgroundJobs.pm | 99 +++++ > admin/background_jobs.pl | 33 -- > api/v1/swagger/definitions/job.yaml | 54 +++ > api/v1/swagger/paths/jobs.yaml | 87 ++++ > api/v1/swagger/swagger.yaml | 15 + > .../prog/en/includes/admin-menu.inc | 4 +- > .../prog/en/modules/admin/admin-home.tt | 6 +- > .../prog/en/modules/admin/background_jobs.tt | 373 ++++++++++-------- > t/db_dependent/Koha/BackgroundJobs.t | 53 ++- > t/db_dependent/api/v1/jobs.t | 125 ++++++ > t/lib/TestBuilder.pm | 3 + > 13 files changed, 738 insertions(+), 207 deletions(-) > create mode 100644 Koha/REST/V1/BackgroundJobs.pm > create mode 100644 api/v1/swagger/definitions/job.yaml > create mode 100644 api/v1/swagger/paths/jobs.yaml > create mode 100755 t/db_dependent/api/v1/jobs.t > >diff --git a/Koha/BackgroundJob.pm b/Koha/BackgroundJob.pm >index e4aede3fe3..5b8e017940 100644 >--- a/Koha/BackgroundJob.pm >+++ b/Koha/BackgroundJob.pm >@@ -47,7 +47,7 @@ my $job_id = Koha::BackgroundJob->enqueue( > ); > > Consumer: >-Koha::BackgrounJobs->find($job_id)->process; >+Koha::BackgroundJobs->find($job_id)->process; > See also C<misc/background_jobs_worker.pl> for a full example > > =head1 API >@@ -386,7 +386,7 @@ sub _derived_class { > > =head3 type_to_class_mapping > >- my $mapping = Koha::BackgrounJob->new->type_to_class_mapping; >+ my $mapping = Koha::BackgroundJob->new->type_to_class_mapping; > > Returns the available types to class mappings. > >@@ -404,7 +404,7 @@ sub type_to_class_mapping { > > =head3 core_types_to_classes > >- my $mappings = Koha::BackgrounJob->new->core_types_to_classes >+ my $mappings = Koha::BackgroundJob->new->core_types_to_classes > > Returns the core background jobs types to class mappings. > >@@ -469,6 +469,44 @@ sub plugin_types_to_classes { > return $self->{_plugin_mapping}; > } > >+=head3 to_api >+ >+ my $json = $job->to_api; >+ >+Overloaded method that returns a JSON representation of the Koha::BackgroundJob object, >+suitable for API output. >+ >+=cut >+ >+sub to_api { >+ my ( $self, $params ) = @_; >+ >+ my $json = $self->SUPER::to_api( $params ); >+ >+ $json->{context} = $self->json->decode($self->context) >+ if defined $self->context; >+ $json->{data} = $self->decoded_data; >+ >+ return $json; >+} >+ >+=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 => 'job_id', >+ borrowernumber => 'patron_id', >+ ended_on => 'ended_date', >+ enqueued_on => 'enqueued_date', >+ started_on => 'started_date', >+ }; >+} >+ > =head3 _type > > =cut >diff --git a/Koha/BackgroundJobs.pm b/Koha/BackgroundJobs.pm >index d2ff22347f..48afbf68fa 100644 >--- a/Koha/BackgroundJobs.pm >+++ b/Koha/BackgroundJobs.pm >@@ -16,19 +16,64 @@ package Koha::BackgroundJobs; > # along with Koha; if not, see <http://www.gnu.org/licenses>. > > use Modern::Perl; >-use base qw(Koha::Objects); >+ > use Koha::BackgroundJob; > >+use base qw(Koha::Objects); >+ > =head1 NAME > > Koha::BackgroundJobs - Koha BackgroundJob Object set class > > =head1 API > >-=head2 Class Methods >+=head2 Class methods >+ >+=head3 search_limited >+ >+ my $background_jobs = Koha::BackgroundJobs->search_limited( $params, $attributes ); >+ >+Returns all background jobs the logged in user should be allowed to see >+ >+=cut >+ >+sub search_limited { >+ my ( $self, $params, $attributes ) = @_; >+ >+ my $can_manage_background_jobs; >+ my $logged_in_user; >+ my $userenv = C4::Context->userenv; >+ if ( $userenv and $userenv->{number} ) { >+ $logged_in_user = Koha::Patrons->find( $userenv->{number} ); >+ $can_manage_background_jobs = $logged_in_user->has_permission( >+ { parameters => 'manage_background_jobs' } ); >+ } >+ >+ return $self->search( $params, $attributes ) if $can_manage_background_jobs; >+ my $id = $logged_in_user ? $logged_in_user->borrowernumber : undef; >+ return $self->search({ borrowernumber => $id })->search( $params, $attributes ); >+} >+ >+=head3 filter_by_current >+ >+ my $current_jobs = $jobs->filter_by_current; >+ >+Returns a new resultset, filtering out finished jobs. > > =cut > >+sub filter_by_current { >+ my ($self) = @_; >+ >+ return $self->search( >+ { >+ status => { not_in => [ 'cancelled', 'failed', 'finished' ] } >+ } >+ ); >+} >+ >+=head2 Internal methods >+ > =head3 _type > > =cut >diff --git a/Koha/REST/V1/BackgroundJobs.pm b/Koha/REST/V1/BackgroundJobs.pm >new file mode 100644 >index 0000000000..4db1ea3f3f >--- /dev/null >+++ b/Koha/REST/V1/BackgroundJobs.pm >@@ -0,0 +1,99 @@ >+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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Mojo::Base 'Mojolicious::Controller'; >+ >+use Koha::BackgroundJobs; >+ >+use Try::Tiny; >+ >+=head1 API >+ >+=head2 Methods >+ >+=head3 list >+ >+Controller function that handles listing Koha::BackgroundJob objects >+ >+=cut >+ >+sub list { >+ my $c = shift->openapi->valid_input or return; >+ >+ return try { >+ >+ my $only_current = delete $c->validation->output->{only_current}; >+ >+ my $bj_rs = Koha::BackgroundJobs->new; >+ >+ if ($only_current) { >+ $bj_rs = $bj_rs->filter_by_current; >+ } >+ >+ return $c->render( >+ status => 200, >+ openapi => $c->objects->search($bj_rs) >+ ); >+ } catch { >+ $c->unhandled_exception($_); >+ }; >+} >+ >+=head3 get >+ >+Controller function that handles retrieving a single Koha::BackgroundJob object >+ >+=cut >+ >+sub get { >+ my $c = shift->openapi->valid_input or return; >+ >+ return try { >+ >+ my $job_id = $c->validation->param('job_id'); >+ my $patron = $c->stash('koha.user'); >+ >+ my $can_manage_background_jobs = >+ $patron->has_permission( { parameters => 'manage_background_jobs' } ); >+ >+ my $job = Koha::BackgroundJobs->find($job_id); >+ >+ return $c->render( >+ status => 404, >+ openapi => { error => "Object not found" } >+ ) unless $job; >+ >+ return $c->render( >+ status => 403, >+ openapi => { error => "Cannot see background job info" } >+ ) >+ if !$can_manage_background_jobs >+ && $job->borrowernumber != $patron->borrowernumber; >+ >+ return $c->render( >+ status => 200, >+ openapi => $job->to_api >+ ); >+ } >+ 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/api/v1/swagger/definitions/job.yaml b/api/v1/swagger/definitions/job.yaml >new file mode 100644 >index 0000000000..966e3a3ef8 >--- /dev/null >+++ b/api/v1/swagger/definitions/job.yaml >@@ -0,0 +1,54 @@ >+--- >+type: object >+properties: >+ job_id: >+ type: integer >+ description: internally assigned job identifier >+ readOnly: true >+ status: >+ description: job status >+ type: string >+ progress: >+ description: job progress >+ type: >+ - string >+ - "null" >+ size: >+ description: job size >+ type: >+ - string >+ - "null" >+ patron_id: >+ description: job enqueuer >+ type: >+ - string >+ - "null" >+ type: >+ description: job type >+ type: string >+ queue: >+ description: job queue >+ type: string >+ data: >+ description: job data >+ type: object >+ context: >+ description: job context >+ type: object >+ enqueued_date: >+ description: job enqueue date >+ type: string >+ format: date-time >+ started_date: >+ description: job start date >+ type: >+ - string >+ - "null" >+ format: date-time >+ ended_date: >+ description: job end date >+ type: >+ - string >+ - "null" >+ format: date-time >+additionalProperties: false >diff --git a/api/v1/swagger/paths/jobs.yaml b/api/v1/swagger/paths/jobs.yaml >new file mode 100644 >index 0000000000..62ae157dc0 >--- /dev/null >+++ b/api/v1/swagger/paths/jobs.yaml >@@ -0,0 +1,87 @@ >+--- >+/jobs: >+ get: >+ x-mojo-to: BackgroundJobs#list >+ operationId: listJobs >+ tags: >+ - jobs >+ summary: List jobs >+ produces: >+ - application/json >+ parameters: >+ - name: only_current >+ in: query >+ required: false >+ type: boolean >+ description: Only include current jobs >+ - $ref: "../swagger.yaml#/parameters/match" >+ - $ref: "../swagger.yaml#/parameters/order_by" >+ - $ref: "../swagger.yaml#/parameters/page" >+ - $ref: "../swagger.yaml#/parameters/per_page" >+ - $ref: "../swagger.yaml#/parameters/q_param" >+ - $ref: "../swagger.yaml#/parameters/q_body" >+ - $ref: "../swagger.yaml#/parameters/q_header" >+ - $ref: "../swagger.yaml#/parameters/request_id_header" >+ responses: >+ "200": >+ description: A list of jobs >+ schema: >+ type: array >+ items: >+ $ref: "../swagger.yaml#/definitions/job" >+ "403": >+ description: Access forbidden >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "500": >+ description: | >+ Internal server error. Possible `error_code` attribute values: >+ >+ * `internal_server_error` >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "503": >+ description: Under maintenance >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ x-koha-authorization: >+ permissions: >+ catalogue: "1" >+"/jobs/{job_id}": >+ get: >+ x-mojo-to: BackgroundJobs#get >+ operationId: getJob >+ tags: >+ - jobs >+ summary: Get a job >+ parameters: >+ - $ref: "../swagger.yaml#/parameters/job_id_pp" >+ produces: >+ - application/json >+ responses: >+ "200": >+ description: A job >+ schema: >+ $ref: "../swagger.yaml#/definitions/job" >+ "403": >+ description: Access forbidden >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "404": >+ description: Job not found >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "500": >+ description: | >+ Internal server error. Possible `error_code` attribute values: >+ >+ * `internal_server_error` >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ "503": >+ description: Under maintenance >+ schema: >+ $ref: "../swagger.yaml#/definitions/error" >+ x-koha-authorization: >+ permissions: >+ catalogue: "1" >diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml >index 88e3bef4bd..c9291f820e 100644 >--- a/api/v1/swagger/swagger.yaml >+++ b/api/v1/swagger/swagger.yaml >@@ -42,6 +42,8 @@ definitions: > $ref: ./definitions/invoice.yaml > item: > $ref: ./definitions/item.yaml >+ job: >+ $ref: ./definitions/job.yaml > library: > $ref: ./definitions/library.yaml > order: >@@ -155,6 +157,10 @@ paths: > $ref: "./paths/items.yaml#/~1items~1{item_id}" > "/items/{item_id}/pickup_locations": > $ref: "./paths/items.yaml#/~1items~1{item_id}~1pickup_locations" >+ /jobs: >+ $ref: ./paths/jobs.yaml#/~1jobs >+ "/jobs/{job_id}": >+ $ref: "./paths/jobs.yaml#/~1jobs~1{job_id}" > /libraries: > $ref: ./paths/libraries.yaml#/~1libraries > "/libraries/{library_id}": >@@ -300,6 +306,12 @@ parameters: > name: item_id > required: true > type: integer >+ job_id_pp: >+ description: Job internal identifier >+ in: path >+ name: job_id >+ required: true >+ type: integer > library_id_pp: > description: Internal library identifier > in: path >@@ -560,6 +572,9 @@ tags: > - description: "Manage items\n" > name: items > x-displayName: Items >+ - description: "Manage jobs\n" >+ name: jobs >+ x-displayName: Jobs > - description: "Manage libraries\n" > name: libraries > x-displayName: Libraries >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >index 8fb7ccccd7..bbb6370d85 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc >@@ -75,9 +75,9 @@ > [% END %] > > [% IF CAN_user_parameters_manage_background_jobs %] >- <h5>Background jobs</h5> >+ <h5>Jobs</h5> > <ul> >- <li><a href="/cgi-bin/koha/admin/background_jobs.pl">Background jobs</a></li> >+ <li><a href="/cgi-bin/koha/admin/background_jobs.pl">Jobs</a></li> > </ul> > [% END %] > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >index cd15f66e04..d100efa638 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt >@@ -147,10 +147,10 @@ > [% END %] > > [% IF CAN_user_parameters_manage_background_jobs %] >- <h3>Background jobs</h3> >+ <h3>Jobs</h3> > <dl> >- <dt><a href="/cgi-bin/koha/admin/background_jobs.pl">Manage background jobs</a></dt> >- <dd>View, manage and cancel background jobs.</dd> >+ <dt><a href="/cgi-bin/koha/admin/background_jobs.pl">Manage jobs</a></dt> >+ <dd>View, manage and cancel jobs.</dd> > </dl> > [% END %] > >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 0fafb30da4..1432cbdc2a 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 >@@ -3,54 +3,12 @@ > [% USE Asset %] > [% USE KohaDates %] > [% SET footerjs = 1 %] >-[% BLOCK show_job_status %] >- [% SWITCH job.status %] >- [% CASE "new" %] >- <span>New</span> >- [% CASE "cancelled" %] >- <span>Cancelled</span> >- [% CASE "finished" %] >- <span>Finished</span> >- [% CASE "started" %] >- <span>Started</span> >- [% CASE "running" %] >- <span>Running</span> >- [% CASE "failed" %] >- <span>Failed</span> >- [% CASE # Default case %] >- [% job.status | html %] >- [% END -%] >-[% END %] >-[% BLOCK show_job_type %] >- [% SWITCH job_type %] >- [% CASE 'batch_biblio_record_modification' %] >- <span>Batch bibliographic record modification</span> >- [% CASE 'batch_biblio_record_deletion' %] >- <span>Batch bibliographic record record deletion</span> >- [% CASE 'batch_authority_record_modification' %] >- <span>Batch authority record modification</span> >- [% CASE 'batch_authority_record_deletion' %] >- <span>Batch authority record deletion</span> >- [% CASE 'batch_item_record_modification' %] >- <span>Batch item record modification</span> >- [% CASE 'batch_item_record_deletion' %] >- <span>Batch item record deletion</span> >- [% CASE "batch_hold_cancel" %] >- <span>Batch hold cancellation</span> >- [% CASE 'update_elastic_index' %] >- <span>Update Elasticsearch index</span> >- [% CASE 'update_holds_queue_for_biblios' %] >- <span>Holds queue update</span> >- [% CASE %]<span>Unknown job type '[% job_type | html %]'</span> >- [% END %] >- >-[% END %] > [% INCLUDE 'doc-head-open.inc' %] > <title> > [% IF op == 'view' %] > Details of job #[% job.id | html %] › > [% END %] >- Background jobs › >+ Jobs › > Administration › Koha > </title> > >@@ -73,14 +31,14 @@ > </li> > [% IF op == 'view' %] > <li> >- <a href="/cgi-bin/koha/admin/background_jobs.pl">Background jobs</a> >+ <a href="/cgi-bin/koha/admin/background_jobs.pl">Jobs</a> > </li> > <li> > <a href="#" aria-current="page">Details of job #[% job.id | html %]</a> > </li> > [% ELSE %] > <li> >- <a href="#" aria-current="page">Background jobs</a> >+ <a href="#" aria-current="page">Jobs</a> > </li> > [% END %] > [% ELSE %] >@@ -112,17 +70,17 @@ > > [% PROCESS "background_jobs/${job.type}.inc" %] > >- <fieldset class="rows"> >+ <fieldset class="rows" style="display:none;"> > <ol> > <li><span class="label">Job ID: </span>[% job.id | html %]</li> > <li> > <label for="job_status">Status: </label> >- [% PROCESS show_job_status %] >+ <span id="job_status_description"></span> > </li> > <li><label for="job_progress">Progress: </label>[% job.progress || 0 | html %] / [% job.size | html %]</li> > <li> > <label for="job_type">Type: </label> >- [% PROCESS show_job_type job_type => job.type %] >+ <span id="job_type_description"></span> > </li> > <li> > <label for="job_enqueued_on">Queued: </label> >@@ -154,108 +112,32 @@ > > [% IF op == 'list' %] > >- <h1>Background jobs</h1> >+ <h1>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">Completed 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> >+ <div> >+ <input type="checkbox" id="only_current" checked /> >+ <label for="only_current">Current jobs only</label> >+ </div> > >- <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> >+ <input type="checkbox" id="include_last_hour" checked /> >+ <label for="include_last_hour">Only include jobs started in the last hour</label> > </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 %] > > </main> >@@ -270,26 +152,193 @@ > > [% 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") >+ }, >+ { >+ '_id': 'stage_marc_for_import', >+ '_str': _("Staged MARC records for import") >+ }, >+ { >+ '_id': 'marc_import_commit_batch', >+ '_str': _("Import MARC records") >+ }, >+ { >+ '_id': 'marc_import_revert_batch', >+ '_str': _("Revert import MARC records") >+ }, >+ ]; >+ >+ 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" >- })); >+ [% IF op == 'view' %] >+ $("#job_status_description").html( get_job_status("[% job.status | html %]") ); >+ $("#job_type_description").html( get_job_type("[% job.type | html %]") ); >+ $("fieldset.rows").show(); >+ [% END %] >+ >+ 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 { >+ return { "<": now.toISOString() }; >+ } >+ } >+ }; >+ >+ let only_current_filter = function(){ >+ if ( $("#only_current").is(":checked") ) { >+ return 'only_current=1'; >+ } else { >+ return 'only_current=0'; >+ } >+ } >+ >+ let jobs_table = $("#table_jobs").kohaTable({ >+ "ajax": { >+ "url": "/api/v1/jobs?" + only_current_filter() >+ }, >+ "order": [[ 1, "desc" ]], >+ "columns": [ >+ { >+ "data": "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_date", >+ "searchable": true, >+ "orderable": true, >+ "render": function(data, type, row, meta) { >+ return $datetime(row.enqueued_date); >+ } >+ }, >+ { >+ "data": "started_date", >+ "searchable": true, >+ "orderable": true, >+ "render": function(data, type, row, meta) { >+ return $datetime(row.started_date); >+ } >+ }, >+ { >+ "data": "ended_date", >+ "searchable": true, >+ "orderable": true, >+ "render": function(data, type, row, meta) { >+ return $datetime(row.ended_date); >+ } >+ }, >+ { >+ "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.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.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" >- })); >+ $("#only_current").on("change", function(){ >+ jobs_table.DataTable().ajax.url("/api/v1/jobs?" + only_current_filter()).load(); >+ return false; >+ }); > }); > </script> > [% IF op == 'view' %] >diff --git a/t/db_dependent/Koha/BackgroundJobs.t b/t/db_dependent/Koha/BackgroundJobs.t >index 0f5bede989..cfb9c28cc7 100755 >--- a/t/db_dependent/Koha/BackgroundJobs.t >+++ b/t/db_dependent/Koha/BackgroundJobs.t >@@ -19,9 +19,11 @@ > > use Modern::Perl; > >-use Test::More tests => 12; >+use Test::More tests => 14; > use Test::MockModule; > >+use List::MoreUtils qw(any); >+ > use Koha::Database; > use Koha::BackgroundJobs; > use Koha::DateUtils qw( dt_from_string ); >@@ -31,7 +33,8 @@ use t::lib::Mocks; > use t::lib::Dates; > use t::lib::Koha::BackgroundJob::BatchTest; > >-my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new; >+my $schema = Koha::Database->new->schema; > $schema->storage->txn_begin; > > t::lib::Mocks::mock_userenv; >@@ -91,3 +94,49 @@ is_deeply( > is_deeply( $new_job->additional_report(), {} ); > > $schema->storage->txn_rollback; >+ >+subtest 'filter_by_current() tests' => sub { >+ >+ plan tests => 4; >+ >+ $schema->storage->txn_begin; >+ >+ my $job_new = $builder->build_object( { class => 'Koha::BackgroundJobs', value => { status => 'new' } } ); >+ my $job_cancelled = $builder->build_object( { class => 'Koha::BackgroundJobs', value => { status => 'cancelled' } } ); >+ my $job_failed = $builder->build_object( { class => 'Koha::BackgroundJobs', value => { status => 'failed' } } ); >+ my $job_finished = $builder->build_object( { class => 'Koha::BackgroundJobs', value => { status => 'finished' } } ); >+ >+ my $rs = Koha::BackgroundJobs->search( >+ { >+ id => [ $job_new->id, $job_cancelled->id, $job_failed->id, $job_finished->id ] >+ } >+ ); >+ >+ is( $rs->count, 4, '4 jobs in resultset' ); >+ ok( any {$_->status eq 'new'} @{$rs->as_list}, "There is a 'new' job" ); >+ >+ $rs = $rs->filter_by_current; >+ >+ is( $rs->count, 1, 'Only 1 job in filtered resultset' ); >+ is( $rs->next->status, 'new', "The only job in resultset is 'new'" ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'search_limited' => sub { >+ plan tests => 3; >+ >+ $schema->storage->txn_begin; >+ my $patron1 = $builder->build_object( { class => 'Koha::Patrons', value => { flags => 0 } } ); >+ my $patron2 = $builder->build_object( { class => 'Koha::Patrons', value => { flags => 0 } } ); >+ my $job1 = $builder->build_object( { class => 'Koha::BackgroundJobs', value => { borrowernumber => $patron1->id } } ); >+ >+ C4::Context->set_userenv( undef, q{} ); >+ is( Koha::BackgroundJobs->search_limited->count, 0, 'No jobs found without userenv' ); >+ C4::Context->set_userenv( $patron1->id, $patron1->userid ); >+ is( Koha::BackgroundJobs->search_limited->count, 1, 'My job found' ); >+ C4::Context->set_userenv( $patron2->id, $patron2->userid ); >+ is( Koha::BackgroundJobs->search_limited->count, 0, 'No jobs for me' ); >+ >+ $schema->storage->txn_rollback; >+}; >diff --git a/t/db_dependent/api/v1/jobs.t b/t/db_dependent/api/v1/jobs.t >new file mode 100755 >index 0000000000..4d993c68e0 >--- /dev/null >+++ b/t/db_dependent/api/v1/jobs.t >@@ -0,0 +1,125 @@ >+#!/usr/bin/env perl >+ >+# 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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Test::More tests => 25; >+use Test::Mojo; >+ >+use t::lib::TestBuilder; >+use t::lib::Mocks; >+ >+use Koha::BackgroundJobs; >+use Koha::Database; >+ >+my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new; >+ >+my $t = Test::Mojo->new('Koha::REST::V1'); >+#use t::lib::Mojo; >+#my $t = t::lib::Mojo->new('Koha::REST::V1'); >+t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); >+ >+$schema->storage->txn_begin; >+ >+Koha::BackgroundJobs->delete; >+my $superlibrarian = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 1 }, >+ } >+); >+my $password = 'thePassword123'; >+$superlibrarian->set_password( { password => $password, skip_validation => 1 } ); >+my $superlibrarian_userid = $superlibrarian->userid; >+ >+my $librarian = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 2 ** 2 }, # catalogue flag = 2 >+ } >+); >+$librarian->set_password( { password => $password, skip_validation => 1 } ); >+my $librarian_userid = $librarian->userid; >+ >+my $patron = $builder->build_object( >+ { >+ class => 'Koha::Patrons', >+ value => { flags => 0 }, >+ } >+); >+$patron->set_password( { password => $password, skip_validation => 1 } ); >+my $patron_userid = $patron->userid; >+ >+$t->get_ok("//$librarian_userid:$password@/api/v1/jobs") >+ ->status_is(200) >+ ->json_is( [] ); >+ >+my $job = $builder->build_object( >+ { >+ class => 'Koha::BackgroundJobs', >+ value => { >+ status => 'finished', >+ progress => 100, >+ size => 100, >+ borrowernumber => $patron->borrowernumber, >+ type => 'batch_item_record_modification', >+ queue => 'default', >+ #data => '{"record_ids":["1"],"regex_mod":null,"exclude_from_local_holds_priority":null,"new_values":{"itemnotes":"xxx"}}' , >+ data => '{"regex_mod":null,"report":{"total_records":1,"modified_fields":1,"modified_itemnumbers":[1]},"new_values":{"itemnotes":"xxx"},"record_ids":["1"],"exclude_from_local_holds_priority":null}', >+ } >+ } >+); >+ >+{ >+ $t->get_ok("//$superlibrarian_userid:$password@/api/v1/jobs") >+ ->status_is(200)->json_is( [ $job->to_api ] ); >+ >+ $t->get_ok("//$librarian_userid:$password@/api/v1/jobs") >+ ->status_is(200)->json_is( [] ); >+ >+ $t->get_ok("//$patron_userid:$password@/api/v1/jobs") >+ ->status_is(403); >+ >+ $job->borrowernumber( $librarian->borrowernumber )->store; >+ >+ $t->get_ok("//$librarian_userid:$password@/api/v1/jobs") >+ ->status_is(200)->json_is( [ $job->to_api ] ); >+} >+ >+{ >+ $t->get_ok( "//$superlibrarian_userid:$password@/api/v1/jobs/" >+ . $job->id )->status_is(200) >+ ->json_is( $job->to_api ); >+ >+ $t->get_ok( "//$librarian_userid:$password@/api/v1/jobs/" >+ . $job->id )->status_is(200) >+ ->json_is( $job->to_api ); >+ >+ $job->borrowernumber( $superlibrarian->borrowernumber )->store; >+ $t->get_ok( "//$librarian_userid:$password@/api/v1/jobs/" >+ . $job->id )->status_is(403); >+} >+ >+{ >+ $job->delete; >+ $t->get_ok( "//$superlibrarian_userid:$password@/api/v1/jobs/" >+ . $job->id )->status_is(404) >+ ->json_is( '/error' => 'Object not found' ); >+} >+ >+$schema->storage->txn_rollback; >diff --git a/t/lib/TestBuilder.pm b/t/lib/TestBuilder.pm >index 1f21a7a473..47d98d6a4a 100644 >--- a/t/lib/TestBuilder.pm >+++ b/t/lib/TestBuilder.pm >@@ -547,6 +547,9 @@ sub _gen_blob { > sub _gen_default_values { > my ($self) = @_; > return { >+ BackgroundJob => { >+ context => '{}' >+ }, > Borrower => { > login_attempts => 0, > gonenoaddress => undef, >-- >2.30.2
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 30982
:
136245
|
136246
|
136670
|
136671
|
136672
|
136707
|
136708
|
136715
|
138114
|
139636
|
139637
|
139638
|
139639
|
139640
|
139641
|
140488
|
140489
|
140490
|
140491
|
140492
|
140493
|
140494
|
140495
|
140496
|
140497
|
140498
|
140589
|
140590
|
140591
|
140592
|
140593
|
140594
|
140595
|
140596
|
140597
|
140598
|
140599
|
140789
|
140884
|
140885
|
140886
|
140887
|
140888
|
140889
|
140890
|
140891
|
140892
|
140893
|
140894
|
140895
|
140896
|
140897
| 142859