View | Details | Raw Unified | Return to bug 30982
Collapse All | Expand All

(-)a/Koha/BackgroundJob.pm (-3 / +41 lines)
Lines 47-53 my $job_id = Koha::BackgroundJob->enqueue( Link Here
47
);
47
);
48
48
49
Consumer:
49
Consumer:
50
Koha::BackgrounJobs->find($job_id)->process;
50
Koha::BackgroundJobs->find($job_id)->process;
51
See also C<misc/background_jobs_worker.pl> for a full example
51
See also C<misc/background_jobs_worker.pl> for a full example
52
52
53
=head1 API
53
=head1 API
Lines 386-392 sub _derived_class { Link Here
386
386
387
=head3 type_to_class_mapping
387
=head3 type_to_class_mapping
388
388
389
    my $mapping = Koha::BackgrounJob->new->type_to_class_mapping;
389
    my $mapping = Koha::BackgroundJob->new->type_to_class_mapping;
390
390
391
Returns the available types to class mappings.
391
Returns the available types to class mappings.
392
392
Lines 404-410 sub type_to_class_mapping { Link Here
404
404
405
=head3 core_types_to_classes
405
=head3 core_types_to_classes
406
406
407
    my $mappings = Koha::BackgrounJob->new->core_types_to_classes
407
    my $mappings = Koha::BackgroundJob->new->core_types_to_classes
408
408
409
Returns the core background jobs types to class mappings.
409
Returns the core background jobs types to class mappings.
410
410
Lines 469-474 sub plugin_types_to_classes { Link Here
469
    return $self->{_plugin_mapping};
469
    return $self->{_plugin_mapping};
470
}
470
}
471
471
472
=head3 to_api
473
474
    my $json = $job->to_api;
475
476
Overloaded method that returns a JSON representation of the Koha::BackgroundJob object,
477
suitable for API output.
478
479
=cut
480
481
sub to_api {
482
    my ( $self, $params ) = @_;
483
484
    my $json = $self->SUPER::to_api( $params );
485
486
    $json->{context} = $self->json->decode($self->context)
487
      if defined $self->context;
488
    $json->{data} = $self->decoded_data;
489
490
    return $json;
491
}
492
493
=head3 to_api_mapping
494
495
This method returns the mapping for representing a Koha::BackgroundJob object
496
on the API.
497
498
=cut
499
500
sub to_api_mapping {
501
    return {
502
        id             => 'job_id',
503
        borrowernumber => 'patron_id',
504
        ended_on       => 'ended_date',
505
        enqueued_on    => 'enqueued_date',
506
        started_on     => 'started_date',
507
    };
508
}
509
472
=head3 _type
510
=head3 _type
473
511
474
=cut
512
=cut
(-)a/Koha/BackgroundJobs.pm (-2 / +47 lines)
Lines 16-34 package Koha::BackgroundJobs; Link Here
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
use base qw(Koha::Objects);
19
20
use Koha::BackgroundJob;
20
use Koha::BackgroundJob;
21
21
22
use base qw(Koha::Objects);
23
22
=head1 NAME
24
=head1 NAME
23
25
24
Koha::BackgroundJobs - Koha BackgroundJob Object set class
26
Koha::BackgroundJobs - Koha BackgroundJob Object set class
25
27
26
=head1 API
28
=head1 API
27
29
28
=head2 Class Methods
30
=head2 Class methods
31
32
=head3 search_limited
33
34
  my $background_jobs = Koha::BackgroundJobs->search_limited( $params, $attributes );
35
36
Returns all background jobs the logged in user should be allowed to see
37
38
=cut
39
40
sub search_limited {
41
    my ( $self, $params, $attributes ) = @_;
42
43
    my $can_manage_background_jobs;
44
    my $logged_in_user;
45
    my $userenv = C4::Context->userenv;
46
    if ( $userenv and $userenv->{number} ) {
47
        $logged_in_user = Koha::Patrons->find( $userenv->{number} );
48
        $can_manage_background_jobs = $logged_in_user->has_permission(
49
            { parameters => 'manage_background_jobs' } );
50
    }
51
52
    return $self->search( $params, $attributes ) if $can_manage_background_jobs;
53
    my $id = $logged_in_user ? $logged_in_user->borrowernumber : undef;
54
    return $self->search({ borrowernumber => $id  })->search( $params, $attributes );
55
}
56
57
=head3 filter_by_current
58
59
    my $current_jobs = $jobs->filter_by_current;
60
61
Returns a new resultset, filtering out finished jobs.
29
62
30
=cut
63
=cut
31
64
65
sub filter_by_current {
66
    my ($self) = @_;
67
68
    return $self->search(
69
        {
70
            status => { not_in => [ 'cancelled', 'failed', 'finished' ] }
71
        }
72
    );
73
}
74
75
=head2 Internal methods
76
32
=head3 _type
77
=head3 _type
33
78
34
=cut
79
=cut
(-)a/Koha/REST/V1/BackgroundJobs.pm (+99 lines)
Line 0 Link Here
1
package Koha::REST::V1::BackgroundJobs;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Mojo::Base 'Mojolicious::Controller';
21
22
use Koha::BackgroundJobs;
23
24
use Try::Tiny;
25
26
=head1 API
27
28
=head2 Methods
29
30
=head3 list
31
32
Controller function that handles listing Koha::BackgroundJob objects
33
34
=cut
35
36
sub list {
37
    my $c = shift->openapi->valid_input or return;
38
39
    return try {
40
41
        my $only_current = delete $c->validation->output->{only_current};
42
43
        my $bj_rs = Koha::BackgroundJobs->new;
44
45
        if ($only_current) {
46
            $bj_rs = $bj_rs->filter_by_current;
47
        }
48
49
        return $c->render(
50
            status  => 200,
51
            openapi => $c->objects->search($bj_rs)
52
        );
53
    } catch {
54
        $c->unhandled_exception($_);
55
    };
56
}
57
58
=head3 get
59
60
Controller function that handles retrieving a single Koha::BackgroundJob object
61
62
=cut
63
64
sub get {
65
    my $c = shift->openapi->valid_input or return;
66
67
    return try {
68
69
        my $job_id = $c->validation->param('job_id');
70
        my $patron = $c->stash('koha.user');
71
72
        my $can_manage_background_jobs =
73
          $patron->has_permission( { parameters => 'manage_background_jobs' } );
74
75
        my $job = Koha::BackgroundJobs->find($job_id);
76
77
        return $c->render(
78
            status  => 404,
79
            openapi => { error => "Object not found" }
80
        ) unless $job;
81
82
        return $c->render(
83
            status  => 403,
84
            openapi => { error => "Cannot see background job info" }
85
          )
86
          if !$can_manage_background_jobs
87
          && $job->borrowernumber != $patron->borrowernumber;
88
89
        return $c->render(
90
            status  => 200,
91
            openapi => $job->to_api
92
        );
93
    }
94
    catch {
95
        $c->unhandled_exception($_);
96
    };
97
}
98
99
1;
(-)a/admin/background_jobs.pl (-33 lines)
Lines 77-115 if ( $op eq 'cancel' ) { Link Here
77
    $op = 'list';
77
    $op = 'list';
78
}
78
}
79
79
80
81
if ( $op eq 'list' ) {
82
    my $queued_jobs =
83
      $can_manage_background_jobs
84
      ? Koha::BackgroundJobs->search( { ended_on => undef },
85
        { order_by => { -desc => 'enqueued_on' } } )
86
      : Koha::BackgroundJobs->search(
87
        { borrowernumber => $logged_in_user->borrowernumber, ended_on => undef },
88
        { order_by       => { -desc => 'enqueued_on' } }
89
      );
90
    $template->param( queued => $queued_jobs );
91
92
    my $ended_since = dt_from_string->subtract( minutes => '60' );
93
    my $dtf = Koha::Database->new->schema->storage->datetime_parser;
94
95
    my $complete_jobs =
96
      $can_manage_background_jobs
97
      ? Koha::BackgroundJobs->search(
98
        {
99
            ended_on => { '>=' => $dtf->format_datetime($ended_since) }
100
        },
101
        { order_by => { -desc => 'enqueued_on' } }
102
      )
103
      : Koha::BackgroundJobs->search(
104
        {
105
            borrowernumber => $logged_in_user->borrowernumber,
106
            ended_on       => { '>=' => $dtf->format_datetime($ended_since) }
107
        },
108
        { order_by => { -desc => 'enqueued_on' } }
109
      );
110
    $template->param( complete => $complete_jobs );
111
}
112
113
$template->param(
80
$template->param(
114
    messages => \@messages,
81
    messages => \@messages,
115
    op       => $op,
82
    op       => $op,
(-)a/api/v1/swagger/definitions/job.yaml (+54 lines)
Line 0 Link Here
1
---
2
type: object
3
properties:
4
  job_id:
5
    type: integer
6
    description: internally assigned job identifier
7
    readOnly: true
8
  status:
9
    description: job status
10
    type: string
11
  progress:
12
    description: job progress
13
    type:
14
      - string
15
      - "null"
16
  size:
17
    description: job size
18
    type:
19
      - string
20
      - "null"
21
  patron_id:
22
    description: job enqueuer
23
    type:
24
      - string
25
      - "null"
26
  type:
27
    description: job type
28
    type: string
29
  queue:
30
    description: job queue
31
    type: string
32
  data:
33
    description: job data
34
    type: object
35
  context:
36
    description: job context
37
    type: object
38
  enqueued_date:
39
    description: job enqueue date
40
    type: string
41
    format: date-time
42
  started_date:
43
    description: job start date
44
    type:
45
      - string
46
      - "null"
47
    format: date-time
48
  ended_date:
49
    description: job end date
50
    type:
51
      - string
52
      - "null"
53
    format: date-time
54
additionalProperties: false
(-)a/api/v1/swagger/paths/jobs.yaml (+87 lines)
Line 0 Link Here
1
---
2
/jobs:
3
  get:
4
    x-mojo-to: BackgroundJobs#list
5
    operationId: listJobs
6
    tags:
7
      - jobs
8
    summary: List jobs
9
    produces:
10
      - application/json
11
    parameters:
12
      - name: only_current
13
        in: query
14
        required: false
15
        type: boolean
16
        description: Only include current jobs
17
      - $ref: "../swagger.yaml#/parameters/match"
18
      - $ref: "../swagger.yaml#/parameters/order_by"
19
      - $ref: "../swagger.yaml#/parameters/page"
20
      - $ref: "../swagger.yaml#/parameters/per_page"
21
      - $ref: "../swagger.yaml#/parameters/q_param"
22
      - $ref: "../swagger.yaml#/parameters/q_body"
23
      - $ref: "../swagger.yaml#/parameters/q_header"
24
      - $ref: "../swagger.yaml#/parameters/request_id_header"
25
    responses:
26
      "200":
27
        description: A list of jobs
28
        schema:
29
          type: array
30
          items:
31
            $ref: "../swagger.yaml#/definitions/job"
32
      "403":
33
        description: Access forbidden
34
        schema:
35
          $ref: "../swagger.yaml#/definitions/error"
36
      "500":
37
        description: |
38
          Internal server error. Possible `error_code` attribute values:
39
40
          * `internal_server_error`
41
        schema:
42
          $ref: "../swagger.yaml#/definitions/error"
43
      "503":
44
        description: Under maintenance
45
        schema:
46
          $ref: "../swagger.yaml#/definitions/error"
47
    x-koha-authorization:
48
      permissions:
49
        catalogue: "1"
50
"/jobs/{job_id}":
51
  get:
52
    x-mojo-to: BackgroundJobs#get
53
    operationId: getJob
54
    tags:
55
      - jobs
56
    summary: Get a job
57
    parameters:
58
      - $ref: "../swagger.yaml#/parameters/job_id_pp"
59
    produces:
60
      - application/json
61
    responses:
62
      "200":
63
        description: A job
64
        schema:
65
          $ref: "../swagger.yaml#/definitions/job"
66
      "403":
67
        description: Access forbidden
68
        schema:
69
          $ref: "../swagger.yaml#/definitions/error"
70
      "404":
71
        description: Job not found
72
        schema:
73
          $ref: "../swagger.yaml#/definitions/error"
74
      "500":
75
        description: |
76
          Internal server error. Possible `error_code` attribute values:
77
78
          * `internal_server_error`
79
        schema:
80
          $ref: "../swagger.yaml#/definitions/error"
81
      "503":
82
        description: Under maintenance
83
        schema:
84
          $ref: "../swagger.yaml#/definitions/error"
85
    x-koha-authorization:
86
      permissions:
87
        catalogue: "1"
(-)a/api/v1/swagger/swagger.yaml (+15 lines)
Lines 42-47 definitions: Link Here
42
    $ref: ./definitions/invoice.yaml
42
    $ref: ./definitions/invoice.yaml
43
  item:
43
  item:
44
    $ref: ./definitions/item.yaml
44
    $ref: ./definitions/item.yaml
45
  job:
46
    $ref: ./definitions/job.yaml
45
  library:
47
  library:
46
    $ref: ./definitions/library.yaml
48
    $ref: ./definitions/library.yaml
47
  order:
49
  order:
Lines 155-160 paths: Link Here
155
    $ref: "./paths/items.yaml#/~1items~1{item_id}"
157
    $ref: "./paths/items.yaml#/~1items~1{item_id}"
156
  "/items/{item_id}/pickup_locations":
158
  "/items/{item_id}/pickup_locations":
157
    $ref: "./paths/items.yaml#/~1items~1{item_id}~1pickup_locations"
159
    $ref: "./paths/items.yaml#/~1items~1{item_id}~1pickup_locations"
160
  /jobs:
161
    $ref: ./paths/jobs.yaml#/~1jobs
162
  "/jobs/{job_id}":
163
    $ref: "./paths/jobs.yaml#/~1jobs~1{job_id}"
158
  /libraries:
164
  /libraries:
159
    $ref: ./paths/libraries.yaml#/~1libraries
165
    $ref: ./paths/libraries.yaml#/~1libraries
160
  "/libraries/{library_id}":
166
  "/libraries/{library_id}":
Lines 300-305 parameters: Link Here
300
    name: item_id
306
    name: item_id
301
    required: true
307
    required: true
302
    type: integer
308
    type: integer
309
  job_id_pp:
310
    description: Job internal identifier
311
    in: path
312
    name: job_id
313
    required: true
314
    type: integer
303
  library_id_pp:
315
  library_id_pp:
304
    description: Internal library identifier
316
    description: Internal library identifier
305
    in: path
317
    in: path
Lines 560-565 tags: Link Here
560
  - description: "Manage items\n"
572
  - description: "Manage items\n"
561
    name: items
573
    name: items
562
    x-displayName: Items
574
    x-displayName: Items
575
  - description: "Manage jobs\n"
576
    name: jobs
577
    x-displayName: Jobs
563
  - description: "Manage libraries\n"
578
  - description: "Manage libraries\n"
564
    name: libraries
579
    name: libraries
565
    x-displayName: Libraries
580
    x-displayName: Libraries
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/admin-menu.inc (-2 / +2 lines)
Lines 75-83 Link Here
75
    [% END %]
75
    [% END %]
76
76
77
    [% IF CAN_user_parameters_manage_background_jobs %]
77
    [% IF CAN_user_parameters_manage_background_jobs %]
78
        <h5>Background jobs</h5>
78
        <h5>Jobs</h5>
79
        <ul>
79
        <ul>
80
            <li><a href="/cgi-bin/koha/admin/background_jobs.pl">Background jobs</a></li>
80
            <li><a href="/cgi-bin/koha/admin/background_jobs.pl">Jobs</a></li>
81
        </ul>
81
        </ul>
82
    [% END %]
82
    [% END %]
83
83
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (-3 / +3 lines)
Lines 147-156 Link Here
147
            [% END %]
147
            [% END %]
148
148
149
            [% IF CAN_user_parameters_manage_background_jobs %]
149
            [% IF CAN_user_parameters_manage_background_jobs %]
150
                <h3>Background jobs</h3>
150
                <h3>Jobs</h3>
151
                <dl>
151
                <dl>
152
                    <dt><a href="/cgi-bin/koha/admin/background_jobs.pl">Manage background jobs</a></dt>
152
                    <dt><a href="/cgi-bin/koha/admin/background_jobs.pl">Manage jobs</a></dt>
153
                    <dd>View, manage and cancel background jobs.</dd>
153
                    <dd>View, manage and cancel jobs.</dd>
154
                </dl>
154
                </dl>
155
            [% END %]
155
            [% END %]
156
156
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt (-162 / +211 lines)
Lines 3-56 Link Here
3
[% USE Asset %]
3
[% USE Asset %]
4
[% USE KohaDates %]
4
[% USE KohaDates %]
5
[% SET footerjs = 1 %]
5
[% SET footerjs = 1 %]
6
[% BLOCK show_job_status %]
7
    [% SWITCH job.status %]
8
        [% CASE "new" %]
9
            <span>New</span>
10
        [% CASE "cancelled" %]
11
            <span>Cancelled</span>
12
        [% CASE "finished" %]
13
            <span>Finished</span>
14
        [% CASE "started" %]
15
            <span>Started</span>
16
        [% CASE "running" %]
17
            <span>Running</span>
18
        [% CASE "failed" %]
19
            <span>Failed</span>
20
        [% CASE # Default case %]
21
            [% job.status | html %]
22
    [% END -%]
23
[% END %]
24
[% BLOCK show_job_type %]
25
    [% SWITCH job_type %]
26
    [% CASE 'batch_biblio_record_modification' %]
27
        <span>Batch bibliographic record modification</span>
28
    [% CASE 'batch_biblio_record_deletion' %]
29
        <span>Batch bibliographic record record deletion</span>
30
    [% CASE 'batch_authority_record_modification' %]
31
        <span>Batch authority record modification</span>
32
    [% CASE 'batch_authority_record_deletion' %]
33
        <span>Batch authority record deletion</span>
34
    [% CASE 'batch_item_record_modification' %]
35
        <span>Batch item record modification</span>
36
    [% CASE 'batch_item_record_deletion' %]
37
        <span>Batch item record deletion</span>
38
    [% CASE "batch_hold_cancel" %]
39
        <span>Batch hold cancellation</span>
40
    [% CASE 'update_elastic_index' %]
41
        <span>Update Elasticsearch index</span>
42
    [% CASE 'update_holds_queue_for_biblios' %]
43
        <span>Holds queue update</span>
44
    [% CASE %]<span>Unknown job type '[% job_type | html %]'</span>
45
    [% END %]
46
47
[% END %]
48
[% INCLUDE 'doc-head-open.inc' %]
6
[% INCLUDE 'doc-head-open.inc' %]
49
<title>
7
<title>
50
    [% IF op == 'view' %]
8
    [% IF op == 'view' %]
51
        Details of job #[% job.id | html %] &rsaquo;
9
        Details of job #[% job.id | html %] &rsaquo;
52
    [% END %]
10
    [% END %]
53
    Background jobs &rsaquo;
11
    Jobs &rsaquo;
54
    Administration &rsaquo; Koha
12
    Administration &rsaquo; Koha
55
</title>
13
</title>
56
14
Lines 73-86 Link Here
73
        </li>
31
        </li>
74
        [% IF op == 'view' %]
32
        [% IF op == 'view' %]
75
            <li>
33
            <li>
76
                <a href="/cgi-bin/koha/admin/background_jobs.pl">Background jobs</a>
34
                <a href="/cgi-bin/koha/admin/background_jobs.pl">Jobs</a>
77
            </li>
35
            </li>
78
            <li>
36
            <li>
79
                <a href="#" aria-current="page">Details of job #[% job.id | html %]</a>
37
                <a href="#" aria-current="page">Details of job #[% job.id | html %]</a>
80
            </li>
38
            </li>
81
        [% ELSE %]
39
        [% ELSE %]
82
            <li>
40
            <li>
83
                <a href="#" aria-current="page">Background jobs</a>
41
                <a href="#" aria-current="page">Jobs</a>
84
            </li>
42
            </li>
85
        [% END %]
43
        [% END %]
86
    [% ELSE %]
44
    [% ELSE %]
Lines 112-128 Link Here
112
70
113
    [% PROCESS "background_jobs/${job.type}.inc" %]
71
    [% PROCESS "background_jobs/${job.type}.inc" %]
114
72
115
    <fieldset class="rows">
73
    <fieldset class="rows" style="display:none;">
116
        <ol>
74
        <ol>
117
            <li><span class="label">Job ID: </span>[% job.id | html %]</li>
75
            <li><span class="label">Job ID: </span>[% job.id | html %]</li>
118
            <li>
76
            <li>
119
                <label for="job_status">Status: </label>
77
                <label for="job_status">Status: </label>
120
                [% PROCESS show_job_status %]
78
                <span id="job_status_description"></span>
121
            </li>
79
            </li>
122
            <li><label for="job_progress">Progress: </label>[% job.progress || 0 | html %] / [% job.size | html %]</li>
80
            <li><label for="job_progress">Progress: </label>[% job.progress || 0 | html %] / [% job.size | html %]</li>
123
            <li>
81
            <li>
124
                <label for="job_type">Type: </label>
82
                <label for="job_type">Type: </label>
125
                [% PROCESS show_job_type job_type => job.type %]
83
                <span id="job_type_description"></span>
126
            </li>
84
            </li>
127
            <li>
85
            <li>
128
                <label for="job_enqueued_on">Queued: </label>
86
                <label for="job_enqueued_on">Queued: </label>
Lines 154-261 Link Here
154
112
155
[% IF op == 'list' %]
113
[% IF op == 'list' %]
156
114
157
    <h1>Background jobs</h1>
115
    <h1>Jobs</h1>
158
116
159
    <div id="taskstabs" class="toptabs">
117
    <div>
160
        <ul class="nav nav-tabs" role="tablist">
118
        <input type="checkbox" id="only_current" checked />
161
            <li role="presentation" class="active"><a href="#queued" aria-controls="queued" role="tab" data-toggle="tab">Queued jobs</a></li>
119
        <label for="only_current">Current jobs only</label>
162
            <li role="presentation"><a href="#complete" aria-controls="complete" role="tab" data-toggle="tab">Completed jobs</a></li>
120
    </div>
163
        </ul>
164
165
        <div class="tab-content">
166
            <div role="tabpanel" class="tab-pane active" id="queued">
167
                [% IF queued.count %]
168
                    <table id="table_queued_jobs">
169
                        <thead>
170
                            <tr>
171
                                <th>Job ID</th>
172
                                <th>Status</th>
173
                                <th>Progress</th>
174
                                <th>Type</th>
175
                                <th>Queued</th>
176
                                <th>Started</th>
177
                                <th class="noExport">Actions</th>
178
                            </tr>
179
                        </thead>
180
                        <tbody>
181
                            [% FOREACH job IN queued %]
182
                            <tr>
183
                                <td>[% job.id | html %]</td>
184
                                <td>
185
                                    [% PROCESS show_job_status %]
186
                                </td>
187
                                <td>[% job.progress || 0 | html %] / [% job.size | html %]</td>
188
                                <td>
189
                                    [% PROCESS show_job_type job_type => job.type %]
190
                                </td>
191
                                <td>[% job.enqueued_on | $KohaDates with_hours = 1 %]</td>
192
                                <td>[% job.started_on| $KohaDates with_hours = 1 %]</td>
193
                                <td class="actions">
194
                                    <a class="btn btn-default btn-xs" href="/cgi-bin/koha/admin/background_jobs.pl?op=view&amp;id=[% job.id | html %]"><i class="fa fa-eye"></i> View</a>
195
                                    [% IF job.status == 'new' || job.status == 'started' %]
196
                                        <a class="btn btn-default btn-xs" href="/cgi-bin/koha/admin/background_jobs.pl?op=cancel&amp;id=[% job.id | html %]"><i class="fa fa-trash"></i> Cancel</a>
197
                                    [% END %]
198
                                </td>
199
                            </tr>
200
                            [% END %]
201
                        </tbody>
202
                    </table>
203
                [% ELSE %]
204
                    <div class="dialog message">
205
                        There are no queued background jobs yet.
206
                    </div>
207
                [% END %]
208
            </div>
209
121
210
            <div role="tabpanel" class="tab-pane" id="complete">
122
    <div>
211
                [% IF complete.count %]
123
        <input type="checkbox" id="include_last_hour" checked />
212
                    <p>Jobs completed in the last 60 minutes.</p>
124
        <label for="include_last_hour">Only include jobs started in the last hour</label>
213
                    <table id="table_complete_jobs">
214
                        <thead>
215
                            <tr>
216
                                <th>Job ID</th>
217
                                <th>Status</th>
218
                                <th>Progress</th>
219
                                <th>Type</th>
220
                                <th>Queued</th>
221
                                <th>Started</th>
222
                                <th>Ended</th>
223
                                <th class="noExport">Actions</th>
224
                            </tr>
225
                        </thead>
226
                        <tbody>
227
                            [% FOREACH job IN complete %]
228
                            <tr>
229
                                <td>[% job.id | html %]</td>
230
                                <td>
231
                                    [% PROCESS show_job_status %]
232
                                </td>
233
                                <td>[% job.progress || 0 | html %] / [% job.size | html %]</td>
234
                                <td>
235
                                    [% PROCESS show_job_type job_type => job.type %]
236
                                </td>
237
                                <td>[% job.enqueued_on | $KohaDates with_hours = 1 %]</td>
238
                                <td>[% job.started_on| $KohaDates with_hours = 1 %]</td>
239
                                <td>[% job.ended_on| $KohaDates with_hours = 1 %]</td>
240
                                <td class="actions">
241
                                    <a class="btn btn-default btn-xs" href="/cgi-bin/koha/admin/background_jobs.pl?op=view&amp;id=[% job.id | html %]"><i class="fa fa-eye"></i> View</a>
242
                                    [% IF job.status == 'new' || job.status == 'started' %]
243
                                        <a class="btn btn-default btn-xs" href="/cgi-bin/koha/admin/background_jobs.pl?op=cancel&amp;id=[% job.id | html %]"><i class="fa fa-trash"></i> Cancel</a>
244
                                    [% END %]
245
                                </td>
246
                            </tr>
247
                            [% END %]
248
                        </tbody>
249
                    </table>
250
                [% ELSE %]
251
                    <div class="dialog message">
252
                        There were no completed background jobs completed in the last 60 minutes.
253
                    </div>
254
                [% END %]
255
            </div>
256
        </div>
257
    </div>
125
    </div>
258
126
127
    <table id="table_jobs">
128
        <thead>
129
            <tr>
130
                <th>Job ID</th>
131
                <th data-filter="job_statuses">Status</th>
132
                <th>Progress</th>
133
                <th data-filter="job_types">Type</th>
134
                <th>Queued</th>
135
                <th>Started</th>
136
                <th>Ended</th>
137
                <th class="noExport">Actions</th>
138
            </tr>
139
        </thead>
140
    </table>
259
[% END %]
141
[% END %]
260
142
261
            </main>
143
            </main>
Lines 270-295 Link Here
270
152
271
[% MACRO jsinclude BLOCK %]
153
[% MACRO jsinclude BLOCK %]
272
    [% Asset.js("js/admin-menu.js") | $raw %]
154
    [% Asset.js("js/admin-menu.js") | $raw %]
155
    [% INCLUDE 'js-date-format.inc' %]
273
    [% INCLUDE 'datatables.inc' %]
156
    [% INCLUDE 'datatables.inc' %]
274
    <script>
157
    <script>
158
        const job_statuses = [
159
            {'_id': 'new',       '_str': _("New")},
160
            {'_id': 'cancelled', '_str': _("Cancelled")},
161
            {'_id': 'finished',  '_str': _("Finished")},
162
            {'_id': 'started',   '_str': _("Started")},
163
            {'_id': 'running',   '_str': _("Running")},
164
            {'_id': 'failed',    '_str': _("Failed")},
165
        ];
166
        function get_job_status (status) {
167
            let status_lib = job_statuses.find( s => s._id == status );
168
            if (status_lib) {
169
                return status_lib._str;
170
            }
171
            return status;
172
        }
173
174
        const job_types = [
175
            {
176
                '_id': 'batch_biblio_record_modification',
177
                '_str': _("Batch bibliographic record modification")
178
            },
179
            {
180
                '_id': 'batch_biblio_record_deletion',
181
                '_str': _("Batch bibliographic record record deletion")
182
            },
183
            {
184
                '_id': 'batch_authority_record_modification',
185
                '_str': _("Batch authority record modification")
186
            },
187
            {
188
                '_id': 'batch_authority_record_deletion',
189
                '_str': _("Batch authority record deletion")
190
            },
191
            {
192
                '_id': 'batch_item_record_modification',
193
                '_str': _("Batch item record modification")
194
            },
195
            {
196
                '_id': 'batch_item_record_deletion',
197
                '_str': _("Batch item record deletion")
198
            },
199
            {
200
                '_id': 'batch_hold_cancel',
201
                '_str': _("Batch hold cancellation")
202
            },
203
            {
204
                '_id': 'update_elastic_index',
205
                '_str': _("Update Elasticsearch index")
206
            },
207
            {
208
                '_id': 'update_holds_queue_for_biblios',
209
                '_str': _("Holds queue update")
210
            },
211
            {
212
                '_id': 'stage_marc_for_import',
213
                '_str': _("Staged MARC records for import")
214
            },
215
            {
216
                '_id': 'marc_import_commit_batch',
217
                '_str': _("Import MARC records")
218
            },
219
            {
220
                '_id': 'marc_import_revert_batch',
221
                '_str': _("Revert import MARC records")
222
            },
223
        ];
224
225
        function get_job_type (job_type) {
226
            let job_type_lib = job_types.find( t => t._id == job_type );
227
            if ( job_type_lib ) {
228
                return job_type_lib._str;
229
            }
230
            return _("Unknown job type '%s'").format(job_type);
231
        }
232
275
        $(document).ready(function() {
233
        $(document).ready(function() {
276
            $("#table_queued_jobs").dataTable($.extend(true, {}, dataTablesDefaults, {
234
            [% IF op == 'view' %]
277
                "aoColumnDefs": [
235
                $("#job_status_description").html( get_job_status("[% job.status | html %]") );
278
                    { "aTargets": [ -1, -2 ], "bSortable": false, "bSearchable": false },
236
                $("#job_type_description").html( get_job_type("[% job.type | html %]") );
279
                ],
237
                $("fieldset.rows").show();
280
                "aaSorting": [[ 0, "desc" ]],
238
            [% END %]
281
                "iDisplayLength": 10,
239
282
                "sPaginationType": "full_numbers"
240
            let additional_filters = {
283
            }));
241
                started_on: function(){
242
                    let now = new Date();
243
                    if ( $("#include_last_hour").is(":checked") ) {
244
                        now.setHours(now.getHours() - 1);
245
                        return { ">": now.toISOString() };
246
                    } else {
247
                        return { "<": now.toISOString() };
248
                    }
249
                }
250
            };
251
252
            let only_current_filter = function(){
253
                if ( $("#only_current").is(":checked") ) {
254
                    return 'only_current=1';
255
                } else {
256
                    return 'only_current=0';
257
                }
258
            }
259
260
            let jobs_table = $("#table_jobs").kohaTable({
261
                "ajax": {
262
                    "url": "/api/v1/jobs?" + only_current_filter()
263
                },
264
                "order": [[ 1, "desc" ]],
265
                "columns": [
266
                    {
267
                        "data": "job_id",
268
                        "searchable": true,
269
                        "orderable": true
270
                    },
271
                    {
272
                        "data": "status",
273
                        "searchable": true,
274
                        "orderable": true,
275
                        "render": function(data, type, row, meta) {
276
                            return get_job_status(row.status).escapeHtml();
277
                        }
278
                    },
279
                    {
280
                        "data": "progress,size",
281
                        "searchable": false,
282
                        "orderable": true,
283
                        "render": function(data, type, row, meta) {
284
                            return "%s/%s".format(row.progress, row.size).escapeHtml();
285
                        }
286
                    },
287
                    {
288
                        "data": "type",
289
                        "searchable": true,
290
                        "orderable": true,
291
                        "render": function(data, type, row, meta) {
292
                            return get_job_type(row.type).escapeHtml();
293
                        }
294
                    },
295
                    {
296
                        "data": "enqueued_date",
297
                        "searchable": true,
298
                        "orderable": true,
299
                        "render": function(data, type, row, meta) {
300
                            return $datetime(row.enqueued_date);
301
                        }
302
                    },
303
                    {
304
                        "data": "started_date",
305
                        "searchable": true,
306
                        "orderable": true,
307
                        "render": function(data, type, row, meta) {
308
                            return $datetime(row.started_date);
309
                        }
310
                    },
311
                    {
312
                        "data": "ended_date",
313
                        "searchable": true,
314
                        "orderable": true,
315
                        "render": function(data, type, row, meta) {
316
                            return $datetime(row.ended_date);
317
                        }
318
                    },
319
                    {
320
                        "data": function( row, type, val, meta ) {
321
                            var result = '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/background_jobs.pl?op=view&amp;id='+ encodeURIComponent(row.job_id) +'"><i class="fa fa-eye" aria-hidden="true"></i> '+_("View")+'</a>'+"\n";
322
                            if ( row.status == 'new' || row.status == 'started' ) {
323
                                result += '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/bakcground_jobs.pl?op=cancel&amp;id='+ encodeURIComponent(row.job_id) +'"><i class="fa fa-trash" aria-hidden="true"></i> '+_("Cancel")+'</a>';
324
                            }
325
                            return result;
326
                        },
327
                        "searchable": false,
328
                        "orderable": false
329
                    }
330
                ]
331
            }, null, 1, additional_filters);
332
333
            $("#include_last_hour").on("change", function(){
334
                jobs_table.DataTable().draw();
335
                return false;
336
            });
284
337
285
            $("#table_complete_jobs").dataTable($.extend(true, {}, dataTablesDefaults, {
338
            $("#only_current").on("change", function(){
286
                "aoColumnDefs": [
339
                jobs_table.DataTable().ajax.url("/api/v1/jobs?" + only_current_filter()).load();
287
                    { "aTargets": [ -1, -2 ], "bSortable": false, "bSearchable": false },
340
                return false;
288
                ],
341
            });
289
                "aaSorting": [[ 0, "desc" ]],
290
                "iDisplayLength": 10,
291
                "sPaginationType": "full_numbers"
292
            }));
293
        });
342
        });
294
    </script>
343
    </script>
295
    [% IF op == 'view' %]
344
    [% IF op == 'view' %]
(-)a/t/db_dependent/Koha/BackgroundJobs.t (-2 / +51 lines)
Lines 19-27 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::More tests => 12;
22
use Test::More tests => 14;
23
use Test::MockModule;
23
use Test::MockModule;
24
24
25
use List::MoreUtils qw(any);
26
25
use Koha::Database;
27
use Koha::Database;
26
use Koha::BackgroundJobs;
28
use Koha::BackgroundJobs;
27
use Koha::DateUtils qw( dt_from_string );
29
use Koha::DateUtils qw( dt_from_string );
Lines 31-37 use t::lib::Mocks; Link Here
31
use t::lib::Dates;
33
use t::lib::Dates;
32
use t::lib::Koha::BackgroundJob::BatchTest;
34
use t::lib::Koha::BackgroundJob::BatchTest;
33
35
34
my $schema = Koha::Database->new->schema;
36
my $builder = t::lib::TestBuilder->new;
37
my $schema  = Koha::Database->new->schema;
35
$schema->storage->txn_begin;
38
$schema->storage->txn_begin;
36
39
37
t::lib::Mocks::mock_userenv;
40
t::lib::Mocks::mock_userenv;
Lines 91-93 is_deeply( Link Here
91
is_deeply( $new_job->additional_report(), {} );
94
is_deeply( $new_job->additional_report(), {} );
92
95
93
$schema->storage->txn_rollback;
96
$schema->storage->txn_rollback;
97
98
subtest 'filter_by_current() tests' => sub {
99
100
    plan tests => 4;
101
102
    $schema->storage->txn_begin;
103
104
    my $job_new       = $builder->build_object( { class => 'Koha::BackgroundJobs', value => { status => 'new' } } );
105
    my $job_cancelled = $builder->build_object( { class => 'Koha::BackgroundJobs', value => { status => 'cancelled' } } );
106
    my $job_failed    = $builder->build_object( { class => 'Koha::BackgroundJobs', value => { status => 'failed' } } );
107
    my $job_finished  = $builder->build_object( { class => 'Koha::BackgroundJobs', value => { status => 'finished' } } );
108
109
    my $rs = Koha::BackgroundJobs->search(
110
        {
111
            id => [ $job_new->id, $job_cancelled->id, $job_failed->id, $job_finished->id ]
112
        }
113
    );
114
115
    is( $rs->count, 4, '4 jobs in resultset' );
116
    ok( any {$_->status eq 'new'} @{$rs->as_list}, "There is a 'new' job"  );
117
118
    $rs = $rs->filter_by_current;
119
120
    is( $rs->count, 1, 'Only 1 job in filtered resultset' );
121
    is( $rs->next->status, 'new', "The only job in resultset is 'new'"  );
122
123
    $schema->storage->txn_rollback;
124
};
125
126
subtest 'search_limited' => sub {
127
    plan tests => 3;
128
129
    $schema->storage->txn_begin;
130
    my $patron1 = $builder->build_object( { class => 'Koha::Patrons', value => { flags => 0 } } );
131
    my $patron2 = $builder->build_object( { class => 'Koha::Patrons', value => { flags => 0 } } );
132
    my $job1 = $builder->build_object( { class => 'Koha::BackgroundJobs', value => { borrowernumber => $patron1->id } } );
133
134
    C4::Context->set_userenv( undef, q{} );
135
    is( Koha::BackgroundJobs->search_limited->count, 0, 'No jobs found without userenv' );
136
    C4::Context->set_userenv( $patron1->id, $patron1->userid );
137
    is( Koha::BackgroundJobs->search_limited->count, 1, 'My job found' );
138
    C4::Context->set_userenv( $patron2->id, $patron2->userid );
139
    is( Koha::BackgroundJobs->search_limited->count, 0, 'No jobs for me' );
140
141
    $schema->storage->txn_rollback;
142
};
(-)a/t/db_dependent/api/v1/jobs.t (+125 lines)
Line 0 Link Here
1
#!/usr/bin/env perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 25;
21
use Test::Mojo;
22
23
use t::lib::TestBuilder;
24
use t::lib::Mocks;
25
26
use Koha::BackgroundJobs;
27
use Koha::Database;
28
29
my $schema  = Koha::Database->new->schema;
30
my $builder = t::lib::TestBuilder->new;
31
32
my $t = Test::Mojo->new('Koha::REST::V1');
33
#use t::lib::Mojo;
34
#my $t = t::lib::Mojo->new('Koha::REST::V1');
35
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
36
37
$schema->storage->txn_begin;
38
39
Koha::BackgroundJobs->delete;
40
my $superlibrarian = $builder->build_object(
41
    {
42
        class => 'Koha::Patrons',
43
        value => { flags => 1 },
44
    }
45
);
46
my $password = 'thePassword123';
47
$superlibrarian->set_password( { password => $password, skip_validation => 1 } );
48
my $superlibrarian_userid = $superlibrarian->userid;
49
50
my $librarian = $builder->build_object(
51
    {
52
        class => 'Koha::Patrons',
53
        value => { flags => 2 ** 2 }, # catalogue flag = 2
54
    }
55
);
56
$librarian->set_password( { password => $password, skip_validation => 1 } );
57
my $librarian_userid = $librarian->userid;
58
59
my $patron = $builder->build_object(
60
    {
61
        class => 'Koha::Patrons',
62
        value => { flags => 0 },
63
    }
64
);
65
$patron->set_password( { password => $password, skip_validation => 1 } );
66
my $patron_userid = $patron->userid;
67
68
$t->get_ok("//$librarian_userid:$password@/api/v1/jobs")
69
  ->status_is(200)
70
  ->json_is( [] );
71
72
my $job = $builder->build_object(
73
    {
74
        class => 'Koha::BackgroundJobs',
75
        value => {
76
            status         => 'finished',
77
            progress       => 100,
78
            size           => 100,
79
            borrowernumber => $patron->borrowernumber,
80
            type           => 'batch_item_record_modification',
81
            queue => 'default',
82
            #data => '{"record_ids":["1"],"regex_mod":null,"exclude_from_local_holds_priority":null,"new_values":{"itemnotes":"xxx"}}' ,
83
            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}',
84
        }
85
    }
86
);
87
88
{
89
    $t->get_ok("//$superlibrarian_userid:$password@/api/v1/jobs")
90
      ->status_is(200)->json_is( [ $job->to_api ] );
91
92
    $t->get_ok("//$librarian_userid:$password@/api/v1/jobs")
93
      ->status_is(200)->json_is( [] );
94
95
    $t->get_ok("//$patron_userid:$password@/api/v1/jobs")
96
      ->status_is(403);
97
98
    $job->borrowernumber( $librarian->borrowernumber )->store;
99
100
    $t->get_ok("//$librarian_userid:$password@/api/v1/jobs")
101
      ->status_is(200)->json_is( [ $job->to_api ] );
102
}
103
104
{
105
    $t->get_ok( "//$superlibrarian_userid:$password@/api/v1/jobs/"
106
          . $job->id )->status_is(200)
107
      ->json_is( $job->to_api );
108
109
    $t->get_ok( "//$librarian_userid:$password@/api/v1/jobs/"
110
          . $job->id )->status_is(200)
111
      ->json_is( $job->to_api );
112
113
    $job->borrowernumber( $superlibrarian->borrowernumber )->store;
114
    $t->get_ok( "//$librarian_userid:$password@/api/v1/jobs/"
115
          . $job->id )->status_is(403);
116
}
117
118
{
119
    $job->delete;
120
    $t->get_ok( "//$superlibrarian_userid:$password@/api/v1/jobs/"
121
          . $job->id )->status_is(404)
122
      ->json_is( '/error' => 'Object not found' );
123
}
124
125
$schema->storage->txn_rollback;
(-)a/t/lib/TestBuilder.pm (-1 / +3 lines)
Lines 547-552 sub _gen_blob { Link Here
547
sub _gen_default_values {
547
sub _gen_default_values {
548
    my ($self) = @_;
548
    my ($self) = @_;
549
    return {
549
    return {
550
        BackgroundJob => {
551
            context => '{}'
552
        },
550
        Borrower => {
553
        Borrower => {
551
            login_attempts => 0,
554
            login_attempts => 0,
552
            gonenoaddress  => undef,
555
            gonenoaddress  => undef,
553
- 

Return to bug 30982