From c0572a04232bd9e6b2ea7289e17b571162f0e2ef Mon Sep 17 00:00:00 2001 From: waleedhassan Date: Mon, 19 Aug 2024 08:29:12 +0100 Subject: [PATCH] MDL-81780 tool_task : Adhoc task improvements Added fail delay column in the adhock task table on the admin page, Added a new column for a delete action for the adhoc tasks on and Wrote delete_adhoctask.php for delete functionality of adhoc tasks. Wrote behat test for the delete functionality. Also there was a logical error in the previous code where it was checking if the task is due or not and it was just showing Never or ASAP instead of actual time so fixed that as well. --- admin/tool/task/adhoctasks.php | 5 +- admin/tool/task/delete_adhoctasks.php | 46 +++++++++++++++++++ admin/tool/task/lang/en/tool_task.php | 5 ++ admin/tool/task/renderer.php | 46 +++++++++++++++++-- admin/tool/task/run_adhoctasks.php | 17 ++++--- .../tool/task/tests/behat/delete_task.feature | 21 +++++++++ lib/classes/task/manager.php | 25 ++++++++-- 7 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 admin/tool/task/delete_adhoctasks.php create mode 100644 admin/tool/task/tests/behat/delete_task.feature diff --git a/admin/tool/task/adhoctasks.php b/admin/tool/task/adhoctasks.php index 74a98e742d5..d967148fd81 100644 --- a/admin/tool/task/adhoctasks.php +++ b/admin/tool/task/adhoctasks.php @@ -28,6 +28,7 @@ require_once($CFG->libdir.'/adminlib.php'); admin_externalpage_setup('adhoctasks'); $failedonly = optional_param('failedonly', false, PARAM_BOOL); +$dueonly = optional_param('dueonly', false, PARAM_BOOL); $classname = optional_param('classname', null, PARAM_RAW); $renderer = $PAGE->get_renderer('tool_task'); @@ -37,7 +38,7 @@ if ($classname) { $PAGE->navbar->add(get_string('adhoctasks', 'tool_task'), $pageurl); $PAGE->navbar->add(s($classname), $PAGE->url); - $tasks = core\task\manager::get_adhoc_tasks($classname, $failedonly); + $tasks = core\task\manager::get_adhoc_tasks($classname, $failedonly, dueonly: $dueonly); echo $OUTPUT->header(); @@ -45,7 +46,7 @@ if ($classname) { echo $renderer->cron_disabled(); } - echo $renderer->adhoc_tasks_class_table($classname, $tasks, ['failedonly' => $failedonly]); + echo $renderer->adhoc_tasks_class_table($classname, $tasks, ['failedonly' => $failedonly , 'dueonly' => $dueonly ]); } else { $summary = core\task\manager::get_adhoc_tasks_summary(); diff --git a/admin/tool/task/delete_adhoctasks.php b/admin/tool/task/delete_adhoctasks.php new file mode 100644 index 00000000000..af838317ec2 --- /dev/null +++ b/admin/tool/task/delete_adhoctasks.php @@ -0,0 +1,46 @@ +. + +/** + * Script deletes an adhoc task. + * + * @package tool_task + * @copyright Catalyst + * @author Waleed ul hassan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +define('NO_OUTPUT_BUFFERING', true); + +require('../../../config.php'); + +// Basic security checks. +require_admin(); +$context = context_system::instance(); + +// Get task and check the parameter is valid. +$taskid = required_param('taskid', PARAM_INT); +$task = \core\task\manager::get_adhoc_task($taskid); +if (!$task) { + throw new \moodle_exception('cannotfindinfo', 'error', $taskid); +} + +$returnurl = new moodle_url('/admin/tool/task/adhoctasks.php', + ['classname' => get_class($task)]); + +require_sesskey(); +\core\task\manager::delete_adhoc_task($taskid); +redirect($returnurl); diff --git a/admin/tool/task/lang/en/tool_task.php b/admin/tool/task/lang/en/tool_task.php index 0489f4ae2ed..808ddfde9b2 100644 --- a/admin/tool/task/lang/en/tool_task.php +++ b/admin/tool/task/lang/en/tool_task.php @@ -46,11 +46,13 @@ $string['checklongrunningtasks'] = 'Long running tasks'; $string['checklongrunningtaskcount'] = 'Long running tasks: {$a}'; $string['clearfaildelay_confirm'] = 'Are you sure you want to clear the fail delay for task \'{$a}\'? After clearing the delay, the task will run according to its normal schedule.'; $string['component'] = 'Component'; +$string['confirmdeletetaskwithid'] = 'Are you sure you want to delete the task with id: {$a->id}?'; $string['corecomponent'] = 'Core'; $string['crondisabled'] = 'Cron is disabled. No new tasks will be started. The system will not operate properly until it is enabled again.'; $string['cronok'] = 'Cron is running frequently'; $string['default'] = 'Default'; $string['defaultx'] = 'Default: {$a}'; +$string['deletetask'] = 'Delete task'; $string['disabled'] = 'Disabled'; $string['disabled_help'] = 'Disabled scheduled tasks are not executed from cron, however they can still be executed manually via the CLI tool.'; $string['edittaskschedule'] = 'Edit task schedule: {$a}'; @@ -85,6 +87,7 @@ $string['runadhoctasks'] = 'Run all \'{$a}\' tasks'; $string['runadhoctasksfailed'] = 'Run failed \'{$a}\' tasks'; $string['runnow_confirm'] = 'Are you sure you want to run this task \'{$a}\' now? The task will run on the web server and may take some time to complete.'; $string['runclassname'] = 'Run all'; +$string['runclassnamedueonly'] = 'Run due only'; $string['runclassnamefailedonly'] = 'Run all failed'; $string['runpattern'] = 'Run pattern'; $string['scheduled'] = 'Scheduled'; @@ -140,3 +143,5 @@ $string['taskschedulemonth_help'] = 'Month field for task schedule. The field us * 1,5 Every January and May'; $string['privacy:metadata'] = 'The Scheduled task configuration plugin does not store any personal data.'; $string['viewlogs'] = 'View logs for {$a}'; +$string['actions'] = 'Actions'; +$string['deleteadhoctask'] = 'Delete ad hoc task {$a->id}'; diff --git a/admin/tool/task/renderer.php b/admin/tool/task/renderer.php index 02bb26c79a7..1a14556dd81 100644 --- a/admin/tool/task/renderer.php +++ b/admin/tool/task/renderer.php @@ -96,6 +96,16 @@ class tool_task_renderer extends plugin_renderer_base { ), 'task-runnow' ); + $duecontent .= html_writer::div( + html_writer::link( + new moodle_url( + $adhocrunurl, + ['classname' => $classname, 'dueonly' => 1] + ), + get_string('runclassnamedueonly', 'tool_task') + ), + 'task-runnow' + ); } // Mark cell if has failed tasks. @@ -233,6 +243,7 @@ class tool_task_renderer extends plugin_renderer_base { */ private function generate_adhoc_tasks_simple_table(array $tasks, bool $wantruntasks = false): html_table { $adhocrunurl = '/admin/tool/task/run_adhoctasks.php'; + $adhocdeleteurl = '/admin/tool/task/delete_adhoctasks.php'; $now = time(); $failedstr = get_string('failed', 'tool_task'); @@ -243,7 +254,9 @@ class tool_task_renderer extends plugin_renderer_base { get_string('taskid', 'tool_task'), get_string('nextruntime', 'tool_task'), get_string('payload', 'tool_task'), - $failedstr + $failedstr, + get_string('faildelay', 'tool_task'), + get_string('actions','tool_task'), ]; $table->attributes['class'] = 'generaltable'; @@ -263,9 +276,9 @@ class tool_task_renderer extends plugin_renderer_base { // Mark cell if task has failed. $faildelay = $task->get_fail_delay(); - $faildelaycell = new html_table_cell($faildelay ? $failedstr : ''); + $failedcell = new html_table_cell($faildelay ? $failedstr : ''); if ($faildelay) { - $faildelaycell->attributes['class'] = 'table-danger'; + $failedcell->attributes['class'] = 'table-danger'; } // Prepares the next run time cell contents. @@ -293,11 +306,38 @@ class tool_task_renderer extends plugin_renderer_base { } } + // Create fail delay cell. + $faildelaycellcontent = $faildelay; + $faildelaycell = new html_table_cell($faildelaycellcontent); + + if ($faildelay) { + $faildelaycell->attributes['class'] = 'table-danger'; + } + + // Add delete link with modal trigger. + $deletelink = html_writer::link( + new moodle_url($adhocdeleteurl, ['taskid' => $taskid, 'sesskey' => sesskey()]), + get_string('delete'), + [ + 'class' => 'btn btn-danger', + 'role' => 'button', + 'aria-label' => get_string('deleteadhoctask', 'tool_task', $taskid), + 'data-confirmation' => 'modal', + 'data-confirmation-type' => 'delete', + 'data-confirmation-title-str' => '["deleteadhoctask", "tool_task",{"id":'.$taskid.'}]', + 'data-confirmation-content-str' => '["confirmdeletetaskwithid", "tool_task", {"id":'.$taskid.'}]', + 'data-confirmation-yes-button-str' => '["delete", "core"]', + ] + ); + $deletecell = new html_table_cell($deletelink); + $data[] = new html_table_row([ $taskidcell, new html_table_cell($nextrun), new html_table_cell($task->get_custom_data_as_string()), + $failedcell, $faildelaycell, + $deletecell, ]); } $table->data = $data; diff --git a/admin/tool/task/run_adhoctasks.php b/admin/tool/task/run_adhoctasks.php index 7912f4b1beb..292c3427525 100644 --- a/admin/tool/task/run_adhoctasks.php +++ b/admin/tool/task/run_adhoctasks.php @@ -37,6 +37,7 @@ $tasksurl = '/admin/tool/task/adhoctasks.php'; // Allow execution of single task. This requires login and has different rules. $classname = optional_param('classname', null, PARAM_RAW); $failedonly = optional_param('failedonly', false, PARAM_BOOL); +$dueonly = optional_param('dueonly', false, PARAM_BOOL); $taskid = optional_param('id', null, PARAM_INT); $confirmed = optional_param('confirm', 0, PARAM_INT); @@ -45,7 +46,7 @@ if (!\core\task\manager::is_runnable()) { throw new moodle_exception('cannotfindthepathtothecli', 'tool_task', $redirecturl->out()); } -$params = ['classname' => $classname, 'failedonly' => $failedonly, 'id' => $taskid]; +$params = ['classname' => $classname, 'failedonly' => $failedonly, 'dueonly' => $dueonly, 'id' => $taskid]; // Check input parameter id against all existing tasks. if ($taskid) { @@ -69,7 +70,7 @@ if ($taskid) { $now = time(); $tasks = array_filter( - core\task\manager::get_adhoc_tasks($classname, $failedonly, true), + core\task\manager::get_adhoc_tasks($classname, $failedonly, $dueonly, true), function ($t) use ($now) { return $t->get_fail_delay() || $t->get_next_run_time() <= $now; } @@ -130,14 +131,16 @@ if ($taskid) { \core\task\manager::run_adhoc_from_cli($taskid); echo html_writer::end_tag('pre'); } else { - $repeat = core\task\manager::get_adhoc_tasks($classname, $failedonly, true); + $repeat = core\task\manager::get_adhoc_tasks($classname, $failedonly, $dueonly, true); // Run failed first (if any). We have to run them separately anyway, // because faildelay is observed if failed flag is not true. - echo html_writer::tag('p', get_string('runningfailedtasks', 'tool_task'), ['class' => 'lead']); - echo html_writer::start_tag('pre', ['class' => 'task-output']); - \core\task\manager::run_all_adhoc_from_cli(true, $classname); - echo html_writer::end_tag('pre'); + if (!$dueonly) { + echo html_writer::tag('p', get_string('runningfailedtasks', 'tool_task'), ['class' => 'lead']); + echo html_writer::start_tag('pre', ['class' => 'task-output']); + \core\task\manager::run_all_adhoc_from_cli(true, $classname); + echo html_writer::end_tag('pre'); + } if (!$failedonly) { echo html_writer::tag('p', get_string('runningalltasks', 'tool_task'), ['class' => 'lead']); diff --git a/admin/tool/task/tests/behat/delete_task.feature b/admin/tool/task/tests/behat/delete_task.feature new file mode 100644 index 00000000000..18762129a16 --- /dev/null +++ b/admin/tool/task/tests/behat/delete_task.feature @@ -0,0 +1,21 @@ +@tool @tool_task @javascript +Feature: Delete an adhoc task + In order to manage adhoc tasks + As an admin + I need to be able to delete adhoc tasks + + Scenario: Delete an existing adhoc task + Given I log in as "admin" + And the following "tool_task > adhoc tasks" exist: + | classname | seconds | hostname | pid | + | \core\task\asynchronous_backup_task | 7201 | c69335460f7f | 1915 | + | \core\task\asynchronous_restore_task | 172800 | c69335460f7f | 1916 | + And I navigate to "Server > Tasks > Ad hoc tasks" in site administration + Then "asynchronous_backup_task" "table_row" should exist + And "asynchronous_restore_task" "table_row" should exist + And I follow "asynchronous_backup_task" + When I follow "Delete" + And I click on "Delete" "button" in the ".modal-dialog" "css_element" + Then I navigate to "Server > Tasks > Ad hoc tasks" in site administration + And "asynchronous_backup_task" "table_row" should not exist + And "asynchronous_restore_task" "table_row" should exist diff --git a/lib/classes/task/manager.php b/lib/classes/task/manager.php index 7411235394f..b2679047eac 100644 --- a/lib/classes/task/manager.php +++ b/lib/classes/task/manager.php @@ -507,11 +507,17 @@ class manager { * This function load the adhoc tasks for a given classname. * * @param string $classname - * @param bool $failedonly - * @param bool $skiprunning do not return tasks that are in the running state + * @param bool $failedonly Return only failed tasks + * @param bool $skiprunning Do not return tasks that are in the running state + * @param bool $dueonly Return only tasks that are due to run (nextruntime < now) * @return array */ - public static function get_adhoc_tasks(string $classname, bool $failedonly = false, bool $skiprunning = false): array { + public static function get_adhoc_tasks( + string $classname, + bool $failedonly = false, + bool $skiprunning = false, + bool $dueonly = false + ): array { global $DB; $conds[] = 'classname = ?'; @@ -519,6 +525,8 @@ class manager { if ($failedonly) { $conds[] = 'faildelay > 0'; + } else if ($dueonly) { + $conds[] = 'faildelay = 0'; } if ($skiprunning) { $conds[] = 'timestarted IS NULL'; @@ -950,6 +958,17 @@ class manager { return null; } + /** + * This function will delete an adhoc task by id. The task will be removed + * from the database. + * + * @param int $taskid + */ + public static function delete_adhoc_task(int $taskid): void { + global $DB; + $DB->delete_records('task_adhoc', ['id' => $taskid]); + } + /** * This function will set locks on the task. *