MDL-87648 core: Support multiple tests within a check

This commit is contained in:
Andrew Nicols
2026-01-22 11:26:09 +08:00
parent 55ab5d6758
commit 436688d74b
6 changed files with 196 additions and 100 deletions
@@ -0,0 +1,11 @@
issueNumber: MDL-87648
notes:
core:
- message: >
The Checks API now supports multiple results being returned for a single
instance of `\core\check\check`.
To support this a new `get_results(): array;` method has been created
which returns an array of `\core\check\result` objects.
type: improved
+34 -32
View File
@@ -110,47 +110,49 @@ $exitcode = $exitcodes[result::OK];
foreach ($checks as $check) {
$ref = $check->get_ref();
$result = $check->get_result();
$status = $result->get_status();
$checkexitcode = $exitcodes[$status];
foreach ($check->get_results() as $result) {
$status = $result->get_status();
$checkexitcode = $exitcodes[$status];
// Summary is treated as html.
$summary = $result->get_summary();
$summary = html_to_text($summary, 60, false);
// Summary is treated as html.
$summary = $result->get_summary();
$summary = html_to_text($summary, 60, false);
if ($checkexitcode > $exitcode) {
$exitcode = $checkexitcode;
$header = $exitlabel[$status] . ': ' . $check->get_name() . " (" . $check->get_ref() . ")\n";
}
if ($checkexitcode > $exitcode) {
$exitcode = $checkexitcode;
$header = $exitlabel[$status] . ': ' . $check->get_name() . " (" . $check->get_ref() . ")\n";
}
if (empty($messages[$status])) {
$messages[$status] = $result;
}
if (empty($messages[$status])) {
$messages[$status] = $result;
}
$len = strlen(get_string('status' . $status));
$len = strlen(get_string('status' . $status));
if ($options['verbose'] ||
$status == result::WARNING ||
$status == result::CRITICAL ||
$status == result::ERROR) {
if (
$options['verbose']
|| $status == result::WARNING
|| $status == result::CRITICAL
|| $status == result::ERROR
) {
$output .= sprintf(
$format,
$OUTPUT->check_result($result),
sprintf('%s (%s)', $check->get_name(), $ref),
);
$output .= sprintf(
$format,
$OUTPUT->check_result($result),
sprintf('%s (%s)', $check->get_name(), $ref)
);
$summary = str_replace("\n", "\n" . $prefix . ' ', $summary);
$output .= sprintf($format, '', ' ' . $summary);
$output .= sprintf($format, '', ' ' . html_to_text($result->get_details(), width: 0, dolinks: false));
$summary = str_replace("\n", "\n" . $prefix . ' ', $summary);
$output .= sprintf($format, '', ' ' . $summary);
$output .= sprintf($format, '', ' ' . html_to_text($result->get_details(), width: 0, dolinks: false));
if ($options['verbose']) {
$actionlink = $check->get_action_link();
if ($actionlink) {
$output .= sprintf( $format, '', ' ' . $actionlink->url);
if ($options['verbose']) {
$actionlink = $check->get_action_link();
if ($actionlink) {
$output .= sprintf($format, '', ' ' . $actionlink->url);
}
$output .= sprintf($format, '', '');
}
$output .= sprintf( $format, '', '');
}
}
}
+8 -1
View File
@@ -128,5 +128,12 @@ abstract class check {
*/
abstract public function get_result(): result;
/**
* Return all results for this check
*
* @return result[]
*/
public function get_results(): array {
return [$this->get_result()];
}
}
+37 -12
View File
@@ -79,9 +79,11 @@ class router extends check {
}
#[\Override]
public function get_result(): result {
public function get_results(): array {
global $CFG;
$results = [];
if (empty($CFG->routerconfigured)) {
$result = new result(
result::ERROR,
@@ -91,7 +93,12 @@ class router extends check {
]),
);
return $result;
return [$result];
} else {
$results[] = new result(
result::OK,
get_string('routerconfigurationset', 'admin'),
);
}
// The router is marked as configured. Check if it actually works though.
@@ -128,9 +135,14 @@ class router extends check {
'statuscode' => $code,
'statuscodetitle' => $codetitle,
];
if ($code !== $test['expectedcode']) {
if ($code === $test['expectedcode']) {
$results[] = new result(
result::OK,
get_string('routerconfigureddetails', 'admin', $resultprops),
);
} else {
$expectedgot = get_string('routerexpectedgot', 'admin', $resultprops);
return new result(
$results[] = new result(
result::ERROR,
get_string($test['failfeedbackstr'], 'admin', $resultprops),
get_string('routernotconfigureddetailwithurl', 'admin', $resultprops) . " {$expectedgot}",
@@ -142,17 +154,30 @@ class router extends check {
}
}
return new result(
result::OK,
get_string('routerconfiguredok', 'admin'),
);
return $results;
}
#[\Override]
public function get_action_link(): ?action_link {
return new action_link(
new \core\url(get_docs_url('Configuring_the_Router')),
get_string('routerdocs', 'admin'),
public function get_result(): result {
// Aggregate the results.
$failed = array_filter(
$this->get_results(),
fn ($result) => $result->get_status() !== result::OK,
);
if (empty($failed)) {
// Nothing failed. Return the OK result.
return new result(
result::OK,
get_string('routerconfiguredok', 'admin'),
);
}
// Some form of failure. Return the generic failure result.
return new result(
result::ERROR,
get_string('routerconfiguredwithissues', 'admin'),
get_string('routerconfiguredwithissuesdetail', 'admin', ['count' => count($failed)]),
);
}
}
+27 -4
View File
@@ -16,6 +16,8 @@
namespace core\check;
use core\output\action_link;
/**
* A check object returns a result object
*
@@ -29,7 +31,6 @@ namespace core\check;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class result implements \renderable {
/**
* This is used to notify if a check does not apply.
*
@@ -99,7 +100,14 @@ class result implements \renderable {
* This may be a large amount of preformatted html text, possibly describing all the
* different states and actions to address them.
*/
protected $details = '';
protected string $details = '';
/**
* @var null|action_link An action link which is more specific than the general check link, if relvant
*
* If no specific check link is specified, then the check link will be used.
*/
protected ?action_link $actionlink = null;
/**
* Constructor
@@ -107,11 +115,18 @@ class result implements \renderable {
* @param string $status code
* @param string $summary a 1 liner summary
* @param string $details as a html chunk
* @param ?action_link $details An action link which is more specific than the general check link, if relevant
*/
public function __construct($status, $summary, $details = '') {
public function __construct(
string $status,
string $summary,
string $details = '',
?action_link $actionlink = null,
) {
$this->status = $status;
$this->summary = $summary;
$this->details = $details;
$this->actionlink = $actionlink;
}
/**
@@ -166,5 +181,13 @@ class result implements \renderable {
public function get_template_name(): string {
return 'core/check/result';
}
}
/**
* Get an action link if a more specific one was specified.
*
* @return null|action_link
*/
public function get_action_link(): ?action_link {
return $this->actionlink;
}
}
+79 -51
View File
@@ -14,26 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core\check;
use core\output\html_writer;
/**
* A table of check results
* A table of check results.
*
* @package core
* @category check
* @copyright 2020 Brendan Heywood <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\check;
defined('MOODLE_INTERNAL') || die();
/**
* A table of check results
*
* @copyright 2020 Brendan Heywood <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class table implements \renderable {
class table implements \core\output\renderable {
/**
* @var \moodle_url $url
*/
@@ -49,6 +42,13 @@ class table implements \renderable {
*/
public $detail = '';
/**
* The name of the check that was requested.
*
* @var string
*/
protected string $checkname = '';
/**
* @var array $checks shown in this table
*/
@@ -62,7 +62,6 @@ class table implements \renderable {
* @param string $detail check to focus on
*/
public function __construct($type, $url, $detail = '') {
// We may need a bit more memory and this may take a long time to process.
\raise_memory_limit(MEMORY_EXTRA);
\core_php_time_limit::raise();
@@ -70,6 +69,7 @@ class table implements \renderable {
$this->type = $type;
$this->url = $url;
$this->checks = \core\check\manager::get_checks($type);
$this->checkname = $detail;
if ($detail) {
$this->checks = array_filter($this->checks, function($check) use ($detail) {
@@ -84,63 +84,91 @@ class table implements \renderable {
/**
* Render a table of checks
*
* @param renderer $output to use
* @param \core\output\renderer $output to use
* @return string html output
*/
public function render($output) {
$html = '';
$table = new \html_table();
$table = new \core_table\output\html_table();
$table->data = [];
$table->head = [
get_string('status'),
get_string('check'),
get_string('summary'),
get_string('action'),
];
$table->colclasses = [
'rightalign status',
'leftalign check',
'leftalign summary',
'leftalign action',
];
$table->head = [get_string('status')];
$table->colclasses = ['rightalign status'];
if (empty($this->checkname)) {
$table->head[] = get_string('check');
$table->colclasses[] = 'leftalign check';
} else {
$html .= html_writer::tag('h3', $this->detail->get_name());
}
$table->head[] = get_string('summary');
$table->colclasses[] = 'leftalign summary';
$table->head[] = get_string('action');
$table->colclasses[] = 'leftalign action';
$table->id = $this->type . 'reporttable';
$table->attributes = ['class' => 'admintable ' . $this->type . 'report table generaltable'];
$fails = [];
foreach ($this->checks as $check) {
$ref = $check->get_ref();
$result = $check->get_result();
$component = $check->get_component();
$actionlink = $check->get_action_link();
$link = new \moodle_url($this->url, ['detail' => $ref]);
$row = [];
$row[] = $output->check_result($result);
$row[] = $output->action_link($link, $check->get_name());
$results = empty($this->checkname)
? [$check->get_result()]
: $check->get_results();
$row[] = $result->get_summary()
. '<br>'
. \html_writer::start_tag('small')
. $output->action_link($link, get_string('moreinfo'))
. \html_writer::end_tag('small');
if ($actionlink) {
$row[] = $output->render($actionlink);
} else {
$row[] = '';
foreach ($results as $result) {
$row = [];
if ($result->get_status() !== result::OK) {
$fails[] = $result;
}
$row[] = $output->check_result($result);
if (empty($this->checkname)) {
$row[] = $output->action_link($link, $check->get_name());
}
$row[] = $result->get_summary()
. '<br>'
. \html_writer::start_tag('small')
. $output->action_link($link, get_string('moreinfo'))
. \html_writer::end_tag('small');
$actionlink = $result->get_action_link() ?? $check->get_action_link();
if ($actionlink) {
$row[] = $output->render($actionlink);
} else {
$row[] = '';
}
$table->data[] = $row;
}
$table->data[] = $row;
}
$html = \html_writer::table($table);
$html .= \html_writer::table($table);
if ($this->detail && $result) {
$details = $result->get_details();
$details = array_filter(array_map(
fn ($result) => $result->get_details(),
$fails,
));
if (count($details) > 0) {
$html .= $output->heading(get_string('details'), 3);
if (!empty($details)) {
$html .= $output->heading(get_string('details'), 3);
$html .= $output->box($details, 'generalbox boxwidthnormal boxaligncenter');
if (count($details) === 1) {
$result = reset($fails);
$html .= $output->box($result->get_details(), 'generalbox boxwidthnormal boxaligncenter');
} else {
$html .= html_writer::start_tag('ul');
foreach ($details as $detail) {
$html .= html_writer::tag('li', $detail);
}
$html .= html_writer::end_tag('ul');
}
}
if ($this->detail) {
$html .= $output->continue_button($this->url);
}