An error occurred while loading the file. Please try again.
dta_view_submission_utils.php 25.79 KiB
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
/**
 * This file contains the backend webservice contact functionality for the DTA plugin.
 * @package   assignsubmission_dta
 * @copyright 2023 Your Name
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
namespace assignsubmission_dta;
use assignsubmission_dta\dta_db_utils;
use assignsubmission_dta\dta_backend_utils;
use assignsubmission_dta\models\dta_result;
use assignsubmission_dta\models\dta_result_summary;
use assignsubmission_dta\models\dta_recommendation;
/**
 * Utility class for DTA submission plugin result display.
 * @package   assignsubmission_dta
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
class dta_view_submission_utils {
    /**
     * Broadly used in logic, parametrized for easier change.
    public const ASSIGNSUBMISSION_DTA_COMPONENT_NAME = 'assignsubmission_dta';
    /**
     * Generates a short summary HTML (like your old plugin).
     * @param int $assignmentid The assignment ID.
     * @param int $submissionid The submission ID to create a report for.
     * @return string The HTML summary.
    public static function assignsubmission_dta_generate_summary_html(
        int $assignmentid,
        int $submissionid
    ): string {
        // 1) Retrieve the summary data from the DB (adjust your DB-utils class as needed).
        $summary = dta_db_utils::assignsubmission_dta_get_result_summary_from_database($assignmentid, $submissionid);
        // 2) Prepare an HTML buffer.
        $html = '';
        // 3) Extract counts from your new method names.
        $unknowncount = $summary->assignsubmission_dta_unknown_count();
        $compilecount = $summary->assignsubmission_dta_compilation_error_count();
        $successcount = $summary->assignsubmission_dta_successful_count();
        $failcount    = $summary->assignsubmission_dta_failed_count();
        $totalcount   = $summary->assignsubmission_dta_result_count();
        // 4) Compute success rate if no unknown/compile errors and total>0.
$successrate = '?'; if ($unknowncount === 0 && $compilecount === 0 && $totalcount > 0) { $successrate = round(($successcount / $totalcount) * 100, 2); } // 5) "X/Y (Z%) tests successful" line: // If either compile errors or unknown exist -> show "?", else X/Y (rate%). $html .= $successcount . '/'; if ($compilecount === 0 && $unknowncount === 0) { $html .= ($totalcount > 0) ? ($totalcount . ' (' . $successrate . '%)') : ('0 (' . $successrate . ')'); } else { $html .= '?'; } $html .= get_string('tests_successful', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) . "<br />"; // 6) If there are compilation errors, show them. if ($compilecount > 0) { $html .= $compilecount . get_string('compilation_errors', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) . "<br />"; } // 7) If there are unknown results, show them. if ($unknowncount > 0) { $html .= $unknowncount . get_string('unknown_state', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) . "<br />"; } // If there are failed tests, show them. if ($failcount > 0) { $html .= $failcount . get_string('tests_failed', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) . "<br />"; } // 8) Competencies (like your old snippet). $showncompetencies = explode(';', $summary->successfultestcompetencies); $overallcompetencies = explode(';', $summary->overalltestcompetencies); $tmp = ''; $size = count($showncompetencies); for ($i = 0; $i < $size; $i++) { $shown = $showncompetencies[$i]; $comp = $overallcompetencies[$i]; // If the competency was actually used (non-zero?), show a row. if ($shown !== '0') { $shownval = (float) $shown; $compval = (float) $comp; // Guard division by zero. $pct = 0; if ($compval > 0) { $pct = 100.0 * $shownval / $compval; } // "compX XX%<br />" $tmp .= get_string('comp' . $i, self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) . ' ' . round($pct, 2) . '%<br />'; } } $html .= get_string('success_competencies', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) . "<br />" . $tmp . "<br />"; // 9) Wrap it in a DIV for styling, and return. return \html_writer::div($html, 'dtaSubmissionSummary');
} /** * Generates detailed view HTML. * * @param int $assignmentid The assignment ID. * @param int $submissionid The submission to create a report for. * @return string HTML detail view. */ public static function assignsubmission_dta_generate_detail_html( int $assignmentid, int $submissionid ): string { // Fetch data. $summary = dta_db_utils::assignsubmission_dta_get_result_summary_from_database( $assignmentid, $submissionid ); $recommendations = dta_db_utils::assignsubmission_dta_get_recommendations_from_database( $assignmentid, $submissionid ); $html = ''; // Summary table. $tableheaderrowattributes = ['class' => 'dtaTableHeaderRow']; $tablerowattributes = ['class' => 'dtaTableRow']; $resultrowattributes = $tablerowattributes; $unknownattributes = 'dtaResultUnknown'; $successattributes = 'dtaResultSuccess'; $failureattributes = 'dtaResultFailure'; $compilationerrorattributes = 'dtaResultCompilationError'; $attributes = ['class' => 'dtaTableData']; // Build the summary table header. $tmp = \html_writer::tag( 'th', get_string('summary', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), ['class' => 'dtaTableHeader'] ); $tmp .= \html_writer::empty_tag('th', ['class' => 'dtaTableHeader']); $header = \html_writer::tag('tr', $tmp, $tableheaderrowattributes); $header = \html_writer::tag('thead', $header); $body = ''; // Pull the counters from the summary object. $resultcount = $summary->assignsubmission_dta_result_count(); $successfulcount = $summary->assignsubmission_dta_successful_count(); $failedcount = $summary->assignsubmission_dta_failed_count(); $compilationcount = $summary->assignsubmission_dta_compilation_error_count(); $unknowncount = $summary->assignsubmission_dta_unknown_count(); // Total items. $tmp = \html_writer::tag( 'td', get_string('total_items', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $resultcount, $attributes); $resultrowattributes = $tablerowattributes; // Original code colors this row as unknown by default. $resultrowattributes['class'] .= ' ' . $unknownattributes; $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); // Tests successful. $tmp = \html_writer::tag( 'td', get_string('tests_successful', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME),
$attributes ); $tmp .= \html_writer::tag('td', $successfulcount, $attributes); $resultrowattributes = $tablerowattributes; // Compute success rate if no unknown or compilation errors, and resultcount > 0. $successrate = '?'; if ($unknowncount == 0 && $compilationcount == 0 && $resultcount > 0) { $successrate = round(($successfulcount / $resultcount) * 100, 2); if ($successrate < 50) { $resultrowattributes['class'] .= ' ' . $compilationerrorattributes; } else if ($successrate < 75) { $resultrowattributes['class'] .= ' ' . $failureattributes; } else { $resultrowattributes['class'] .= ' ' . $successattributes; } } else { // If unknown or compilation errors => highlight as unknown. $resultrowattributes['class'] .= ' ' . $unknownattributes; } $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); // Failures. $tmp = \html_writer::tag( 'td', get_string('failures', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $failedcount, $attributes); $resultrowattributes = $tablerowattributes; if ($failedcount > 0) { $resultrowattributes['class'] .= ' ' . $failureattributes; } else { $resultrowattributes['class'] .= ' ' . $successattributes; } $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); // Compilation errors. $tmp = \html_writer::tag( 'td', get_string('compilation_errors', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $compilationcount, $attributes); $resultrowattributes = $tablerowattributes; if ($compilationcount > 0) { $resultrowattributes['class'] .= ' ' . $compilationerrorattributes; } else { $resultrowattributes['class'] .= ' ' . $successattributes; } $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); // Unknown state. $tmp = \html_writer::tag( 'td', get_string('unknown_state', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $unknowncount, $attributes); $resultrowattributes = $tablerowattributes; if ($unknowncount > 0) { $resultrowattributes['class'] .= ' ' . $unknownattributes; } else { $resultrowattributes['class'] .= ' ' . $successattributes; } $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); // Success rate row. $tmp = \html_writer::tag( 'td',
\html_writer::tag('b', get_string('success_rate', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME)), $attributes ); // If no compilation errors or unknown => show successrate, else "?". $suffix = ($compilationcount == 0 && $unknowncount == 0 && $resultcount > 0) ? ($resultcount . ' (' . $successrate . '%)') : '?'; $tmp .= \html_writer::tag( 'td', \html_writer::tag('b', $successfulcount . '/' . $suffix), $attributes ); $resultrowattributes = $tablerowattributes; if ($compilationcount == 0 && $unknowncount == 0 && $resultcount > 0) { if ($successrate !== '?' && $successrate < 50) { $resultrowattributes['class'] .= ' ' . $compilationerrorattributes; } else if ($successrate !== '?' && $successrate < 75) { $resultrowattributes['class'] .= ' ' . $failureattributes; } else { $resultrowattributes['class'] .= ' ' . $successattributes; } } else { $resultrowattributes['class'] .= ' ' . $unknownattributes; } $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); // Finalize the summary table. $body = \html_writer::tag('tbody', $body); $table = \html_writer::tag('table', $header . $body, ['class' => 'dtaTable']); $html .= $table; // Spacing after the summary table. $html .= \html_writer::empty_tag('div', ['class' => 'dtaSpacer']); // Recommendations table. if (!empty($recommendations)) { $allowedsortfields = ['topic', 'exercise_name', 'difficulty', 'score']; $allowedsortdirs = ['asc', 'desc']; // Make sure only one space before ?? $sortby = $_POST['sortby'] ?? 'score'; $sortdir = $_POST['sortdir'] ?? 'asc'; if (!in_array($sortby, $allowedsortfields)) { $sortby = 'score'; } if (!in_array($sortdir, $allowedsortdirs)) { $sortdir = 'asc'; } usort($recommendations, function ($a, $b) use ($sortby, $sortdir) { $valuea = $a->{$sortby}; $valueb = $b->{$sortby}; if (is_numeric($valuea) && is_numeric($valueb)) { $comparison = $valuea - $valueb; } else { $comparison = strnatcasecmp($valuea, $valueb); } if ($comparison === 0) { return 0; } if ($sortdir === 'asc') { return ($comparison < 0) ? -1 : 1; } else { return ($comparison < 0) ? 1 : -1; } });
$html .= \html_writer::tag( 'h3', get_string('recommendations', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) ); $generatesortableheader = function ($columnname, $displayname) use ($sortby, $sortdir) { $newsortdir = ($sortby === $columnname && $sortdir === 'asc') ? 'desc' : 'asc'; $class = 'dtaTableHeader'; if ($sortby === $columnname) { $class .= ' sorted ' . $sortdir; } // Sort button. $button = \html_writer::empty_tag('input', [ 'type' => 'submit', 'name' => 'sortbutton', 'value' => ($newsortdir === 'asc' ? '↑' : '↓'), 'class' => 'sort-button', ]); // Hidden inputs. $hiddeninputs = \html_writer::empty_tag('input', [ 'type' => 'hidden', 'name' => 'sortby', 'value' => $columnname, ]); $hiddeninputs .= \html_writer::empty_tag('input', [ 'type' => 'hidden', 'name' => 'sortdir', 'value' => $newsortdir, ]); $form = \html_writer::start_tag('form', [ 'method' => 'post', 'style' => 'display:inline', ]); $form .= $hiddeninputs; $form .= $displayname . ' ' . $button; $form .= \html_writer::end_tag('form'); return \html_writer::tag('th', $form, ['class' => $class]); }; // Build the recommendations table header. $tableheader = ''; $tableheader .= $generatesortableheader( 'topic', get_string('topic', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) ); $tableheader .= $generatesortableheader( 'exercise_name', get_string('exercise_name', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) ); $tableheader .= \html_writer::tag( 'th', get_string('url', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), ['class' => 'dtaTableHeader'] ); $tableheader .= $generatesortableheader( 'difficulty', get_string('difficulty', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) ); $tableheader .= $generatesortableheader( 'score', get_string('score', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME) ); $tableheader = \html_writer::tag('tr', $tableheader, ['class' => 'dtaTableHeaderRow']); $tableheader = \html_writer::tag('thead', $tableheader);
// Table body for recommendations. $tablebody = ''; foreach ($recommendations as $recommendation) { $row = ''; $row .= \html_writer::tag('td', $recommendation->topic, $attributes); $row .= \html_writer::tag('td', $recommendation->exercise_name, $attributes); $row .= \html_writer::tag( 'td', \html_writer::link($recommendation->url, $recommendation->url), $attributes ); $row .= \html_writer::tag('td', $recommendation->difficulty, $attributes); $row .= \html_writer::tag('td', $recommendation->score, $attributes); $tablebody .= \html_writer::tag('tr', $row, $tablerowattributes); } $tablebody = \html_writer::tag('tbody', $tablebody); $html .= \html_writer::tag('table', $tableheader . $tablebody, ['class' => 'dtaTable']); // Spacing after recommendations. $html .= \html_writer::empty_tag('div', ['class' => 'dtaSpacer']); } // Competency assessment table. $body = ''; $tmp = \html_writer::tag( 'th', get_string('competencies', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), ['class' => 'dtaTableHeader'] ); $tmp .= \html_writer::empty_tag('th', ['class' => 'dtaTableHeader']); $header = \html_writer::tag('tr', $tmp, $tableheaderrowattributes); $header = \html_writer::tag('thead', $header); $showncompetencies = explode(';', $summary->successfultestcompetencies); $overallcompetencies = explode(';', $summary->overalltestcompetencies); for ($index = 0, $size = count($overallcompetencies); $index < $size; $index++) { $comp = $overallcompetencies[$index]; $shown = $showncompetencies[$index]; // If the competency was actually assessed, add a row. if ($comp !== '0') { $compval = (float) $comp; $shownval = (float) $shown; // Guard division by zero. $pct = 0; if ($compval > 0) { $pct = (100.0 * $shownval / $compval); } $resultrowattributes = $tablerowattributes; $tmp = ''; $tmp .= \html_writer::tag( 'td', get_string('comp' . $index, self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $resultrowattributes ); $tmp .= \html_writer::tag( 'td', round($pct, 2) . '% (' . $shown . ' / ' . $comp . ')', $resultrowattributes ); $tmp .= \html_writer::tag( 'td', get_string('comp_expl' . $index, self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $resultrowattributes );
$body .= \html_writer::tag('tr', $tmp, $resultrowattributes); } } $body = \html_writer::tag('tbody', $body); $html .= \html_writer::tag('table', $header . $body, ['class' => 'dtaTable']); // Add empty div for spacing. $html .= \html_writer::empty_tag('div', ['class' => 'dtaSpacer']); // Details table. $tmp = ''; $tmp .= \html_writer::tag( 'th', get_string('details', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), ['class' => 'dtaTableHeader'] ); $tmp .= \html_writer::empty_tag('th', ['class' => 'dtaTableHeader']); $header = \html_writer::tag('tr', $tmp, $tableheaderrowattributes); $header = \html_writer::tag('thead', $header); $body = ''; $spacerrow = null; foreach ($summary->results as $r) { // Add spacer row before each new entry (after the first). if (!is_null($spacerrow)) { $body .= $spacerrow; } $resultrowattributes = $tablerowattributes; // Set CSS class for colored left-border according to results state. if ($r->state === 0) { $resultrowattributes['class'] .= ' dtaResultUnknown'; } else if ($r->state === 1) { $resultrowattributes['class'] .= ' dtaResultSuccess'; } else if ($r->state === 2) { $resultrowattributes['class'] .= ' dtaResultFailure'; } else if ($r->state === 3) { $resultrowattributes['class'] .= ' dtaResultCompilationError'; } $tmp = ''; $tmp .= \html_writer::tag( 'td', get_string('package_name', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $r->packagename, $attributes); $tmp .= \html_writer::tag( 'td', get_string('unit_name', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $r->classname, $attributes); $tmp .= \html_writer::tag( 'td', get_string('test_name', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $r->name, $attributes); $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); $tmp = ''; $tmp .= \html_writer::tag( 'td', get_string('status', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag( 'td',
dta_result::assignsubmission_dta_get_statename($r->state), $attributes ); $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); // If state != 1 (not successful), show additional info. if ($r->state !== 1) { $tmp = ''; $tmp .= \html_writer::tag( 'td', get_string('failure_type', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $r->failureType, $attributes); $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); $tmp = ''; $tmp .= \html_writer::tag( 'td', get_string('failure_reason', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $r->failureReason, $attributes); $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); if (!is_null($r->lineNumber) && $r->lineNumber > 0) { $tmp = ''; $tmp .= \html_writer::tag( 'td', get_string('line_no', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $r->lineNumber, $attributes); $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); } if (!is_null($r->columnNumber) && $r->columnNumber > 0) { $tmp = ''; $tmp .= \html_writer::tag( 'td', get_string('col_no', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $r->columnNumber, $attributes); $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); } if (!is_null($r->position) && $r->position > 0) { $tmp = ''; $tmp .= \html_writer::tag( 'td', get_string('pos', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag('td', $r->position, $attributes); $body .= \html_writer::tag('tr', $tmp, $resultrowattributes); } $tmp = ''; $tmp .= \html_writer::tag( 'td', get_string('stacktrace', self::ASSIGNSUBMISSION_DTA_COMPONENT_NAME), $attributes ); $tmp .= \html_writer::tag( 'td', \html_writer::tag('details', $r->stacktrace, ['class' => 'dtaStacktraceDetails']), $attributes ); $body .= \html_writer::tag('tr', $tmp, $resultrowattributes);
} if (is_null($spacerrow)) { // Reuse this spacer row between subsequent items. $spacerrow = \html_writer::empty_tag('tr', ['class' => 'dtaTableSpacer']); } } $html .= \html_writer::tag('table', $header . $body, ['class' => 'dtaTable']); // Wrap generated HTML into final div. $html = \html_writer::div($html, 'dtaSubmissionDetails'); return $html; } }