Skip to content

Commit 667488d

Browse files
Implement Process error table
1 parent 970932c commit 667488d

13 files changed

+477
-7
lines changed

amd/build/tablebulkactions.min.js

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

amd/build/tablebulkactions.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.

amd/src/tablebulkactions.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
* Javascript controller for checkboxed table.
18+
* @module tool_lifecycle/tablebulkactions
19+
* @copyright 2021 Justus Dieckmann WWU
20+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
21+
*/
22+
23+
/**
24+
* Helper function to redirect via POST
25+
* @param {String} url redirect to
26+
* @param {Array} data redirect with data
27+
*/
28+
function redirectPost(url, data) {
29+
const form = document.createElement('form');
30+
document.body.appendChild(form);
31+
form.method = 'post';
32+
form.action = url;
33+
for (const pair of data) {
34+
const input = document.createElement('input');
35+
input.type = 'hidden';
36+
input.name = pair.k;
37+
input.value = pair.v;
38+
form.appendChild(input);
39+
}
40+
form.submit();
41+
}
42+
43+
/**
44+
* Init function
45+
*/
46+
export function init() {
47+
const checkboxes = document.querySelectorAll('input[name="procerror-select"]');
48+
49+
const action = document.querySelectorAll('*[data-lifecycle-action]');
50+
action.forEach((a) => {
51+
a.onclick = (e) => {
52+
e.preventDefault();
53+
let data = [
54+
{k: 'action', v: a.getAttribute('data-lifecycle-action')},
55+
{k: 'sesskey', v: M.cfg.sesskey}
56+
];
57+
if (a.getAttribute('data-lifecycle-forall') === '1') {
58+
data.push({k: 'all', v: '1'});
59+
redirectPost(window.location, data);
60+
} else {
61+
checkboxes.forEach((c) => {
62+
if (c.checked) {
63+
data.push({k: 'id[]', v: c.value});
64+
}
65+
});
66+
redirectPost(window.location, data);
67+
}
68+
};
69+
});
70+
}

classes/local/manager/process_manager.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
namespace tool_lifecycle\local\manager;
2525

