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

(-)a/Koha/BackgroundJob.pm (+14 lines)
Lines 454-459 sub plugin_types_to_classes { Link Here
454
    return $self->{_plugin_mapping};
454
    return $self->{_plugin_mapping};
455
}
455
}
456
456
457
=head3 to_api_mapping
458
459
This method returns the mapping for representing a Koha::BackgroundJob object
460
on the API.
461
462
=cut
463
464
sub to_api_mapping {
465
    return {
466
        id             => 'background_job_id',
467
        borrowernumber => 'patron_id',
468
    };
469
}
470
457
=head3 _type
471
=head3 _type
458
472
459
=cut
473
=cut
(-)a/Koha/REST/V1/BackgroundJobs.pm (+58 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
=cut
33
34
sub list {
35
    my $c = shift->openapi->valid_input or return;
36
37
    return try {
38
        my $patron = $c->stash('koha.user');
39
40
        my $can_manage_background_jobs =
41
          $patron->has_permission( { parameters => 'manage_background_jobs' } );
42
43
        my $background_jobs_set =
44
          $can_manage_background_jobs
45
          ? Koha::BackgroundJobs->new
46
          : Koha::BackgroundJobs->search(
47
            { borrowernumber => $patron->borrowernumber } );
48
49
        my $background_jobs = $c->objects->search( $background_jobs_set );
50
        return $c->render( status => 200, openapi => $background_jobs );
51
    }
52
    catch {
53
        $c->unhandled_exception($_);
54
    };
55
56
}
57
58
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/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt (-116 / +171 lines)
Lines 43-49 Link Here
43
        <span>Holds queue update</span>
43
        <span>Holds queue update</span>
44
    [% CASE %]<span>Unknown job type '[% job_type | html %]'</span>
44
    [% CASE %]<span>Unknown job type '[% job_type | html %]'</span>
45
    [% END %]
45
    [% END %]
46
47
[% END %]
46
[% END %]
48
[% INCLUDE 'doc-head-open.inc' %]
47
[% INCLUDE 'doc-head-open.inc' %]
49
<title>
48
<title>
Lines 156-260 Link Here
156
155
157
    <h1>Background jobs</h1>
156
    <h1>Background jobs</h1>
158
157
159
    <div id="taskstabs" class="toptabs">
158
    <label for="include_last_hour">Only include jobs started in the last hour</label>
160
        <ul class="nav nav-tabs" role="tablist">
159
    <input type="checkbox" id="include_last_hour" checked />
161
            <li role="presentation" class="active"><a href="#queued" aria-controls="queued" role="tab" data-toggle="tab">Queued jobs</a></li>
162
            <li role="presentation"><a href="#complete" aria-controls="complete" role="tab" data-toggle="tab">Complete jobs</a></li>
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
160
210
            <div role="tabpanel" class="tab-pane" id="complete">
161
    <table id="table_jobs">
211
                [% IF complete.count %]
162
        <thead>
212
                    <p>Jobs completed in the last 60 minutes.</p>
163
            <tr>
213
                    <table id="table_complete_jobs">
164
                <th>Job ID</th>
214
                        <thead>
165
                <th data-filter="job_statuses">Status</th>
215
                            <tr>
166
                <th>Progress</th>
216
                                <th>Job ID</th>
167
                <th data-filter="job_types">Type</th>
217
                                <th>Status</th>
168
                <th>Queued</th>
218
                                <th>Progress</th>
169
                <th>Started</th>
219
                                <th>Type</th>
170
                <th>Ended</th>
220
                                <th>Queued</th>
171
                <th class="noExport">Actions</th>
221
                                <th>Started</th>
172
            </tr>
222
                                <th>Ended</th>
173
        </thead>
223
                                <th class="noExport">Actions</th>
174
    </table>
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>
258
175
259
[% END %]
176
[% END %]
260
177
Lines 270-295 Link Here
270
187
271
[% MACRO jsinclude BLOCK %]
188
[% MACRO jsinclude BLOCK %]
272
    [% Asset.js("js/admin-menu.js") | $raw %]
189
    [% Asset.js("js/admin-menu.js") | $raw %]
190
    [% INCLUDE 'js-date-format.inc' %]
273
    [% INCLUDE 'datatables.inc' %]
191
    [% INCLUDE 'datatables.inc' %]
274
    <script>
192
    <script>
193
        const job_statuses = [
194
            {'_id': 'new',       '_str': _('New')},
195
            {'_id': 'cancelled', '_str': _('Cancelled')},
196
            {'_id': 'finished',  '_str': _('Finished')},
197
            {'_id': 'started',   '_str': _('Started')},
198
            {'_id': 'running',   '_str': _('Running')},
199
            {'_id': 'failed',    '_str': _('Failed')},
200
        ];
201
        function get_job_status (status) {
202
            let status_lib = job_statuses.find( s => s._id == status );
203
            if (status_lib) {
204
                return status_lib._str;
205
            }
206
            return status;
207
        }
208
209
        const job_types = [
210
            {
211
                '_id': 'batch_biblio_record_modification',
212
                '_str': _('Batch bibliographic record modification')
213
            },
214
            {
215
                '_id': 'batch_biblio_record_deletion',
216
                '_str': _('Batch bibliographic record record deletion')
217
            },
218
            {
219
                '_id': 'batch_authority_record_modification',
220
                '_str': _('Batch authority record modification')
221
            },
222
            {
223
                '_id': 'batch_authority_record_deletion',
224
                '_str': _('Batch authority record deletion')
225
            },
226
            {
227
                '_id': 'batch_item_record_modification',
228
                '_str': _('Batch item record modification')
229
            },
230
            {
231
                '_id': 'batch_item_record_deletion',
232
                '_str': _('Batch item record deletion')
233
            },
234
            {
235
                '_id': 'batch_hold_cancel',
236
                '_str': _('Batch hold cancellation')
237
            },
238
            {
239
                '_id': 'update_elastic_index'
240
                , '_str': _('Update Elasticsearch index')
241
            },
242
            {
243
                '_id': 'update_holds_queue_for_biblios',
244
                '_str': _('Holds queue update')
245
            }
246
        ];
247
248
        function get_job_type (job_type) {
249
            let job_type_lib = job_types.find( t => t._id == job_type );
250
            if ( job_type_lib ) {
251
                return job_type_lib._str;
252
            }
253
            return _("Unknown job type '%s'").format(job_type);
254
        }
255
275
        $(document).ready(function() {
256
        $(document).ready(function() {
276
            $("#table_queued_jobs").dataTable($.extend(true, {}, dataTablesDefaults, {
257
            let additional_filters = {
277
                "aoColumnDefs": [
258
                started_on: function(){
278
                    { "aTargets": [ -1, -2 ], "bSortable": false, "bSearchable": false },
259
                    let now = new Date();
279
                ],
260
                    if ( $("#include_last_hour").is(":checked") ) {
280
                "aaSorting": [[ 0, "desc" ]],
261
                        now.setHours(now.getHours() - 1);
281
                "iDisplayLength": 10,
262
                        return { ">": now.toISOString() };
282
                "sPaginationType": "full_numbers"
263
                    } else {
283
            }));
264
                    console.log("not checked");
265
                        return { "<": now.toISOString() };
266
                    }
267
                },
268
            };
269
            let jobs_table = $("#table_jobs").kohaTable({
270
                "ajax": {
271
                    "url": "/api/v1/background_jobs"
272
                },
273
                "order": [[ 1, "desc" ]],
274
                "columns": [
275
                    {
276
                        "data": "background_job_id",
277
                        "searchable": true,
278
                        "orderable": true
279
                    },
280
                    {
281
                        "data": "status",
282
                        "searchable": true,
283
                        "orderable": true,
284
                        "render": function(data, type, row, meta) {
285
                            return get_job_status(row.status).escapeHtml();
286
                        },
287
                    },
288
                    {
289
                        "data": "progress,size",
290
                        "searchable": false,
291
                        "orderable": true,
292
                        "render": function(data, type, row, meta) {
293
                            return "%s/%s".format(row.progress, row.size).escapeHtml();
294
                        },
295
                    },
296
                    {
297
                        "data": "type",
298
                        "searchable": true,
299
                        "orderable": true,
300
                        "render": function(data, type, row, meta) {
301
                            return get_job_type(row.type).escapeHtml();
302
                        },
303
                    },
304
                    {
305
                        "data": "enqueued_on",
306
                        "searchable": true,
307
                        "orderable": true,
308
                        "render": function(data, type, row, meta) {
309
                            return $datetime(row.enqueued_on);
310
                        },
311
                    },
312
                    {
313
                        "data": "started_on",
314
                        "searchable": true,
315
                        "orderable": true,
316
                        "render": function(data, type, row, meta) {
317
                            return $datetime(row.started_on);
318
                        },
319
                    },
320
321
                    {
322
                        "data": "ended_on",
323
                        "searchable": true,
324
                        "orderable": true,
325
                        "render": function(data, type, row, meta) {
326
                            return $datetime(row.ended_on);
327
                        },
328
                    },
329
                    {
330
                        "data": function( row, type, val, meta ) {
331
                            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.background_job_id) +'"><i class="fa fa-eye" aria-hidden="true"></i> '+_("View")+'</a>'+"\n";
332
                            if ( row.status == 'new' || row.status == 'started' ) {
333
                                result += '<a class="btn btn-default btn-xs" role="button" href="/cgi-bin/koha/admin/bakcground_jobs.pl?op=cancel&amp;id='+ encodeURIComponent(row.background_job_id) +'"><i class="fa fa-trash" aria-hidden="true"></i> '+_("Cancel")+'</a>';
334
                            }
335
                            return result;
336
337
                        },
338
                        "searchable": false,
339
                        "orderable": false
340
                    }
341
                ]
342
            }, null, 1, additional_filters);
343
344
            $("#include_last_hour").on("change", function(){
345
                jobs_table.DataTable().draw();
346
                return false;
347
            });
284
348
285
            $("#table_complete_jobs").dataTable($.extend(true, {}, dataTablesDefaults, {
286
                "aoColumnDefs": [
287
                    { "aTargets": [ -1, -2 ], "bSortable": false, "bSearchable": false },
288
                ],
289
                "aaSorting": [[ 0, "desc" ]],
290
                "iDisplayLength": 10,
291
                "sPaginationType": "full_numbers"
292
            }));
293
        });
349
        });
294
    </script>
350
    </script>
295
    [% IF op == 'view' %]
351
    [% IF op == 'view' %]
296
- 

Return to bug 30982