diff --git a/dta.zip b/dta.zip index 26b39cf40a3686ca39a9f8b767a05ad14afe7802..80696317da532464fcf04be1712d79690ebb3986 100644 Binary files a/dta.zip and b/dta.zip differ diff --git a/dta/classes/database.php b/dta/classes/database.php index a444dee285e5a2b17047a1dd0d8e74a613b638a2..a384b9607a71c7612100cda97e86eb96dcc3d7c3 100644 --- a/dta/classes/database.php +++ b/dta/classes/database.php @@ -32,6 +32,26 @@ class DbUtils { */ private const TABLE_RESULT = "assignsubmission_dta_result"; + private const TABLE_RECOMMENDATIONS = "assignsubmission_dta_recommendations"; + + /** + * Gets the recommendations for a given submission. + * + * @param int $submissionid ID of the submission + * @return array list of recommendations + */ + public static function get_recommendations_from_database(int $assignmentid,int $submissionid ): array { + global $DB; + + // Query the database to get all recommendations for the given submission id. + $records = $DB->get_records(self::TABLE_RECOMMENDATIONS, [ + 'assignment_id' => $assignmentid, + 'submission_id' => $submissionid, + ]); + + return $records; + } + /** * gets summary with all corresponding result entries * @@ -83,6 +103,52 @@ class DbUtils { return $summary; } + public static function storeRecommendationstoDatabase( + int $assignmentid, + int $submissionid, + array $recommendations + ): void { + global $DB; + error_log(print_r($recommendations,true)); + + // Prepare recommendations to persist to array. + $recommendationrecords = []; + foreach ($recommendations as $recommendation) { + $record = new stdClass(); + $record->assignment_id = $assignmentid; + $record->submission_id = $submissionid; + $record->topic = $recommendation['topic']; + $record->url = $recommendation['url']; + $record->exercise_name = $recommendation['exercise_name']; + $record->difficulty = $recommendation['difficulty']; + $record->score = $recommendation['score']; + $recommendationrecords[] = $record; + } + + error_log("Das sind die Recommendationrecords."); + error_log(print_r($recommendationrecords,true)); + + // If recommendations already exist, delete old values beforehand. + $existingrecords = $DB->get_record('assignsubmission_dta_recommendations', [ + 'assignment_id' => $assignmentid, + 'submission_id' => $submissionid, + ]); + + if ($existingrecords) { + $DB->delete_records('assignsubmission_dta_recommendations', [ + 'assignment_id' => $assignmentid, + 'submission_id' => $submissionid, + ]); + } + + // Create new recommendation entries. + foreach ($recommendationrecords as $rec) { + error_log("Insert record"); + error_log(print_r($rec,true)); + $DB->insert_record('assignsubmission_dta_recommendations', $rec); + } + } + /** * save given result summary and single results to database @@ -107,8 +173,7 @@ class DbUtils { $summaryrecord->global_stacktrace = $summary->globalstacktrace; $summaryrecord->successful_competencies = $summary->successfultestcompetencies; $summaryrecord->tested_competencies = $summary->overalltestcompetencies; - $summaryrecord->recommendations = $summary->recommendations; - + // Prepare results to persist to array. $resultrecords = []; foreach ($summary->results as $r) { @@ -161,6 +226,8 @@ class DbUtils { $DB->delete_records(self::TABLE_RESULT, null); $DB->delete_records(self::TABLE_SUMMARY, null); + $DB->delete_records(self::TABLE_RECOMMENDATIONS, null); + } } diff --git a/dta/classes/privacy/provider.php b/dta/classes/privacy/provider.php index ca261213609af6f73285b2cad7edfbad1e8e28e1..7eb66e4137ecf7f00eaae72ac14935078fbc2bc6 100644 --- a/dta/classes/privacy/provider.php +++ b/dta/classes/privacy/provider.php @@ -57,7 +57,6 @@ class provider implements \core_privacy\local\metadata\provider, 'global_stacktrace' => 'privacy:metadata:assignsubmission_dta_summary:global_stacktrace', 'successful_competencies' => 'privacy:metadata:assignsubmission_dta_summary:successful_competencies', 'tested_competencies' => 'privacy:metadata:assignsubmission_dta_summary:tested_competencies', - 'recommendations' => 'privacy:metadata:assignsubmission_dta_summary:recommendations', ], 'privacy:metadata:assignsubmission_dta_summary' diff --git a/dta/classes/view.php b/dta/classes/view.php index b93d3e57ead9c84bee90de33277b149217d65668..eb1d8a94d56114b64ddada34998d90f7b12b4f3b 100644 --- a/dta/classes/view.php +++ b/dta/classes/view.php @@ -19,7 +19,6 @@ * * @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 { @@ -85,342 +84,318 @@ class view_submission_utils { } /** - * 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); + * Generiert die detaillierte HTML-Ansicht, einschließlich Zusammenfassung, Kompetenzen, Details und Empfehlungen. + * + * @param int $assignmentid Assignment-ID + * @param int $submissionid Submission-ID, für die der Bericht erstellt wird + * @return string HTML-Code + */ +public static function generatedetailhtml( + int $assignmentid, + int $submissionid +): string { + + // HTML-Inhalt initialisieren + $html = ""; + + // Daten abrufen + $summary = DbUtils::getResultSummaryFromDatabase($assignmentid, $submissionid); + + // CSS-Klassen und HTML-Attributarrays definieren + $tableheaderrowattributes = ["class" => "dtaTableHeaderRow"]; + $tablerowattributes = ["class" => "dtaTableRow"]; + $attributes = ["class" => "dtaTableData"]; + $unknownattributes = 'dtaResultUnknown'; + $successattributes = 'dtaResultSuccess'; + $failureattributes = 'dtaResultFailure'; + $compilationerrorattributes = 'dtaResultCompilationError'; + + // **Zusammenfassungstabelle erstellen** + // Kopfzeile + $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); + + // Tabellenkörper + $body = ""; + + // Gesamtanzahl + $tmp = ""; + $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'] .= " " . $unknownattributes; + + $body .= html_writer::tag("tr", $tmp, $resultrowattributes); + + // Erfolgreiche Tests + $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'] .= " " . $unknownattributes; + } else { + $successrate = round(($summary->successfulCount() / $summary->resultCount()) * 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); + + // Fehlgeschlagene Tests + $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'] .= " " . $failureattributes; + } else { + $resultrowattributes['class'] .= " " . $successattributes; + } + $body .= html_writer::tag("tr", $tmp, $resultrowattributes); + + // Kompilierungsfehler + $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'] .= " " . $compilationerrorattributes; + } else { + $resultrowattributes['class'] .= " " . $successattributes; + } + $body .= html_writer::tag("tr", $tmp, $resultrowattributes); + + // Unbekannter Status + $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'] .= " " . $unknownattributes; + } else { + $resultrowattributes['class'] .= " " . $successattributes; + } + $body .= html_writer::tag("tr", $tmp, $resultrowattributes); + + // Erfolgsrate + $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'] .= " " . $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 = ""; - $tmp = ""; - $attributes = ["class" => "dtaTableData"]; - $tmp .= html_writer::tag( - "td", - get_string("total_items", self::COMPONENT_NAME), - $attributes); + // Tabelle zusammenstellen + $body = html_writer::tag("tbody", $body); + $table = html_writer::tag("table", $header . $body, ["class" => "dtaTable"]); - $tmp .= html_writer::tag( - "td", - $summary->resultCount(), - $attributes); + $html .= $table; - $resultrowattributes = $tablerowattributes; - $resultrowattributes['class'] = $resultrowattributes['class'] . " " . $unknownattributes; + // **Abstand zwischen Tabellen** + $html .= html_writer::empty_tag("div", ["class" => "dtaSpacer"]); - $body .= html_writer::tag("tr", $tmp, $resultrowattributes); + // **Kompetenzbewertungstabelle erstellen** + $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); - $tmp = ""; - $tmp .= html_writer::tag("td", get_string("tests_successful", self::COMPONENT_NAME), $attributes); - $tmp .= html_writer::tag( "td", $summary->successfulCount(), $attributes); + $showncompetencies = explode(";", $summary->successfultestcompetencies); + $overallcompetencies = explode(";", $summary->overalltestcompetencies); - $resultrowattributes = $tablerowattributes; - $successrate = "?"; + for ($index = 0, $size = count($overallcompetencies); $index < $size; $index++) { + $comp = $overallcompetencies[$index]; + $shown = $showncompetencies[$index]; + // Kompetenz wird nur hinzugefügt, wenn sie bewertet wurde + if ($comp != "0") { + $resultrowattributes = $tablerowattributes; + $tmp = ""; + $tmp .= html_writer::tag("td", get_string("comp" . $index, self::COMPONENT_NAME), $attributes); + $tmp .= html_writer::tag("td", 100 * floatval($shown) / floatval($comp) . "% " . + "(" . $shown . " / " . $comp . ")", $attributes); + $tmp .= html_writer::tag("td", get_string("comp_expl" . $index, self::COMPONENT_NAME), $attributes); - 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); } - $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("tbody", $body); + $html .= html_writer::tag("table", $header . $body, ["class" => "dtaTable"]); + + // **Abstand zwischen Tabellen** + $html .= html_writer::empty_tag("div", ["class" => "dtaSpacer"]); + + // **Detailtabelle erstellen** + $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) { + // Abstand zwischen den Ergebnissen + if (!is_null($spacerrow)) { + $body .= $spacerrow; } - $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; + // CSS-Klasse basierend auf dem Status des Ergebnisses hinzufügen + 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'; } - $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; - } - } + $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); - $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 .= 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); + // Zusätzliche Informationen für nicht erfolgreiche Zustände + if ($r->state != 1) { $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); + $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("status", self::COMPONENT_NAME), - $attributes); - - $tmp .= html_writer::tag( - "td", - DtaResult::getStateName($r->state), - $attributes); + $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 state is something different than successful, show additional rows. - if ($r->state != 1) { + // Zeilennummer anzeigen, falls vorhanden + if (!is_null($r->lineNumber) && $r->lineNumber > 0) { $tmp = ""; - $tmp .= html_writer::tag( - "td", - get_string("failure_type", self::COMPONENT_NAME), - $attributes); - - $tmp .= html_writer::tag( - "td", - $r->failureType, - $attributes); + $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); + } + // Spaltennummer anzeigen, falls vorhanden + if (!is_null($r->columnNumber) && $r->columnNumber > 0) { $tmp = ""; - $tmp .= html_writer::tag( - "td", - get_string("failure_reason", self::COMPONENT_NAME), - $attributes); - - $tmp .= html_writer::tag( - "td", - $r->failureReason, - $attributes); + $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); + } - // 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); - } - + // Position anzeigen, falls vorhanden + if (!is_null($r->position) && $r->position > 0) { $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); + $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); } - // Set spacerrow value if null for next round separation. - if (is_null($spacerrow)) { - $spacerrow = html_writer::empty_tag("tr", ["class" => "dtaTableSpacer"]); - } + // Stacktrace anzeigen + $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); } - $html .= html_writer::tag("table", $header . $body, ["class" => "dtaTable"]); - // Wrap generated html into final div. - $html = html_writer::div($html, "dtaSubmissionDetails"); + // Spacerrow für den nächsten Durchlauf setzen + if (is_null($spacerrow)) { + $spacerrow = html_writer::empty_tag("tr", ["class" => "dtaTableSpacer"]); + } + } + $body = html_writer::tag("tbody", $body); + $html .= html_writer::tag("table", $header . $body, ["class" => "dtaTable"]); + + // **Abstand zwischen Detailtabelle und Empfehlungstabelle** + $html .= html_writer::empty_tag("div", ["class" => "dtaSpacer"]); + + // **Empfehlungstabelle hinzufügen** + // Empfehlungen für die Submission abrufen + $recommendations = DbUtils::get_recommendations_from_database($assignmentid, $submissionid); + + if (!empty($recommendations)) { + // Überschrift für Empfehlungen + $html .= html_writer::tag('h3', get_string('recommendations', self::COMPONENT_NAME)); + + // Tabellenkopf für Empfehlungen + $tableheader = ""; + $tableheader .= html_writer::tag("th", get_string("topic", self::COMPONENT_NAME), ["class" => "dtaTableHeader"]); + $tableheader .= html_writer::tag("th", get_string("exercise_name", self::COMPONENT_NAME), ["class" => "dtaTableHeader"]); + $tableheader .= html_writer::tag("th", get_string("url", self::COMPONENT_NAME), ["class" => "dtaTableHeader"]); + $tableheader .= html_writer::tag("th", get_string("difficulty", self::COMPONENT_NAME), ["class" => "dtaTableHeader"]); + $tableheader .= html_writer::tag("th", get_string("score", self::COMPONENT_NAME), ["class" => "dtaTableHeader"]); + + $tableheader = html_writer::tag("tr", $tableheader, ["class" => "dtaTableHeaderRow"]); + $tableheader = html_writer::tag("thead", $tableheader); + + // Tabellenkörper für Empfehlungen + $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); - return $html; + // Empfehlungstabelle zusammenstellen + $html .= html_writer::tag("table", $tableheader . $tablebody, ["class" => "dtaTable"]); } + // Abschließendes Div für die gesamte HTML-Ausgabe + $html = html_writer::div($html, "dtaSubmissionDetails"); + + return $html; +} + + } diff --git a/dta/db/install.xml b/dta/db/install.xml index 4b74456f3de9c87cc529e0c3bea27cc7ab892a5a..56126cf98ded25c4a76255e4a63095644b5994f5 100644 --- a/dta/db/install.xml +++ b/dta/db/install.xml @@ -11,7 +11,6 @@ <FIELD NAME="submission_id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/> <FIELD NAME="successful_competencies" TYPE="char" LENGTH="80" NOTNULL="false"/> <FIELD NAME="tested_competencies" TYPE="char" LENGTH="80" NOTNULL="false"/> - <FIELD NAME="recommendations" TYPE="char" LENGTH="160" NOTNULL="false"/> <FIELD NAME="timestamp" TYPE="int" LENGTH="10"/> <FIELD NAME="global_stacktrace" TYPE="text"/> </FIELDS> @@ -21,6 +20,23 @@ <KEY NAME="fk_submission" TYPE="foreign" FIELDS="submission_id" REFTABLE="assign_submission" REFFIELDS="id" COMMENT="The submission this summary relates to."/> </KEYS> </TABLE> + <TABLE NAME="assignsubmission_dta_recommendations" COMMENT="Stores recommendation data"> + <FIELDS> + <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" COMMENT="Primary Key" /> + <FIELD NAME="assignment_id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/> + <FIELD NAME="submission_id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/> + <FIELD NAME="topic" TYPE="char" LENGTH="255" NOTNULL="true" COMMENT="Recommendation Topic" /> + <FIELD NAME="exercise_name" TYPE="char" LENGTH="255" NOTNULL="true" COMMENT="Exercise Name" /> + <FIELD NAME="url" TYPE="char" LENGTH="255" NOTNULL="true" COMMENT="Exercise URL" /> + <FIELD NAME="difficulty" TYPE="number" LENGTH="10" NOTNULL="true" COMMENT="Exercise Difficulty" /> + <FIELD NAME="score" TYPE="number" LENGTH="10" NOTNULL="true" COMMENT="Exercise Score" /> + </FIELDS> + <KEYS> + <KEY NAME="primary" TYPE="primary" FIELDS="id"/> + <KEY NAME="fk_assignment" TYPE="foreign" FIELDS="assignment_id" REFTABLE="assign" REFFIELDS="id" COMMENT="The assignment instance this recommendations relates to"/> + <KEY NAME="fk_submission" TYPE="foreign" FIELDS="submission_id" REFTABLE="assign_submission" REFFIELDS="id" COMMENT="The submission this recommendations relates to."/> + </KEYS> + </TABLE> <TABLE NAME="assignsubmission_dta_result" COMMENT="DTA testrun single test results"> <FIELDS> <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/> diff --git a/dta/lang/en/assignsubmission_dta.php b/dta/lang/en/assignsubmission_dta.php index e0ddb298cfeb4df2d0dbb411baef342837489e22..9e55c4ef49bb3772972aa7dda7258c459eb75e20 100644 --- a/dta/lang/en/assignsubmission_dta.php +++ b/dta/lang/en/assignsubmission_dta.php @@ -151,7 +151,6 @@ $string["privacy:metadata:assignsubmission_dta_summary:timestamp"] = "Date and t $string["privacy:metadata:assignsubmission_dta_summary"] = "Stack trace of the compilation and test if major problems occur"; $string["privacy:metadata:assignsubmission_dta_summary:successful_competencies"] = "List of the successfully tested competencies"; $string["privacy:metadata:assignsubmission_dta_summary:tested_competencies"] = "List of the tested competencies"; -$string["privacy:metadata:assignsubmission_dta_summary:recommendations"] = "List of recommendations after submitting the code"; $string["privacy:metadata:assignsubmission_dta_summary"] = "Summary of Dockerized Test Agent (DTA) results"; $string["privacy:metadata:assignsubmission_dta_result:package_name"] = "Package name of individual test"; $string["privacy:metadata:assignsubmission_dta_result:class_name"] = "Class name of individual test"; @@ -165,3 +164,12 @@ $string["privacy:metadata:assignsubmission_dta_result:line_number"] = "Line numb $string["privacy:metadata:assignsubmission_dta_result:position"] = "Position of failed individual compilation or test"; $string["privacy:metadata:assignsubmission_dta_result"] = "Individual Dockerized Test Agent (DTA) results"; $string["privacy:metadata:dta_backend"] = "Dockerized Test Agent (DTA) backend ReST web service"; + +//PLUGIN +$string['recommendations'] = 'Recommendations'; +$string['topic'] = 'Topic'; +$string['exercise_name'] = 'Exercise Name'; +$string['url'] = 'URL'; +$string['difficulty'] = 'Difficulty'; +$string['score'] = 'Score'; + diff --git a/dta/locallib.php b/dta/locallib.php index 1a3ab59ece078ee5bb1b3836149049d88b2b4d66..68d2641ceac5230d146e3382eaf6b0ed27cad999 100644 --- a/dta/locallib.php +++ b/dta/locallib.php @@ -240,66 +240,82 @@ class assign_submission_dta extends assign_submission_plugin { return count($files); } - /** - * Save data to the database - * - * @param stdClass $submission - * @param stdClass $data - * @return bool - */ - public function save(stdClass $submission, stdClass $data) { - $data = file_postupdate_standard_filemanager( - $data, - 'tasks', - $this->get_file_options(false), - $this->assignment->get_context(), - self::COMPONENT_NAME, - self::ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION, - $submission->id - ); + /** + * Save data to the database + * + * @param stdClass $submission + * @param stdClass $data + * @return bool + */ +public function save(stdClass $submission, stdClass $data) { + $data = file_postupdate_standard_filemanager( + $data, + 'tasks', + $this->get_file_options(false), + $this->assignment->get_context(), + self::COMPONENT_NAME, + self::ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION, + $submission->id + ); + + // If submission is empty leave directly. + if ($this->is_empty($submission)) { + return true; + } - // If submission is empty leave directly. - if ($this->is_empty($submission)) { - return true; - } + // Get submitted files. + $fs = get_file_storage(); + $files = $fs->get_area_files( + // Id of current assignment. + $this->assignment->get_context()->id, + self::COMPONENT_NAME, + self::ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION, + $submission->id, + 'id', + false + ); + + // Check if a file is uploaded. + if (empty($files)) { + \core\notification::error(get_string("no_submissionfile_warning", self::COMPONENT_NAME)); + return true; + } - // Get submitted files. - $fs = get_file_storage(); - $files = $fs->get_area_files( - // Id of current assignment. - $this->assignment->get_context()->id, - self::COMPONENT_NAME, - self::ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION, - $submission->id, - 'id', - false - ); + // Get the file. + $file = reset($files); - // Check if a file is uploaded. - if (empty($files)) { - \core\notification::error(get_string("no_submissionfile_warning", self::COMPONENT_NAME)); - return true; - } + // Send file to backend. + $response = DtaBackendUtils::send_submission_to_backend($this->assignment, $submission->id, $file); - // Get the file. - $file = reset($files); + // With a null response, return an error. + if (is_null($response)) { + return false; + } - // Send file to backend. - $response = DtaBackendUtils::send_submission_to_backend($this->assignment, $submission->id, $file); + // Convert received json to valid class instances. + $resultsummary = DtaResultSummary::decodejson($response); + // Log an error message. + + $recommendations = DtaResultSummary::decodeJsonRecommendation($response); - // With a null response, return an error. - if (is_null($response)) { - return false; - } + error_log(print_r($recommendations,true)); - // Convert received json to valid class instances. - $resultsummary = DtaResultSummary::decodejson($response); - // Persist new results to database. - DbUtils::storeresultsummarytodatabase($this->assignment->get_instance()->id, $submission->id, $resultsummary); - return true; - } + // Persist new results to database. + DbUtils::storeresultsummarytodatabase($this->assignment->get_instance()->id, $submission->id, $resultsummary); + + + + + // Store the array of records in the database. + DbUtils::storeRecommendationstoDatabase($this->assignment->get_instance()->id, $submission->id, $recommendations); + + + + return true; +} + /** * Display a short summary of the test results of the submission diff --git a/dta/models/DtaResult.php b/dta/models/DtaResult.php index bd7d4b0842ac26d8a94145426fcb096c0014b0ce..867ee8182ee5968b10ea77cd36d84a77da5ed4e8 100644 --- a/dta/models/DtaResult.php +++ b/dta/models/DtaResult.php @@ -147,6 +147,7 @@ class DtaResultSummary { */ public static function decodejson(string $jsonstring): DtaResultSummary { $response = json_decode($jsonstring); + $summary = new DtaResultSummary(); $summary->timestamp = $response->timestamp; @@ -160,6 +161,35 @@ class DtaResultSummary { return $summary; } + public static function decodeJsonRecommendation(string $jsonstring): array { + // Decode the JSON string into a PHP object + $response = json_decode($jsonstring); + error_log("decodeJsonRecommendation"); + error_log(print_r($response, true)); + + + // Initialize an empty array to store recommendations + $recommendations = []; + + // Loop through the recommendations in the response + if (!empty($response->recommendations)) { + foreach ($response->recommendations as $recommendation) { + // For each recommendation, create an associative array with the properties + $recommendations[] = [ + 'topic' => $recommendation->topic ?? null, + 'url' => $recommendation->exerciseName ?? null, + 'exercise_name' => $recommendation->url ?? null, + 'difficulty' => $recommendation->difficulty ?? null, + 'score' => $recommendation->score ?? null + ]; + } + } + error_log(print_r($recommendations,true)); + // Return the array of recommendations + return $recommendations; + } + + /** * 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 diff --git a/dta1.0.zip b/dta1.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..26b39cf40a3686ca39a9f8b767a05ad14afe7802 Binary files /dev/null and b/dta1.0.zip differ