2626
use core\event\course_deleted;
27+
use Exception;
2728
use tool_lifecycle\local\entity\process;
2829
use tool_lifecycle\event\process_proceeded;
2930
use tool_lifecycle\event\process_rollback;
@@ -245,4 +246,66 @@ public static function abort_process($process) {
245246
$steplib->abort_course($process);
246247
self::remove_process($process);
247248
}
249+
250+
/**
251+
* Moves a process into the procerror table.
252+
*
253+
* @param process $process The process
254+
* @param Exception $e The exception
255+
* @return void
256+
*/
257+
public static function insert_process_error(process $process, Exception $e) {
258+
global $DB;
259+
260+
$procerror = (object) clone $process;
261+
$procerror->errormessage = get_class($e) . ': ' . $e->getMessage();
262+
$procerror->errortrace = $e->getTraceAsString();
263+
$m = '';
264+
foreach($e->getTrace() as $v) {
265+
$m .= $v['file'] . ':' . $v['line'] . '::';
266+
}
267+
$procerror->errorhash = md5($m);
268+
$procerror->waiting = intval($procerror->waiting);
269+
270+
$DB->insert_record_raw('tool_lifecycle_proc_error', $procerror, false, false, true);
271+
$DB->delete_records('tool_lifecycle_process', ['id' => $process->id]);
272+
}
273+
274+
/**
275+
* Proceed process from procerror back into the process board.
276+
* @param int $processid the processid
277+
* @return void
278+
*/
279+
public static function proceed_process_after_error(int $processid) {
280+
global $DB;
281+
$process = $DB->get_record('tool_lifecycle_proc_error', ['id' => $processid]);
282+
// Unset process error entries.
283+
unset($process->errormessage);
284+
unset($process->errortrace);
285+
unset($process->errorhash);
286+
287+
$DB->insert_record_raw('tool_lifecycle_process', $process, false, false, true);
288+
$DB->delete_records('tool_lifecycle_proc_error', ['id' => $process->id]);
289+
}
290+
291+
/**
292+
* Rolls back a process from procerror table
293+
* @param int $processid the processid
294+
* @return void
295+
*/
296+
public static function rollback_process_after_error(int $processid) {
297+
global $DB;
298+
299+
$process = $DB->get_record('tool_lifecycle_proc_error', ['id' => $processid]);
300+
// Unset process error entries.
301+
unset($process->errormessage);
302+
unset($process->errortrace);
303+
unset($process->errorhash);
304+
305+
$DB->insert_record_raw('tool_lifecycle_process', $process, false, false, true);
306+
$DB->delete_records('tool_lifecycle_proc_error', ['id' => $process->id]);
307+
308+
delayed_courses_manager::set_course_delayed_for_workflow($process->courseid, true, $process->workflowid);
309+
self::rollback_process($process);
310+
}
248311
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
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+
/**
18+
* Table listing all process errors
19+
*
20+
* @package tool_lifecycle
21+
* @copyright 2021 Justus Dieckmann WWU
22+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23+
*/
24+
namespace tool_lifecycle\local\table;
25+
26+
defined('MOODLE_INTERNAL') || die;
27+
28+
require_once($CFG->libdir . '/tablelib.php');
29+
30+
/**
31+
* Table listing all process errors
32+
*
33+
* @package tool_lifecycle
34+
* @copyright 2021 Justus Dieckmann WWU
35+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36+
*/
37+
class process_errors_table extends \table_sql {
38+
39+
private $strings;
40+
41+
/**
42+
* Constructor for delayed_courses_table.
43+
*
44+
* @throws \coding_exception
45+
*/
46+
public function __construct() {
47+
global $OUTPUT;
48+
49+
parent::__construct('tool_lifecycle-process_errors');
50+
51+
$this->strings = [
52+
'proceed' => get_string('proceed', 'tool_lifecycle'),
53+
'rollback' => get_string('rollback', 'tool_lifecycle')
54+
];
55+
56+
$fields = 'c.fullname as course, w.title as workflow, s.instancename as step, pe.*';
57+
58+
$from = '{tool_lifecycle_proc_error} pe ' .
59+
'JOIN {tool_lifecycle_workflow} w ON pe.workflowid = w.id ' .
60+
'JOIN {tool_lifecycle_step} s ON pe.workflowid = s.workflowid AND pe.stepindex = s.sortindex ' .
61+
'LEFT JOIN {course} c ON pe.courseid = c.id ';
62+
63+
$this->set_sql($fields, $from, 'TRUE');
64+
$this->column_nosort = ['select', 'tools'];
65+
$this->define_columns(['select', 'workflow', 'step', 'courseid', 'course', 'error', 'tools']);
66+
$this->define_headers([
67+
$OUTPUT->render(new \core\output\checkbox_toggleall('procerrors-table', true, [
68+
'id' => 'select-all-procerrors',
69+
'name' => 'select-all-procerrors',
70+
'label' => get_string('selectall'),
71+
'labelclasses' => 'sr-only',
72+
'classes' => 'm-1',
73+
'checked' => false,
74+
])),
75+
get_string('workflow', 'tool_lifecycle'),
76+
get_string('step', 'tool_lifecycle'),
77+
get_string('courseid', 'tool_lifecycle'),
78+
get_string('course'),
79+
get_string('error'),
80+
get_string('tools', 'tool_lifecycle')
81+
]);
82+
}
83+
84+
/**
85+
* Render error column.
86+
*
87+
* @param object $row Row data.
88+
* @return string error cell
89+
* @throws \coding_exception
90+
* @throws \moodle_exception
91+
*/
92+
public function col_error($row) {
93+
return "<details><summary>" .
94+
nl2br(htmlentities($row->errormessage)) .
95+
"</summary>" .
96+
nl2br(htmlentities($row->errortrace)) .
97+
"</details>";
98+
}
99+
100+
/**
101+
* Render tools column.
102+
*
103+
* @param object $row Row data.
104+
* @return string pluginname of the subplugin
105+
* @throws \coding_exception
106+
* @throws \moodle_exception
107+
*/
108+
public function col_tools($row) {
109+
global $OUTPUT;
110+
111+
$actionmenu = new \action_menu();
112+
$actionmenu->add_primary_action(
113+
new \action_menu_link_primary(
114+
new \moodle_url('', ['action' => 'proceed', 'id[]' => $row->id, 'sesskey' => sesskey()]),
115+
new \pix_icon('e/tick', $this->strings['proceed']),
116+
$this->strings['proceed']
117+
)
118+
);
119+
$actionmenu->add_primary_action(
120+
new \action_menu_link_primary(
121+
new \moodle_url('', ['action' => 'rollback', 'id[]' => $row->id, 'sesskey' => sesskey()]),
122+
new \pix_icon('e/undo', $this->strings['rollback']),
123+
$this->strings['rollback']
124+
)
125+
);
126+
return $OUTPUT->render($actionmenu);
127+
}
128+
129+
/**
130+
* Generate the select column.
131+
*
132+
* @param \stdClass $data
133+
* @return string
134+
*/
135+
public function col_select($data) {
136+
global $OUTPUT;
137+
138+
$checkbox = new \core\output\checkbox_toggleall('procerrors-table', false, [
139+
'classes' => 'usercheckbox m-1',
140+
'id' => 'procerror' . $data->id,
141+
'name' => 'procerror-select',
142+
'value' => $data->id,
143+
'checked' => false,
144+
'label' => get_string('selectitem', 'moodle', $data->id),
145+
'labelclasses' => 'accesshide',
146+
]);
147+
148+
return $OUTPUT->render($checkbox);
149+
}
150+
151+
/**
152+
* Override the table show_hide_link to not show for select column.
153+
*
154+
* @param string $column the column name, index into various names.
155+
* @param int $index numerical index of the column.
156+
* @return string HTML fragment.
157+
*/
158+
protected function show_hide_link($column, $index) {
159+
if ($index > 0) {
160+
return parent::show_hide_link($column, $index);
161+
}
162+
return '';
163+
}
164+
165+
/**
166+
* Hook that can be overridden in child classes to wrap a table in a form
167+
* for example. Called only when there is data to display and not
168+
* downloading.
169+
*/
170+
public function wrap_html_finish() {
171+
global $OUTPUT;
172+
parent::wrap_html_finish();
173+
echo "<br>";
174+
175+
$actionmenu = new \action_menu();
176+
$actionmenu->add_secondary_action(
177+
new \action_menu_link_secondary(
178+
new \moodle_url(''),
179+
new \pix_icon('e/tick', $this->strings['proceed']),
180+
$this->strings['proceed'],
181+
['data-lifecycle-action' => 'proceed']
182+
)
183+
);
184+
185+
$actionmenu->add_secondary_action(
186+
new \action_menu_link_secondary(
187+
new \moodle_url(''),
188+
new \pix_icon('e/undo', $this->strings['rollback']),
189+
$this->strings['rollback'],
190+
['data-lifecycle-action' => 'rollback']
191+
)
192+
);
193+
194+
$actionmenu->set_menu_trigger(get_string('forselected', 'tool_lifecycle'));
195+
echo $OUTPUT->render_action_menu($actionmenu);
196+
}
197+
}

