Skip to content

Commit ecb159d

Browse files
committed
MDL-70854 core: Add stored progress bars.
- Alters existing progress_bar class to support extension - Adds stored_progress_bar class as child of progress_bar - Adds webservice to poll stored progress - Updates database tables - Bumps version - Adds unit/behat tests
1 parent 462d5f0 commit ecb159d

28 files changed

+1413
-56
lines changed

admin/tool/task/classes/running_tasks_table.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public function __construct() {
4949
'classname' => get_string('classname', 'tool_task'),
5050
'type' => get_string('tasktype', 'admin'),
5151
'time' => get_string('taskage', 'tool_task'),
52+
'progress' => get_string('progress', 'core'),
5253
'timestarted' => get_string('started', 'tool_task'),
5354
'hostname' => get_string('hostname', 'tool_task'),
5455
'pid' => get_string('pid', 'tool_task'),
@@ -153,4 +154,29 @@ public function col_time($row): string {
153154
public function col_timestarted($row): string {
154155
return userdate($row->timestarted);
155156
}
157+
158+
/**
159+
* Format the progress column.
160+
*
161+
* @param \stdClass $row
162+
* @return string
163+
*/
164+
public function col_progress($row): string {
165+
global $DB;
166+
167+
// Check to see if there is a stored progress record for this task.
168+
if ($row->type === 'adhoc') {
169+
$idnumber = \core\stored_progress_bar::convert_to_idnumber($row->classname, $row->id);
170+
} else {
171+
$idnumber = \core\stored_progress_bar::convert_to_idnumber($row->classname);
172+
}
173+
174+
$bar = \core\stored_progress_bar::get_by_idnumber($idnumber);
175+
if ($bar) {
176+
return $bar->get_content();
177+
} else {
178+
return '-';
179+
}
180+
}
181+
156182
}

admin/tool/task/runningtasks.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* @copyright 2020 Mikhail Golenkov <[email protected]>
2323
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2424
*/
25+
define('NO_OUTPUT_BUFFERING', true);
2526

2627
require_once(__DIR__ . '/../../../config.php');
2728
require_once($CFG->libdir.'/adminlib.php');

admin/tool/task/tests/behat/running_tasks.feature

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,25 @@ Feature: See running scheduled tasks
3838
And I should see "2 days" in the "core\task\asynchronous_restore_task" "table_row"
3939
And I should see "c69335460f7f" in the "core\task\asynchronous_restore_task" "table_row"
4040
And I should see "1916" in the "core\task\asynchronous_restore_task" "table_row"
41+
42+
@javascript
43+
Scenario: If a task with a stored progress bar is running, I should be able to observe the progress.
44+
Given the following config values are set as admin:
45+
| progresspollinterval | 1 |
46+
And the following "tool_task > scheduled tasks" exist:
47+
| classname | seconds | hostname | pid |
48+
| \core\task\delete_unconfirmed_users_task | 120 | c69335460f7f | 1917 |
49+
And the following "stored progress bars" exist:
50+
| idnumber | percent |
51+
| core_task_delete_unconfirmed_users_task | 50.00 |
52+
And I navigate to "Server > Tasks > Tasks running now" in site administration
53+
And I should see "2 mins" in the "Delete unconfirmed users" "table_row"
54+
And I should see "c69335460f7f" in the "Delete unconfirmed users" "table_row"
55+
And I should see "1917" in the "Delete unconfirmed users" "table_row"
56+
And I should see "50.0%" in the "Delete unconfirmed users" "table_row"
57+
58+
When I set the stored progress bar "core_task_delete_unconfirmed_users_task" to "75.00"
59+
# Wait for the progress polling.
60+
And I wait "1" seconds
61+
Then I should not see "50.0%" in the "Delete unconfirmed users" "table_row"
62+
And I should see "75.0%" in the "Delete unconfirmed users" "table_row"

lang/en/admin.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,7 @@
13361336
$string['stickyblocksduplicatenotice'] = 'If any block you add here is already present in a particular page, it will result in a duplicate.<br />Only the pinned block will be non-editable, the duplicate will still be editable.';
13371337
$string['stickyblocksmymoodle'] = 'My Moodle';
13381338
$string['stickyblockspagetype'] = 'Page type to configure';
1339+
$string['storedprogressbarcleanuptask'] = 'Stored progress bar cleanup task';
13391340
$string['strictformsrequired'] = 'Strict validation of required fields';
13401341
$string['stripalltitletags'] = 'Remove HTML tags from all activity names';
13411342
$string['supportandservices'] = 'Support and services';

lib/amd/build/stored_progress.min.js

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/amd/build/stored_progress.min.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/amd/src/stored_progress.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// This file is part of Moodle - http://moodle.org/
2+
//
3+
// Moodle is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation, either version 3 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// Moodle is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
15+
16+
/**
17+
* Script to update stored_progress progress bars on the screen.
18+
*
19+
* @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
20+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
21+
* @author Conn Warwicker <[email protected]>
22+
*/
23+
24+
/*global updateProgressBar*/
25+
26+
import * as Ajax from 'core/ajax';
27+
28+
/**
29+
* @var bool This AMD script is loaded multiple times, for each progress bar on a page.
30+
* So this stops it running multiple times.
31+
* */
32+
var STORED_PROGRESS_LOADED = false;
33+
34+
/**
35+
* Poll a given stored progress record.
36+
*
37+
* @param {array} ids
38+
* @param {integer} timeout
39+
*/
40+
function poll(ids, timeout) {
41+
42+
// Call AJAX request.
43+
let promise = Ajax.call([{
44+
methodname: 'core_poll_stored_progress', args: {'ids': ids}
45+
}]);
46+
47+
let repollids = [];
48+
49+
// When AJAX request returns, handle the results.
50+
promise[0].then(function(results) {
51+
52+
results.forEach(function(data) {
53+
54+
// Update the progress bar percentage and message using the core method from the javascript-static.js.
55+
updateProgressBar(data.uniqueid, data.progress, data.message, data.estimated, data.error);
56+
57+
// Add the bar for re-polling if it's not completed.
58+
if (data.progress < 100 && !data.error) {
59+
repollids.push(data.id);
60+
}
61+
62+
// If a different timeout came back from the script, use that instead.
63+
if (data.timeout && data.timeout > 0) {
64+
timeout = data.timeout;
65+
}
66+
67+
});
68+
69+
// If we still want to poll any of them, do it again.
70+
if (repollids.length > 0) {
71+
setTimeout(() => poll(repollids, timeout), timeout * 1000);
72+
}
73+
74+
}).catch(function() {
75+
// Do nothing.
76+
});
77+
78+
}
79+
80+
/**
81+
* Initialise the polling process.
82+
*
83+
* @param {integer} timeout Timeout to use (seconds).
84+
*/
85+
export const init = (timeout) => {
86+
87+
if (STORED_PROGRESS_LOADED === false) {
88+
89+
let ids = [];
90+
91+
// Find any stored progress bars we want to poll.
92+
document.querySelectorAll('.stored-progress-bar').forEach(el => {
93+
94+
// Get its id and add to array.
95+
let id = el.getAttribute('attr-id');
96+
ids.push(id);
97+
98+
});
99+
100+
// Poll for updates from these IDs.
101+
poll(ids, timeout);
102+
103+
// Script has run, we don't want it to run again.
104+
STORED_PROGRESS_LOADED = true;
105+
106+
}
107+
108+
};

lib/behat/classes/behat_core_generator.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,11 @@ protected function get_creatable_entities(): array {
316316
'required' => ['subject', 'userfrom', 'userto'],
317317
'switchids' => ['userfrom' => 'userfromid', 'userto' => 'usertoid'],
318318
],
319+
'stored progress bars' => [
320+
'singular' => 'stored progress bar',
321+
'datagenerator' => 'stored_progress_bar',
322+
'required' => ['idnumber'],
323+
],
319324
];
320325

