An error occurred while loading the file. Please try again.
view_submission_utils.php 19.62 KiB
<?php
namespace assignsubmission_dta;
// 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/>.
/**
 * utility class for DTA submission plugin result display
 * @package assignsubmission_dta
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
use assignsubmission_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;
class view_submission_utils {
    /**
     * Broadly used in logic, parametrized for easier change.
    const COMPONENT_NAME = "assignsubmission_dta";
    /**
     * generates a short summary html
     * @param int $assignmentid assignment
     * @param int $submissionid submission to create a report for
     * @return string html
    public static function generate_summary_html(
        int $assignmentid,
        int $submissionid
    ): string {
        // Fetch data.
        $summary = db_utils::get_Result_Summary_From_Database($assignmentid, $submissionid);
        $html = "";
        // Calculate success rate, if no unknown result states or compilation errors.
        $successrate = "?";
        if ($summary->unknown_count() == 0 && $summary->compilation_error_count() == 0) {
            $successrate = round(($summary->successful_count() / $summary->result_count()) * 100, 2 );
        // Generate html.
        $html .= $summary->successful_count() . "/";
        $html .= ($summary->compilation_error_count() == 0 && $summary->unknown_count() == 0)
            ? $summary->result_Count() . " (" . $successrate . "%)"
                : "?";
        $html .= get_string("tests_successful", self::COMPONENT_NAME) . "<br />";
        if ($summary->compilation_error_count() > 0) {
            $html .= $summary->compilation_error_count() . get_string("compilation_errors", self::COMPONENT_NAME) . "<br />";
        if ($summary->unknown_count() > 0) {
$html .= $summary->unknown_count() . get_string("unknown_state", self::COMPONENT_NAME) . "<br />"; } $showncompetencies = explode(";", $summary->successfultestcompetencies); $overallcompetencies = explode(";", $summary->overalltestcompetencies); $tmp = ""; for ($index = 0, $size = count($showncompetencies); $index < $size; $index++) { $shown = $showncompetencies[$index]; $comp = $overallcompetencies[$index]; // If the competency was actually assessed by the assignment and tests, add a summary entry. if ($shown != "0") { $tmp .= get_string("comp" . $index, self::COMPONENT_NAME) . " " . 100 * floatval($shown) / floatval($comp) . "% " . "<br />"; } } $html .= get_string("success_competencies", self::COMPONENT_NAME) . "<br />" . $tmp . "<br />"; return \html_writer::div($html, "dtaSubmissionSummary"); } /** * generates detailed view html * * @param int $assignmentid assignment * @param int $submissionid submission to create a report for */ public static function generate_detail_html( int $assignmentid, int $submissionid ): string { // Fetch data. $summary = db_utils::get_result_summary_from_database($assignmentid, $submissionid); $recommendations = db_utils::get_recommendations_from_database($assignmentid, $submissionid); $html = ""; // Define a few css classes and prepare html attribute arrays to beautify the output. $tableheaderrowattributes = ["class" => "dtaTableHeaderRow"]; $tablerowattributes = ["class" => "dtaTableRow"]; $resultrowattributes = $tablerowattributes; $unknownattributes = 'dtaResultUnknown'; $successattributes = 'dtaResultSuccess'; $failureattributes = 'dtaResultFailure'; $compilationerrorattributes = 'dtaResultCompilationError'; $attributes = ["class" => "dtaTableData"]; // *************** // SUMMARY TABLE // *************** $tmp = ""; $tmp .= \html_writer::tag("th", get_string("summary", self::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 = ""; // Total items. $tmp = ""; $tmp .= \html_writer::tag("td", get_string("total_items", self::COMPONENT_NAME), $attributes); $tmp .= \html_writer::tag("td", $summary->result_count(), $attributes); $resultrowattributes = $tablerowattributes; $resultrowattributes['class'] .= " " . $unknownattributes; $body .= \html_writer::tag("tr", $tmp, $resultrowattributes); // Tests successful.
$tmp = ""; $tmp .= \html_writer::tag("td", get_string("tests_successful", self::COMPONENT_NAME), $attributes); $tmp .= \html_writer::tag("td", $summary->successful_count(), $attributes); $resultrowattributes = $tablerowattributes; $successrate = "?"; if ($summary->unknown_count() > 0 || $summary->compilation_error_count() > 0) { $resultrowattributes['class'] .= " " . $unknownattributes; } else { $successrate = round(($summary->successful_count() / $summary->result_count()) * 100, 2); if ($successrate < 50) { $resultrowattributes['class'] .= " " . $compilationerrorattributes; } else if ($successrate < 75) { $resultrowattributes['class'] .= " " . $failureattributes; } else { $resultrowattributes['class'] .= " " . $successattributes; } } $body .= \html_writer::tag("tr", $tmp, $resultrowattributes); // Failures. $tmp = ""; $tmp .= \html_writer::tag("td", get_string("failures", self::COMPONENT_NAME), $attributes); $tmp .= \html_writer::tag("td", $summary->failed_count(), $attributes); $resultrowattributes = $tablerowattributes; if ($summary->failed_count() > 0) { $resultrowattributes['class'] .= " " . $failureattributes; } else { $resultrowattributes['class'] .= " " . $successattributes; } $body .= \html_writer::tag("tr", $tmp, $resultrowattributes); // Compilation errors. $tmp = ""; $tmp .= \html_writer::tag("td", get_string("compilation_errors", self::COMPONENT_NAME), $attributes); $tmp .= \html_writer::tag("td", $summary->compilation_error_count(), $attributes); $resultrowattributes = $tablerowattributes; if ($summary->compilation_error_count() > 0) { $resultrowattributes['class'] .= " " . $compilationerrorattributes; } else { $resultrowattributes['class'] .= " " . $successattributes; } $body .= \html_writer::tag("tr", $tmp, $resultrowattributes); // Unknown state. $tmp = ""; $tmp .= \html_writer::tag("td", get_string("unknown_state", self::COMPONENT_NAME), $attributes); $tmp .= \html_writer::tag("td", $summary->unknown_count(), $attributes); $resultrowattributes = $tablerowattributes; if ($summary->unknown_count() > 0) { $resultrowattributes['class'] .= " " . $unknownattributes; } else { $resultrowattributes['class'] .= " " . $successattributes; } $body .= \html_writer::tag("tr", $tmp, $resultrowattributes); // Success rate. $tmp = ""; $tmp .= \html_writer::tag("td", \html_writer::tag("b", get_string("success_rate", self::COMPONENT_NAME)), $attributes); $tmp .= \html_writer::tag( "td", \html_writer::tag("b", $summary->successful_count() . "/" . (($summary->compilation_error_count() == 0 && $summary->unknown_count() == 0) ? $summary->result_count() . " (" . $successrate . "%)" : "?")), $attributes ); $resultrowattributes = $tablerowattributes; if ($summary->unknown_count() > 0 || $summary->compilation_error_count() > 0) { $resultrowattributes['class'] .= " " . $unknownattributes;
} else { if ($successrate < 50) { $resultrowattributes['class'] .= " " . $compilationerrorattributes; } else if ($successrate < 75) { $resultrowattributes['class'] .= " " . $failureattributes; } else { $resultrowattributes['class'] .= " " . $successattributes; } } $body .= \html_writer::tag("tr", $tmp, $resultrowattributes); $body = \html_writer::tag("tbody", $body); $table = \html_writer::tag("table", $header . $body, ["class" => "dtaTable"]); $html .= $table; // Add empty div for spacing after summary. $html .= \html_writer::empty_tag("div", ["class" => "dtaSpacer"]); // *************** // RECOMMENDATIONS TABLE // *************** if (!empty($recommendations)) { // Sorting logic. $allowed_sort_fields = ['topic', 'exercise_name', 'difficulty', 'score']; $allowed_sort_dirs = ['asc', 'desc']; $sortby = isset($_POST['sortby']) ? $_POST['sortby'] : 'score'; $sortdir = isset($_POST['sortdir']) ? $_POST['sortdir'] : 'asc'; if (!in_array($sortby, $allowed_sort_fields)) { $sortby = 'score'; } if (!in_array($sortdir, $allowed_sort_dirs)) { $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::COMPONENT_NAME)); $generate_sortable_header = function($column_name, $display_name) use ($sortby, $sortdir) { $new_sortdir = ($sortby == $column_name && $sortdir == 'asc') ? 'desc' : 'asc'; $class = 'dtaTableHeader'; if ($sortby == $column_name) { $class .= ' sorted ' . $sortdir; } // Sort button. $button = \html_writer::empty_tag('input', [ 'type' => 'submit',
'name' => 'sortbutton', 'value' => ($new_sortdir == 'asc' ? '↑' : '↓'), 'class' => 'sort-button' ]); // Hidden inputs. $hidden_inputs = \html_writer::empty_tag('input', [ 'type' => 'hidden', 'name' => 'sortby', 'value' => $column_name ]); $hidden_inputs .= \html_writer::empty_tag('input', [ 'type' => 'hidden', 'name' => 'sortdir', 'value' => $new_sortdir ]); $form = \html_writer::start_tag('form', ['method' => 'post', 'style' => 'display:inline']); $form .= $hidden_inputs; $form .= $display_name . ' ' . $button; $form .= \html_writer::end_tag('form'); return \html_writer::tag("th", $form, ["class" => $class]); }; // Table header for recommendations. $tableheader = ""; $tableheader .= $generate_sortable_header('topic', get_string("topic", self::COMPONENT_NAME)); $tableheader .= $generate_sortable_header('exercise_name', get_string("exercise_name", self::COMPONENT_NAME)); $tableheader .= \html_writer::tag("th", get_string("url", self::COMPONENT_NAME), ["class" => "dtaTableHeader"]); $tableheader .= $generate_sortable_header('difficulty', get_string("difficulty", self::COMPONENT_NAME)); $tableheader .= $generate_sortable_header('score', get_string("score", self::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) { $tablerow = ""; $tablerow .= \html_writer::tag("td", $recommendation->topic, $attributes); $tablerow .= \html_writer::tag("td", $recommendation->exercise_name, $attributes); $tablerow .= \html_writer::tag("td", \html_writer::link($recommendation->url, $recommendation->url), $attributes); $tablerow .= \html_writer::tag("td", $recommendation->difficulty, $attributes); $tablerow .= \html_writer::tag("td", $recommendation->score, $attributes); $tablebody .= \html_writer::tag("tr", $tablerow, $tablerowattributes); } $tablebody = \html_writer::tag("tbody", $tablebody); $html .= \html_writer::tag("table", $tableheader . $tablebody, ["class" => "dtaTable"]); // Add empty div for spacing after recommendations. $html .= \html_writer::empty_tag("div", ["class" => "dtaSpacer"]); } // *************** // COMPETENCY ASSESSMENT TABLE // *************** $body = ""; $tmp = ""; $tmp .= \html_writer::tag("th", get_string("competencies", self::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 by the assignment and tests, add a row in the table. if ($comp != "0") { $resultrowattributes = $tablerowattributes; $tmp = ""; $tmp .= \html_writer::tag("td", get_string("comp" . $index, self::COMPONENT_NAME), $resultrowattributes); $tmp .= \html_writer::tag("td", (100 * floatval($shown) / floatval($comp)) . "% (" . $shown . " / " . $comp . ")", $resultrowattributes); $tmp .= \html_writer::tag("td", get_string("comp_expl" . $index, self::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 between competency and details table. $html .= \html_writer::empty_tag("div", ["class" => "dtaSpacer"]); // *************** // DETAILS TABLE // *************** $tmp = ""; $tmp .= \html_writer::tag("th", get_string("details", self::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 first if not null. 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::COMPONENT_NAME), $attributes); $tmp .= \html_writer::tag("td", $r->packagename, $attributes); $tmp .= \html_writer::tag("td", get_string("unit_name", self::COMPONENT_NAME), $attributes); $tmp .= \html_writer::tag("td", $r->classname, $attributes); $tmp .= \html_writer::tag("td", get_string("test_name", self::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::COMPONENT_NAME), $attributes); $tmp .= \html_writer::tag("td", dta_result::get_statename($r->state), $attributes); $body .= \html_writer::tag("tr", $tmp, $resultrowattributes); // If state is different than successful, show additional info. if ($r->state != 1) { $tmp = ""; $tmp .= \html_writer::tag("td", get_string("failure_type", self::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::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::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::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::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::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)) { $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; } }