classes/processor.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,15 @@ public function process_courses() {
119119

120120
$step = step_manager::get_step_instance_by_workflow_index($process->workflowid, $process->stepindex);
121121
$lib = lib_manager::get_step_lib($step->subpluginname);
122-
if ($process->waiting) {
123-
$result = $lib->process_waiting_course($process->id, $step->id, $course);
124-
} else {
125-
$result = $lib->process_course($process->id, $step->id, $course);
122+
try {
123+
if ($process->waiting) {
124+
$result = $lib->process_waiting_course($process->id, $step->id, $course);
125+
} else {
126+
$result = $lib->process_course($process->id, $step->id, $course);
127+
}
128+
} catch (\Exception $e) {
129+
process_manager::insert_process_error($process, $e);
130+
break;
126131
}
127132
if ($result == step_response::waiting()) {
128133
process_manager::set_process_waiting($process);
@@ -220,7 +225,9 @@ public function get_course_recordset($triggers, $exclude) {
220225
$sql = 'SELECT {course}.* from {course} '.
221226
'left join {tool_lifecycle_process} '.
222227
'ON {course}.id = {tool_lifecycle_process}.courseid '.
223-
'WHERE {tool_lifecycle_process}.courseid is null AND ' . $where;
228+
'LEFT JOIN {tool_lifecycle_proc_error} pe ON {course}.id = pe.courseid ' .
229+
'WHERE {tool_lifecycle_process}.courseid is null AND ' .
230+
'pe.courseid IS NULL AND '. $where;
224231
return $DB->get_recordset_sql($sql, $whereparams);
225232
}
226233

0 commit comments

Comments
 (0)