321326
return $entities;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
namespace core\external;
18+
19+
use core_external\external_function_parameters;
20+
use core_external\external_multiple_structure;
21+
use core_external\external_single_structure;
22+
use core_external\external_value;
23+
24+
/**
25+
* Poll Stored Progress webservice.
26+
*
27+
* @package core
28+
* @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
29+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30+
* @author Conn Warwicker <[email protected]>
31+
*/
32+
class poll_stored_progress extends \core_external\external_api {
33+
34+
/**
35+
* Returns description of method parameters
36+
*
37+
* @return external_function_parameters
38+
*/
39+
public static function execute_parameters() {
40+
return new external_function_parameters([
41+
'ids' => new external_multiple_structure(
42+
new external_value(PARAM_INT, 'The stored_progress ID', VALUE_REQUIRED)
43+
),
44+
]);
45+
}
46+
47+
/**
48+
* Returns description of method return data
49+
*
50+
* @return external_multiple_structure
51+
*/
52+
public static function execute_returns() {
53+
return new external_multiple_structure(
54+
new external_single_structure([
55+
'id' => new external_value(PARAM_INT, 'stored_progress record id'),
56+
'uniqueid' => new external_value(PARAM_TEXT, 'unique element id'),
57+
'progress' => new external_value(PARAM_FLOAT, 'percentage progress'),
58+
'estimated' => new external_value(PARAM_RAW, 'estimated time left string'),
59+
'message' => new external_value(PARAM_TEXT, 'message to be displayed with the bar'),
60+
'error' => new external_value(PARAM_TEXT, 'error', VALUE_OPTIONAL),
61+
'timeout' => new external_value(PARAM_TEXT, 'timeout to use in the polling', VALUE_OPTIONAL),
62+
])
63+
);
64+
}
65+
66+
/**
67+
* Poll the database for the progress of stored progress objects
68+
*
69+
* @param array $ids
70+
* @return array
71+
*/
72+
public static function execute(array $ids) {
73+
global $CFG, $DB;
74+
75+
$params = self::validate_parameters(self::execute_parameters(), [
76+
'ids' => $ids,
77+
]);
78+
79+
$return = [];
80+
81+
foreach ($ids as $id) {
82+
83+
// Load the stored progress bar object.
84+
$bar = \core\stored_progress_bar::get_by_id($id);
85+
if ($bar) {
86+
87+
// Return the updated bar data.
88+
$return[$id] = [
89+
'id' => $id,
90+
'uniqueid' => $bar->get_id(),
91+
'progress' => $bar->get_percent(),
92+
'estimated' => $bar->get_estimate_message($bar->get_percent()),
93+
'message' => $bar->get_message(),
94+
'timeout' => \core\stored_progress_bar::get_timeout(),
95+
'error' => $bar->get_haserrored(),
96+
];
97+
98+
} else {
99+
100+
// If we could not find the record, we still need to return the right arguments in the array for the webservice.
101+
$return[$id] = [
102+
'id' => $id,
103+
'uniqueid' => '',
104+
'progress' => 0,
105+
'estimated' => '',
106+
'message' => get_string('invalidrecordunknown', 'error'),
107+
'timeout' => \core\stored_progress_bar::get_timeout(),
108+
'error' => true,
109+
];
110+
111+
}
112+
113+
}
114+
115+
return $return;
116+
}
117+
118+
}

0 commit comments

Comments
 (0)