diff --git a/dta.zip b/dta.zip index 7dcd55a2535cd9a81852ca626777d90e7c5afd0e..46de9304492e612abb398213c29115b028b444d5 100644 Binary files a/dta.zip and b/dta.zip differ diff --git a/dta/classes/db_utils.php b/dta/classes/db_utils.php index 0dfb452f9ae6c6b37216faeabd730d93bbe204a2..2361acf3f94d50f5306d237aef5856c3b9527906 100644 --- a/dta/classes/db_utils.php +++ b/dta/classes/db_utils.php @@ -58,7 +58,7 @@ class db_utils { return $records; } $moduleid = $module->id; - + print_r($records); // Schritt 3: Überprüfe jeden Datensatz foreach ($records as $key => $record) { // Hol den Namen der Übung aus dem Datensatz @@ -84,7 +84,7 @@ class db_utils { // Wenn der Abschlussstatus 1 ist, entferne den Datensatz aus $records if ($completion && $completion->completionstate == 1) { unset($records[$key]); - + } } } diff --git a/dta/classes/models/dta_result.php b/dta/classes/models/dta_result.php index d1650eec94ea7cd7d4db9e107754dac606d44686..654350fb92be24322af86d4c7a65654c039d969a 100644 --- a/dta/classes/models/dta_result.php +++ b/dta/classes/models/dta_result.php @@ -100,7 +100,7 @@ class dta_result { * @param int $state Number of the state * @return string Name of state as defined */ - public static function getstatename(int $state): string { + public static function get_statename(int $state): string { if ($state == 1) { return get_string('tests_successful', self::COMPONENT_NAME); } else if ($state == 2) { diff --git a/dta/classes/models/dta_result_summary.php b/dta/classes/models/dta_result_summary.php index 56ed33eeb3238699e9a1ef9fcc1d8aba532c0cb7..20fc745e2e5f3660aaf8c596cdb11ef9fcc39a6c 100644 --- a/dta/classes/models/dta_result_summary.php +++ b/dta/classes/models/dta_result_summary.php @@ -140,7 +140,7 @@ class dta_result_summary { * * @return int Count of occurrences */ - public function compilationerrorcount(): int { + public function compilation_error_count(): int { return $this->state_occurence_count(3); } @@ -149,7 +149,7 @@ class dta_result_summary { * * @return int Count of occurrences */ - public function failedcount(): int { + public function failed_count(): int { return $this->state_occurence_count(2); } @@ -158,7 +158,7 @@ class dta_result_summary { * * @return int Count of occurrences */ - public function successfulcount(): int { + public function successful_count(): int { return $this->state_occurence_count(1); } @@ -167,7 +167,7 @@ class dta_result_summary { * * @return int Count of occurrences */ - public function unknowncount(): int { + public function unknown_count(): int { return $this->state_occurence_count(0); } } diff --git a/dta/classes/view_submission_utils.php b/dta/classes/view_submission_utils.php index fed291adbd024ae77bf5eb75b742e11d024e524d..10e5d6432ded117d3b0b986daf4aeb3460a22da4 100644 --- a/dta/classes/view_submission_utils.php +++ b/dta/classes/view_submission_utils.php @@ -52,23 +52,23 @@ class view_submission_utils { // 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 ); + if ($summary->unknown_count() == 0 && $summary->compilation_error_count() == 0) { + $successrate = round(($summary->successful_count() / $summary->result_count()) * 100, 2 ); } // Generate html. - $html .= $summary->successfulCount() . "/"; - $html .= ($summary->compilationErrorCount() == 0 && $summary->unknownCount() == 0) + $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->compilationErrorCount() > 0) { - $html .= $summary->compilationErrorCount() . get_string("compilation_errors", 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->unknownCount() > 0) { - $html .= $summary->unknownCount() . get_string("unknown_state", 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); @@ -90,53 +90,154 @@ class view_submission_utils { return \html_writer::div($html, "dtaSubmissionSummary"); } - /** - * Generiert die detaillierte HTML-Ansicht, einschließlich Zusammenfassung, Kompetenzen, Details und Empfehlungen. +/** + * generates detailed view html * - * @param int $assignmentid Assignment-ID - * @param int $submissionid Submission-ID, für die der Bericht erstellt wird - * @return string HTML-Code + * @param int $assignmentid assignment + * @param int $submissionid submission to create a report for */ public static function generate_detail_html( int $assignmentid, int $submissionid ): string { - // HTML-Inhalt initialisieren - $html = ""; + // Fetch data. + $summary = db_utils::get_result_summary_from_database($assignmentid, $submissionid); + $recommendations = db_utils::get_recommendations_from_database($assignmentid, $submissionid); - // Daten abrufen - $summary = db_utils::get_Result_Summary_From_Database($assignmentid, $submissionid); + $html = ""; - // CSS-Klassen und HTML-Attributarrays definieren + // Define a few css classes and prepare html attribute arrays to beautify the output. $tableheaderrowattributes = ["class" => "dtaTableHeaderRow"]; $tablerowattributes = ["class" => "dtaTableRow"]; - $attributes = ["class" => "dtaTableData"]; + $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); - // **Zusammenfassungstabelle erstellen** - // (Ihr bisheriger Code bleibt unverändert) + $body = \html_writer::tag("tbody", $body); + $table = \html_writer::tag("table", $header . $body, ["class" => "dtaTable"]); - // **Abstand zwischen Tabellen** - $html .= \html_writer::empty_tag("div", ["class" => "dtaSpacer"]); + $html .= $table; - // **Empfehlungstabelle hinzufügen** - // Empfehlungen für die Submission abrufen - $recommendations = db_utils::get_recommendations_from_database($assignmentid, $submissionid); + // Add empty div for spacing after summary. + $html .= \html_writer::empty_tag("div", ["class" => "dtaSpacer"]); + // *************** + // RECOMMENDATIONS TABLE + // *************** if (!empty($recommendations)) { - // **Sortierparameter abrufen** + // Sorting logic. $allowed_sort_fields = ['topic', 'exercise_name', 'difficulty', 'score']; $allowed_sort_dirs = ['asc', 'desc']; - // Sortierparameter aus POST-Daten abrufen $sortby = isset($_POST['sortby']) ? $_POST['sortby'] : 'score'; $sortdir = isset($_POST['sortdir']) ? $_POST['sortdir'] : 'asc'; - // Sortierparameter validieren if (!in_array($sortby, $allowed_sort_fields)) { $sortby = 'score'; } @@ -144,7 +245,6 @@ public static function generate_detail_html( $sortdir = 'asc'; } - // Empfehlungen sortieren usort($recommendations, function($a, $b) use ($sortby, $sortdir) { $valueA = $a->{$sortby}; $valueB = $b->{$sortby}; @@ -166,10 +266,8 @@ public static function generate_detail_html( } }); - // Überschrift für Empfehlungen $html .= \html_writer::tag('h3', get_string('recommendations', self::COMPONENT_NAME)); - // Helper-Funktion zum Generieren von sortierbaren Headern $generate_sortable_header = function($column_name, $display_name) use ($sortby, $sortdir) { $new_sortdir = ($sortby == $column_name && $sortdir == 'asc') ? 'desc' : 'asc'; $class = 'dtaTableHeader'; @@ -177,7 +275,7 @@ public static function generate_detail_html( $class .= ' sorted ' . $sortdir; } - // Button erstellen + // Sort button. $button = \html_writer::empty_tag('input', [ 'type' => 'submit', 'name' => 'sortbutton', @@ -185,7 +283,7 @@ public static function generate_detail_html( 'class' => 'sort-button' ]); - // Hidden Inputs für Sortierparameter + // Hidden inputs. $hidden_inputs = \html_writer::empty_tag('input', [ 'type' => 'hidden', 'name' => 'sortby', @@ -197,7 +295,6 @@ public static function generate_detail_html( 'value' => $new_sortdir ]); - // Formular für den Button erstellen $form = \html_writer::start_tag('form', ['method' => 'post', 'style' => 'display:inline']); $form .= $hidden_inputs; $form .= $display_name . ' ' . $button; @@ -206,7 +303,7 @@ public static function generate_detail_html( return \html_writer::tag("th", $form, ["class" => $class]); }; - // Tabellenkopf für Empfehlungen + // 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)); @@ -217,7 +314,7 @@ public static function generate_detail_html( $tableheader = \html_writer::tag("tr", $tableheader, ["class" => "dtaTableHeaderRow"]); $tableheader = \html_writer::tag("thead", $tableheader); - // Tabellenkörper für Empfehlungen + // Table body for recommendations. $tablebody = ""; foreach ($recommendations as $recommendation) { $tablerow = ""; @@ -231,11 +328,136 @@ public static function generate_detail_html( } $tablebody = \html_writer::tag("tbody", $tablebody); - // Empfehlungstabelle zusammenstellen $html .= \html_writer::tag("table", $tableheader . $tablebody, ["class" => "dtaTable"]); + + // Add empty div for spacing after recommendations. + $html .= \html_writer::empty_tag("div", ["class" => "dtaSpacer"]); } - // Abschließendes Div für die gesamte HTML-Ausgabe + // *************** + // 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; @@ -244,4 +466,5 @@ public static function generate_detail_html( + } diff --git a/dta/models/DtaResult.php b/dta/models/DtaResult.php new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/dta_withRecommendationsTableAndNamespace.zip b/dta_withRecommendationsTableAndNamespace.zip new file mode 100644 index 0000000000000000000000000000000000000000..0f7b9a6d280819159f9bbcbf34e8c7a73f287ad0 Binary files /dev/null and b/dta_withRecommendationsTableAndNamespace.zip differ