diff --git a/dta/classes/backend.php b/dta/classes/backend.php new file mode 100644 index 0000000000000000000000000000000000000000..7ca08f49e180ca0a2bd41bfe06c9bbf2bc15c01f --- /dev/null +++ b/dta/classes/backend.php @@ -0,0 +1,144 @@ +<?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 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright Gero Lueckemeyer and student project teams + */ + +/** + * backend webservice contact utility class + * + * @package assignsubmission_dta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright Gero Lueckemeyer and student project teams + */ +class DtaBackendUtils { + + /** + * Returns the base url of the backend webservice as configured in the administration settings. + * @return string backend host base url + */ + private static function getbackendbaseurl(): string { + $backendaddress = get_config(assign_submission_dta::COMPONENT_NAME, "backendHost"); + + if (empty($backendaddress)) { + \core\notification::error(get_string("backendHost_not_set", assign_submission_dta::COMPONENT_NAME)); + } + + return $backendaddress; + } + + /** + * Sends the configuration textfile uploaded by prof to the backend. + * + * @param stdClass $assignment assignment this test-config belongs to + * @param stdClass $file uploaded test-config + * @return bool true if no error occurred + */ + public static function sendtestconfigtobackend($assignment, $file): bool { + $backendaddress = self::getbackendbaseurl(); + if (empty($backendaddress)) { + return true; + } + + // Set endpoint for test upload. + $url = $backendaddress . "/v1/unittest"; + + // Prepare params. + $params = [ + "unitTestFile" => $file, + "assignmentId" => $assignment->get_instance()->id, + ]; + + // If request returned null, return false to indicate failure. + if (is_null(self::post($url, $params))) { + return false; + } else { + return true; + } + } + + /** + * Sends submission config or archive to backend to be tested. + * + * @param stdClass $assignment assignment for the submission + * @param int $submissionid submissionid of the current file + * @param stdClass $file submission config file or archive with submission + * @return string json string with testresults or null on error + */ + public static function sendsubmissiontobackend($assignment, $submissionid, $file): ?string { + $backendaddress = self::getbackendbaseurl(); + if (empty($backendaddress)) { + return true; + } + + // Set endpoint for test upload. + $url = $backendaddress . "/v1/task/" . $submissionid; + + // Prepare params. + $params = [ + "taskFile" => $file, + "assignmentId" => $assignment->get_instance()->id, + ]; + + return self::post($url, $params); + } + + /** + * Posts the given params to the given url and returns the response as a string. + * @param string $url full url to request to + * @param array $params parameters for http-request + * + * @return string received body on success or null on error + */ + private static function post($url, $params): ?string { + if (!isset($url) || !isset($params)) { + return false; + } + + $options = ["CURLOPT_RETURNTRANSFER" => true]; + + $curl = new curl(); + $response = $curl->post($url, $params, $options); + + // Check state of request, if response code is a 2xx return the answer. + $info = $curl->get_info(); + if ($info["http_code"] >= 200 && $info["http_code"] < 300) { + return $response; + } + + // Something went wrong, return null and give an error message. + debugging(assign_submission_dta::COMPONENT_NAME . ": Post file to server was not successful: http_code=" . + $info["http_code"]); + + if ($info['http_code'] >= 400 && $info['http_code'] < 500) { + \core\notification::error(get_string("http_client_error_msg", assign_submission_dta::COMPONENT_NAME)); + return null; + } else if ($info['http_code'] >= 500 && $info['http_code'] < 600) { + \core\notification::error(get_string("http_server_error_msg", assign_submission_dta::COMPONENT_NAME)); + return null; + } else { + \core\notification::error(get_string("http_unknown_error_msg", assign_submission_dta::COMPONENT_NAME) . + $info["http_code"] . $response); + return null; + } + } + +} diff --git a/dta/classes/database.php b/dta/classes/database.php new file mode 100644 index 0000000000000000000000000000000000000000..6f5c82a94f5545911657831ebbadbe1ca5f0ece0 --- /dev/null +++ b/dta/classes/database.php @@ -0,0 +1,165 @@ +<?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/>. + +/** + * persistence layer utility class + * + * @package assignsubmission_dta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright Gero Lueckemeyer and student project teams + */ +class DbUtils { + + /** + * Summary database table name. + */ + private const TABLE_SUMMARY = "assignsubmission_dta_summary"; + /** + * Result database table name. + */ + private const TABLE_RESULT = "assignsubmission_dta_result"; + + /** + * gets summary with all corresponding result entries + * + * @param int $assignmentid assignment id to search for + * @param int $submissionid submission id to search for + * @return DttResultSummary representing given submission + */ + public static function getresultsummaryfromdatabase( + int $assignmentid, + int $submissionid + ): DtaResultSummary { + global $DB; + + // Fetch data from database. + $summaryrecord = $DB->get_record(self::TABLE_SUMMARY, [ + "assignment_id" => $assignmentid, + "submission_id" => $submissionid, + ]); + + $resultsarray = $DB->get_records(self::TABLE_RESULT, [ + "assignment_id" => $assignmentid, + "submission_id" => $submissionid, + ]); + + // Create a summary instance. + $summary = new DtaResultSummary(); + $summary->timestamp = $summaryrecord->timestamp; + $summary->globalstacktrace = $summaryrecord->global_stacktrace; + $summary->successfultestcompetencies = $summaryrecord->successful_competencies; + $summary->overalltestcompetencies = $summaryrecord->tested_competencies; + $summary->results = []; + + // Create result instances and add to array of summary instance. + foreach ($resultsarray as $rr) { + $result = new DtaResult(); + $result->packagename = $rr->package_name; + $result->classname = $rr->class_name; + $result->name = $rr->name; + $result->state = $rr->state; + $result->failuretype = $rr->failure_type; + $result->failurereason = $rr->failure_reason; + $result->stacktrace = $rr->stacktrace; + $result->columnnumber = $rr->column_number; + $result->linenumber = $rr->line_number; + $result->position = $rr->position; + + $summary->results[] = $result; + } + + return $summary; + } + + /** + * save given result summary and single results to database + * under given assignment and submission id + * + * @param int $assignmentid assigment this is submission is linked to + * @param int $submissionid submission of this result + * @param DtaResultSummary $summary instance to persist + */ + public static function storeresultsummarytodatabase( + int $assignmentid, + int $submissionid, + DtaResultSummary $summary + ): void { + global $DB; + + // Prepare new database entries. + $summaryrecord = new stdClass(); + $summaryrecord->assignment_id = $assignmentid; + $summaryrecord->submission_id = $submissionid; + $summaryrecord->timestamp = $summary->timestamp; + $summaryrecord->global_stacktrace = $summary->globalstacktrace; + $summaryrecord->successful_competencies = $summary->successfultestcompetencies; + $summaryrecord->tested_competencies = $summary->overalltestcompetencies; + + // Prepare results to persist to array. + $resultrecords = []; + foreach ($summary->results as $r) { + $record = new stdClass(); + $record->assignment_id = $assignmentid; + $record->submission_id = $submissionid; + $record->package_name = $r->packagename; + $record->class_name = $r->classname; + $record->name = $r->name; + $record->state = $r->state; + $record->failure_type = $r->failuretype; + $record->failure_reason = $r->failurereason; + $record->stacktrace = $r->stacktrace; + $record->column_number = $r->columnnumber; + $record->line_number = $r->linenumber; + $record->position = $r->position; + $resultrecords[] = $record; + } + + // If results already exist, delete old values beforehand. + $submission = $DB->get_record(self::TABLE_SUMMARY, [ + 'assignment_id' => $assignmentid, + 'submission_id' => $submissionid, + ]); + + if ($submission) { + $DB->delete_records(self::TABLE_RESULT, [ + 'assignment_id' => $assignmentid, + 'submission_id' => $submissionid, + ]); + + $DB->delete_records(self::TABLE_SUMMARY, [ + 'assignment_id' => $assignmentid, + 'submission_id' => $submissionid, + ]); + } + + // Create summary and single result entries. + $DB->insert_record(self::TABLE_SUMMARY, $summaryrecord); + foreach ($resultrecords as $rr) { + $DB->insert_record(self::TABLE_RESULT, $rr); + } + } + + /** + * cleans up database if plugin is uninstalled + */ + public static function uninstallplugincleaup(): void { + global $DB; + + $DB->delete_records(self::TABLE_RESULT, null); + $DB->delete_records(self::TABLE_SUMMARY, null); + } + +} diff --git a/dta/classes/view.php b/dta/classes/view.php new file mode 100644 index 0000000000000000000000000000000000000000..b93d3e57ead9c84bee90de33277b149217d65668 --- /dev/null +++ b/dta/classes/view.php @@ -0,0 +1,426 @@ +<?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/>. + +/** + * utility class for DTA submission plugin result display + * + * @package assignsubmission_dta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright Gero Lueckemeyer and student project teams + */ +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 generatesummaryhtml( + int $assignmentid, + int $submissionid + ): string { + + // Fetch data. + $summary = DbUtils::getResultSummaryFromDatabase($assignmentid, $submissionid); + $html = ""; + + // Calculate success rate, if no unknown result states or compilation errors. + $successrate = "?"; + if ($summary->unknownCount() == 0 && $summary->compilationErrorCount() == 0) { + $successrate = round(($summary->successfulCount() / $summary->resultCount()) * 100, 2 ); + } + + // Generate html. + $html .= $summary->successfulCount() . "/"; + $html .= ($summary->compilationErrorCount() == 0 && $summary->unknownCount() == 0) + ? $summary->resultCount() . " (" . $successrate . "%)" + : "?"; + $html .= get_string("tests_successful", self::COMPONENT_NAME) . "<br />"; + + if ($summary->compilationErrorCount() > 0) { + $html .= $summary->compilationErrorCount() . get_string("compilation_errors", self::COMPONENT_NAME) . "<br />"; + } + + if ($summary->unknownCount() > 0) { + $html .= $summary->unknownCount() . 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 generatedetailhtml( + int $assignmentid, + int $submissionid + ): string { + + // Fetch data. + $summary = DbUtils::getResultSummaryFromDatabase($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'; + + // 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 = ""; + $tmp = ""; + $attributes = ["class" => "dtaTableData"]; + $tmp .= html_writer::tag( + "td", + get_string("total_items", self::COMPONENT_NAME), + $attributes); + + $tmp .= html_writer::tag( + "td", + $summary->resultCount(), + $attributes); + + $resultrowattributes = $tablerowattributes; + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $unknownattributes; + + $body .= html_writer::tag("tr", $tmp, $resultrowattributes); + + $tmp = ""; + $tmp .= html_writer::tag("td", get_string("tests_successful", self::COMPONENT_NAME), $attributes); + $tmp .= html_writer::tag( "td", $summary->successfulCount(), $attributes); + + $resultrowattributes = $tablerowattributes; + $successrate = "?"; + + if ($summary->unknownCount() > 0 || $summary->compilationErrorCount() > 0) { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $unknownattributes; + } else { + $successrate = round(($summary->successfulCount() / $summary->resultCount()) * 100, 2 ); + if ($successrate < 50) { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $compilationerrorattributes; + } else if ($successrate < 75) { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $failureattributes; + } else { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $successattributes; + } + } + $body .= html_writer::tag("tr", $tmp, $resultrowattributes); + + $tmp = ""; + $tmp .= html_writer::tag("td", get_string("failures", self::COMPONENT_NAME), $attributes); + $tmp .= html_writer::tag("td", $summary->failedCount(), $attributes); + + $resultrowattributes = $tablerowattributes; + if ($summary->failedCount() > 0) { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $failureattributes; + } else { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $successattributes; + } + $body .= html_writer::tag("tr", $tmp, $resultrowattributes); + + $tmp = ""; + $tmp .= html_writer::tag("td", get_string("compilation_errors", self::COMPONENT_NAME), $attributes); + $tmp .= html_writer::tag("td", $summary->compilationErrorCount(), $attributes); + + $resultrowattributes = $tablerowattributes; + if ($summary->compilationErrorCount() > 0) { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $compilationerrorattributes; + } else { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $successattributes; + } + $body .= html_writer::tag("tr", $tmp, $resultrowattributes); + + $tmp = ""; + $tmp .= html_writer::tag("td", get_string("unknown_state", self::COMPONENT_NAME), $attributes); + $tmp .= html_writer::tag("td", $summary->unknownCount(), $attributes); + + $resultrowattributes = $tablerowattributes; + if ($summary->unknownCount() > 0) { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $unknownattributes; + } else { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $successattributes; + } + $body .= html_writer::tag("tr", $tmp, $resultrowattributes); + + $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->successfulCount() + . "/" . (($summary->compilationErrorCount() == 0 && $summary->unknownCount() == 0) ? $summary->resultCount() + . " (" . $successrate . "%)" + : "?")), + $attributes); + + $resultrowattributes = $tablerowattributes; + if ($summary->unknownCount() > 0 || $summary->compilationErrorCount() > 0) { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $unknownattributes; + } else { + if ($successrate < 50) { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $compilationerrorattributes; + } else if ($successrate < 75) { + $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $failureattributes; + } else { + $resultrowattributes['class'] = $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 between summary and compentency table. + $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") { + // New copy of base attributes array. + $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; + } + + // New copy of base attributes array. + $resultrowattributes = $tablerowattributes; + + // Check which css class to add for the colored left-border according to resuls state. + if ($r->state == 0) { + $resultrowattributes['class'] = $resultrowattributes['class'] . ' dtaResultUnknown'; + } else if ($r->state == 1) { + $resultrowattributes['class'] = $resultrowattributes['class'] . ' dtaResultSuccess'; + } else if ($r->state == 2) { + $resultrowattributes['class'] = $resultrowattributes['class'] . ' dtaResultFailure'; + } else if ($r->state == 3) { + $resultrowattributes['class'] = $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", + DtaResult::getStateName($r->state), + $attributes); + $body .= html_writer::tag("tr", $tmp, $resultrowattributes); + + // If state is something different than successful, show additional rows. + 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); + + // Only show line, column and position if they have useful values. + 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); + } + + // Set spacerrow value if null for next round separation. + 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; + } + +} diff --git a/dta/models/DtaResult.php b/dta/models/DtaResult.php new file mode 100644 index 0000000000000000000000000000000000000000..bd7d4b0842ac26d8a94145426fcb096c0014b0ce --- /dev/null +++ b/dta/models/DtaResult.php @@ -0,0 +1,247 @@ +<?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/>. + +/** + * entity classes for DTA submission plugin result summary and test results + * + * @package assignsubmission_dta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright Gero Lueckemeyer and student project teams + */ +defined('MOODLE_INTERNAL') || die(); + +/** + * entity class for DTA submission plugin result + * + * @package assignsubmission_dta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright Gero Lueckemeyer and student project teams + */ +class DtaResult { + + /** + * Broadly used in logic, parametrized for easier change. + */ + const COMPONENT_NAME = "assignsubmission_dta"; + + /** + * @var $packagename Package name of the test. + */ + public $packagename; + + /** + * @var $classname Unit name of the test. + */ + public $classname; + + /** + * @var $name Name of the test. + */ + public $name; + + /** + * @var $state State is defined like below + * + * 0 UNKNOWN + * 1 SUCCESS + * 2 FAILURE + * 3 COMPILATIONERROR + */ + public $state; + + /** + * @var $failuretype Type of test failure if applicable, "" otherwise. + */ + public $failuretype; + + /** + * @var $failurereason Reason of test failure if applicable, "" otherwise. + */ + public $failurereason; + + /** + * @var $stacktrace Stack trace of test failure if applicable, "" otherwise. + */ + public $stacktrace; + + /** + * @var $columnnumber Column number of compile failure if applicable, "" otherwise. + */ + public $columnnumber; + /** + * @var $linenumber Line number of compile failure if applicable, "" otherwise. + */ + public $linenumber; + /** + * @var $position Position of compile failure if applicable, "" otherwise. + */ + public $position; + + /** + * Returns the name of a state with the given number of display. + * @param int $state number of the state + * @return string name of state as defined + */ + public static function getstatename(int $state): string { + if ($state == 1) { + return get_string("tests_successful", self::COMPONENT_NAME); + } else if ($state == 2) { + return get_string("failures", self::COMPONENT_NAME); + } else if ($state == 3) { + return get_string("compilation_errors", self::COMPONENT_NAME); + } else { + return get_string("unknown_state", self::COMPONENT_NAME); + } + } +} + +/** + * entity class for DTA submission plugin result + * + * @package assignsubmission_dta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright Gero Lueckemeyer and student project teams + */ +class DtaResultSummary { + + /** + * @var $timestamp Result timestamp for chronological ordering and deletion of previous results. + */ + public $timestamp; + + /** + * @var $globalstacktrace Global stack trace if applicable, "" otherwise. + */ + public $globalstacktrace; + + /** + * @var $successfultestcompetencies Successfully tested competencies according to tests and weights, "" otherwise. + */ + public $successfultestcompetencies; + /** + * @var overalltestcompetencies Overall tested competencies according to tests and weights, "" otherwise. + */ + public $overalltestcompetencies; + /** + * @var results List of detail results. + */ + public $results; + + /** + * Decodes the JSON result summary returned by the backend service call into the plugin PHP data structure. + * @param string $jsonstring jsonString containing DtaResultSummary + * @return DtaResultSummary the result summary + */ + public static function decodejson(string $jsonstring): DtaResultSummary { + $response = json_decode($jsonstring); + + $summary = new DtaResultSummary(); + $summary->timestamp = $response->timestamp; + $summary->globalstacktrace = $response->globalstacktrace; + + $summary->successfultestcompetencies = $response->successfulTestCompetencyProfile; + $summary->overalltestcompetencies = $response->overallTestCompetencyProfile; + + $summary->results = self::decodejsonresultarray($response->results); + + return $summary; + } + + /** + * Decodes the array of JSON detail results returned by the backend service call into the plugin PHP data structure. + * @param array $jsonarray decoded json array of results array + * @return array of DtaResult + */ + private static function decodejsonresultarray($jsonarray): array { + $ret = []; + foreach ($jsonarray as $entry) { + $value = new DtaResult(); + $value->packagename = $entry->packageName; + $value->classname = $entry->className; + $value->name = $entry->name; + + $value->state = $entry->state; + + $value->failuretype = $entry->failureType; + $value->failurereason = $entry->failureReason; + $value->stacktrace = $entry->stacktrace; + + $value->columnnumber = $entry->columnNumber; + $value->linenumber = $entry->lineNumber; + $value->position = $entry->position; + + $ret[] = $value; + } + return $ret; + } + + + /** + * Returns the number of detail results attached to the summary. + * @return int count of occurences + */ + public function resultcount(): int { + return count($this->results); + } + + /** + * Returns the number of detail results with the given state attached to the summary. + * @param int $state state ordinal number + * @return int count of occurences provided state has + */ + public function stateoccurencecount(int $state): int { + $num = 0; + foreach ($this->results as $r) { + if ($r->state == $state) { + $num++; + } + } + return $num; + } + + /** + * Returns the number of detail results with compilation errors attached to the summary. + * @return int count of occurences + */ + public function compilationerrorcount(): int { + return $this->stateoccurencecount(3); + } + + /** + * Returns the number of detail results with test failures attached to the summary. + * @return int count of occurences + */ + public function failedcount(): int { + return $this->stateoccurencecount(2); + } + + /** + * Returns the number of detail results with successful tests attached to the summary. + * @return int count of occurences + */ + public function successfulcount(): int { + return $this->stateoccurencecount(1); + } + + /** + * Returns the number of detail results with an unknown result - mostly due to compile errors - attached to the summary. + * @return int count of occurences + */ + public function unknowncount(): int { + return $this->stateoccurencecount(0); + } + +} diff --git a/test/privacy/apicompliance.php b/test/privacy/apicompliance.php index 7a5761e87fa297e4ff842b50a9403794834dc1e8..121ad630786845a1575b9cb058284d6dd6279770 100644 --- a/test/privacy/apicompliance.php +++ b/test/privacy/apicompliance.php @@ -62,7 +62,7 @@ foreach ($list->good as $component) { foreach ($collection->get_collection() as $item) { if ($item instanceof \core_privacy\local\metadata\types\user_preference) { $userprefdescribed = true; - echo " ".$item->assignsubmission_dta_get_name()." : ".get_string($item->get_summary(), $component) . "\n"; + echo " ".$item->get_name()." : ".get_string($item->get_summary(), $component) . "\n"; } } if (!$userprefdescribed) {