From 91cdc95c25ec5ff29daddb58dc1d25c27451bc29 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Tue, 3 Sep 2024 14:37:39 +0100 Subject: [PATCH] MDL-81714 grades: Display "Run now" button for admins in task indicator --- .../moodle/components/task-indicator.md | 26 ++++++++++++++ grade/tests/behat/grade_async_regrade.feature | 15 ++++++++ lib/amd/build/task_indicator.min.js | 2 +- lib/amd/build/task_indicator.min.js.map | 2 +- lib/amd/src/task_indicator.js | 8 ++++- lib/classes/output/task_indicator.php | 27 +++++++++++++- lib/templates/task_indicator.mustache | 35 +++++++++++-------- 7 files changed, 96 insertions(+), 19 deletions(-) diff --git a/admin/tool/componentlibrary/content/moodle/components/task-indicator.md b/admin/tool/componentlibrary/content/moodle/components/task-indicator.md index 4216f49c3ef29..580b3fa658d62 100644 --- a/admin/tool/componentlibrary/content/moodle/components/task-indicator.md +++ b/admin/tool/componentlibrary/content/moodle/components/task-indicator.md @@ -91,3 +91,29 @@ if ($taskindicator->has_task_record()) { If the optional `redirecturl` parameter is set when creating the indicator, the page will automatically reload or redirect to this URL when the progress bar completes. + +While the task is still queued, admins will see a "Run now" button below the progress bar. This is designed for convenience if +a user is blocked on a job and needs the task run immediately. It will run the specific instance of the task tracked by the +indicator. + +{{< mustache template="core/task_indicator" >}} +{ + "heading": "Regrade in progress", + "icon": { + "attributes": [ + {"name": "src", "value": "/pix/i/timer.svg"}, + {"name": "alt", "value": ""} + ] + }, + "message": "Grades are being recalculated due to recent changes.", + "progress": { + "id": "progressbar_test", + "message": "Task pending", + "idnumber": "progressbar_test", + "width": "500", + "value": "0" + }, + "runurl": "http://example.com/runtask.php?id=1", + "runlabel": "Run now" +} +{{< /mustache >}} diff --git a/grade/tests/behat/grade_async_regrade.feature b/grade/tests/behat/grade_async_regrade.feature index 388b3ba0ef333..26981c8173734 100644 --- a/grade/tests/behat/grade_async_regrade.feature +++ b/grade/tests/behat/grade_async_regrade.feature @@ -87,6 +87,7 @@ Feature: Asynchronous regrade on a large course When I am on the "Test course 1" "grades > Grader report > View" page logged in as teacher1 And I should see "Grades are being recalculated due to recent changes." And I should see "Task pending" + And I should not see "Run now" And I should see "0.0%" And "user-grades" "table" should not exist When I run all adhoc tasks @@ -100,3 +101,17 @@ Feature: Asynchronous regrade on a large course And I set the field "Search users" to "Student 1" And "user-grades" "table" should exist And "40.00" "text" should exist in the "student1@example.com" "table_row" + + Scenario: Admin should see a "Run now" button that disappears once the task starts running + When I am on the "Test course 1" "grades > Grader report > View" page logged in as admin + And I should see "Grades are being recalculated due to recent changes." + And I should see "Task pending" + And I should see "Run now" + And I should see "0.0%" + And "user-grades" "table" should not exist + When I run all adhoc tasks + # Progress bar should update. + Then I should not see "Task pending" + And I should not see "Run now" + And I should see "Recalculating grades" + And I should see "100%" diff --git a/lib/amd/build/task_indicator.min.js b/lib/amd/build/task_indicator.min.js index da44e9133794d..799b99c2ef64b 100644 --- a/lib/amd/build/task_indicator.min.js +++ b/lib/amd/build/task_indicator.min.js @@ -9,6 +9,6 @@ define("core/task_indicator",["exports"],(function(_exports){Object.defineProper * @author Mark Johnson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class{static init(id,redirectUrl){document.getElementById(id).addEventListener("update",(event=>{100===event.detail.percent&&window.setTimeout((()=>window.location.assign(redirectUrl)),2e3)}))}},_exports.default})); +class{static init(id,redirectUrl){document.getElementById(id).addEventListener("update",(event=>{const runlink=document.querySelector(".runlink[data-idnumber=".concat(id,"]"));runlink&&event.detail.percent>0&&runlink.remove(),""!==redirectUrl&&100===event.detail.percent&&window.setTimeout((()=>window.location.assign(redirectUrl)),2e3)}))}},_exports.default})); //# sourceMappingURL=task_indicator.min.js.map \ No newline at end of file diff --git a/lib/amd/build/task_indicator.min.js.map b/lib/amd/build/task_indicator.min.js.map index 40edf051f798d..f2428452b3bed 100644 --- a/lib/amd/build/task_indicator.min.js.map +++ b/lib/amd/build/task_indicator.min.js.map @@ -1 +1 @@ -{"version":3,"file":"task_indicator.min.js","sources":["../src/task_indicator.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Task indicator\n *\n * Watches the progress bar inside the task indicator for updates, and redirects when the progress is complete.\n *\n * @module core/task_indicator\n * @copyright 2024 Catalyst IT Europe Ltd\n * @author Mark Johnson \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n /**\n * Watch the progress bar for updates.\n *\n * When the progress bar is updated to 100%, wait a couple of seconds so the user gets to see it if they are watching,\n * then redirect to the specified URL.\n *\n * @param {String} id\n * @param {String} redirectUrl\n */\n static init(id, redirectUrl) {\n document.getElementById(id).addEventListener('update', (event) => {\n if (event.detail.percent === 100) {\n window.setTimeout(() => window.location.assign(redirectUrl), 2000);\n }\n });\n }\n}\n"],"names":["id","redirectUrl","document","getElementById","addEventListener","event","detail","percent","window","setTimeout","location","assign"],"mappings":";;;;;;;;;;;kBAmCgBA,GAAIC,aACZC,SAASC,eAAeH,IAAII,iBAAiB,UAAWC,QACvB,MAAzBA,MAAMC,OAAOC,SACbC,OAAOC,YAAW,IAAMD,OAAOE,SAASC,OAAOV,cAAc"} \ No newline at end of file +{"version":3,"file":"task_indicator.min.js","sources":["../src/task_indicator.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Task indicator\n *\n * Watches the progress bar inside the task indicator for updates, and redirects when the progress is complete.\n *\n * @module core/task_indicator\n * @copyright 2024 Catalyst IT Europe Ltd\n * @author Mark Johnson \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n /**\n * Watch the progress bar for updates.\n *\n * When the progress bar is updated to 100%, wait a couple of seconds so the user gets to see it if they are watching,\n * then redirect to the specified URL.\n *\n * @param {String} id\n * @param {String} redirectUrl\n */\n static init(id, redirectUrl) {\n document.getElementById(id).addEventListener('update', (event) => {\n // Once progress has started, remove the run link.\n const runlink = document.querySelector(`.runlink[data-idnumber=${id}]`);\n if (runlink && event.detail.percent > 0) {\n runlink.remove();\n }\n // Once the progress bar completes, redirect the page.\n if (redirectUrl !== '' && event.detail.percent === 100) {\n window.setTimeout(() => window.location.assign(redirectUrl), 2000);\n }\n });\n }\n}\n"],"names":["id","redirectUrl","document","getElementById","addEventListener","event","runlink","querySelector","detail","percent","remove","window","setTimeout","location","assign"],"mappings":";;;;;;;;;;;kBAmCgBA,GAAIC,aACZC,SAASC,eAAeH,IAAII,iBAAiB,UAAWC,cAE9CC,QAAUJ,SAASK,+CAAwCP,SAC7DM,SAAWD,MAAMG,OAAOC,QAAU,GAClCH,QAAQI,SAGQ,KAAhBT,aAA+C,MAAzBI,MAAMG,OAAOC,SACnCE,OAAOC,YAAW,IAAMD,OAAOE,SAASC,OAAOb,cAAc"} \ No newline at end of file diff --git a/lib/amd/src/task_indicator.js b/lib/amd/src/task_indicator.js index ba963e18edc38..189513a83e81d 100644 --- a/lib/amd/src/task_indicator.js +++ b/lib/amd/src/task_indicator.js @@ -35,7 +35,13 @@ export default class { */ static init(id, redirectUrl) { document.getElementById(id).addEventListener('update', (event) => { - if (event.detail.percent === 100) { + // Once progress has started, remove the run link. + const runlink = document.querySelector(`.runlink[data-idnumber=${id}]`); + if (runlink && event.detail.percent > 0) { + runlink.remove(); + } + // Once the progress bar completes, redirect the page. + if (redirectUrl !== '' && event.detail.percent === 100) { window.setTimeout(() => window.location.assign(redirectUrl), 2000); } }); diff --git a/lib/classes/output/task_indicator.php b/lib/classes/output/task_indicator.php index 8bbc6ee287580..2590d3172a91e 100644 --- a/lib/classes/output/task_indicator.php +++ b/lib/classes/output/task_indicator.php @@ -16,9 +16,11 @@ namespace core\output; +use core\plugin_manager; use core\task\adhoc_task; use core\task\stored_progress_task_trait; use core\url; +use core\context\system; use renderer_base; use stdClass; use renderable; @@ -27,6 +29,11 @@ /** * Indicator for displaying status and progress of a background task * + * This will display a section containing an icon, heading and message describing the background task being performed, + * as well as a progress bar that is updated as the task progresses. Optionally, it will redirect to a given URL (or reload + * the current one) when the task completes. If the task is still waiting in the queue, an admin viewing the indicator + * will also see a "Run now" button. + * * @package core * @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net} * @author Mark Johnson @@ -39,6 +46,12 @@ class task_indicator implements renderable, templatable { /** @var ?stored_progress_bar $progressbar */ protected ?stored_progress_bar $progressbar; + /** @var ?url $runurl The URL to manually run the task. */ + protected ?url $runurl = null; + + /** @var string $runlabel Label for the link to run the task. */ + protected string $runlabel = ''; + /** * Find the task record, and get the progress bar object. * @@ -83,6 +96,16 @@ protected function setup_task_data(): void { $this->task->set_id($this->taskrecord->id); $idnumber = stored_progress_bar::convert_to_idnumber($this->task::class, $this->task->get_id()); $this->progressbar = stored_progress_bar::get_by_idnumber($idnumber); + // As long as the tool_task plugin hasn't been removed, + // allow admins to trigger the task manually if it's not running yet. + if ( + array_key_exists('task', plugin_manager::instance()->get_present_plugins('tool')) + && is_null($this->taskrecord->timestarted) + && has_capability('moodle/site:config', system::instance()) + ) { + $this->runurl = new url('/admin/tool/task/run_adhoctasks.php', ['id' => $this->taskrecord->id]); + $this->runlabel = get_string('runnow', 'tool_task'); + } } } @@ -106,8 +129,10 @@ public function export_for_template(renderer_base $output): array { $export['message'] = $this->message; $export['progress'] = $this->progressbar->export_for_template($output); $export['icon'] = $this->icon ? $this->icon->export_for_template($output) : ''; - $export['redirecturl'] = $this->redirecturl->out(); + $export['redirecturl'] = $this->redirecturl?->out(); $export['extraclasses'] = implode(' ', $this->extraclasses); + $export['runurl'] = $this->runurl?->out(); + $export['runlabel'] = $this->runlabel; $this->progressbar->init_js(); } return $export; diff --git a/lib/templates/task_indicator.mustache b/lib/templates/task_indicator.mustache index e6732e32c8c82..15981e9661726 100644 --- a/lib/templates/task_indicator.mustache +++ b/lib/templates/task_indicator.mustache @@ -39,23 +39,28 @@ } }}
- {{#icon}} -
- {{>core/pix_icon}} -
- {{/icon}} -

{{heading}}

-

{{message}}

+
+ {{#icon}} +
+ {{>core/pix_icon}} +
+ {{/icon}} +

{{heading}}

+

{{message}}

+
{{#progress}} {{>core/progress_bar}} {{/progress}} -
+ {{#runurl}} +

+ {{runlabel}} +

+ {{/runurl}} -{{#redirecturl}} - {{#js}} - require(['core/task_indicator'], function(TaskIndicator) { - TaskIndicator.init('{{progress.idnumber}}', '{{redirecturl}}'); - }); - {{/js}} -{{/redirecturl}} + +{{#js}} + require(['core/task_indicator'], function(TaskIndicator) { + TaskIndicator.init('{{progress.idnumber}}', '{{redirecturl}}'); + }); +{{/js}}