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> Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
parent
ae9d3b0477
commit
eb2dfad0d1
13 changed files with 740 additions and 209 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
99
Koha/REST/V1/BackgroundJobs.pm
Normal file
99
Koha/REST/V1/BackgroundJobs.pm
Normal file
|
@ -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;
|
|
@ -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,
|
||||
|
|
54
api/v1/swagger/definitions/job.yaml
Normal file
54
api/v1/swagger/definitions/job.yaml
Normal file
|
@ -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
|
87
api/v1/swagger/paths/jobs.yaml
Normal file
87
api/v1/swagger/paths/jobs.yaml
Normal file
|
@ -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"
|
|
@ -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
|
||||
|
|
|
@ -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 %]
|
||||
|
||||
|
|
|
@ -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 %]
|
||||
|
||||
|
|
|
@ -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 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="only_current" checked />
|
||||
<label for="only_current">Current jobs only</label>
|
||||
</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>
|
||||
$(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"
|
||||
}));
|
||||
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;
|
||||
}
|
||||
|
||||
$("#table_complete_jobs").dataTable($.extend(true, {}, dataTablesDefaults, {
|
||||
"aoColumnDefs": [
|
||||
{ "aTargets": [ -1, -2 ], "bSortable": false, "bSearchable": false },
|
||||
],
|
||||
"aaSorting": [[ 0, "desc" ]],
|
||||
"iDisplayLength": 10,
|
||||
"sPaginationType": "full_numbers"
|
||||
}));
|
||||
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() {
|
||||
[% 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;
|
||||
});
|
||||
|
||||
$("#only_current").on("change", function(){
|
||||
jobs_table.DataTable().ajax.url("/api/v1/jobs?" + only_current_filter()).load();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
[% IF op == 'view' %]
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
125
t/db_dependent/api/v1/jobs.t
Executable file
125
t/db_dependent/api/v1/jobs.t
Executable file
|
@ -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;
|
|
@ -547,6 +547,9 @@ sub _gen_blob {
|
|||
sub _gen_default_values {
|
||||
my ($self) = @_;
|
||||
return {
|
||||
BackgroundJob => {
|
||||
context => '{}'
|
||||
},
|
||||
Borrower => {
|
||||
login_attempts => 0,
|
||||
gonenoaddress => undef,
|
||||
|
|
Loading…
Reference in a new issue