From 336b341f9608537ceaf394cba6037bbeffbccfbd Mon Sep 17 00:00:00 2001 From: Hamza Remmal <hamza@remmal.net> Date: Sun, 23 Mar 2025 17:14:15 +0000 Subject: [PATCH] chore: add support to get the grade from Moodle --- .idea/dataSources.xml | 2 +- .../classes/external/autograde_get_grade.php | 95 +++++++++++++++++++ .../src/db/access.php | 6 ++ .../src/db/services.php | 7 ++ .../api/v1/SubmissionController.java | 35 +++---- .../response/SubmissionFeedbackResponse.java | 7 ++ .../autograde/service/MoodleWebService.java | 23 +++++ 7 files changed, 150 insertions(+), 25 deletions(-) create mode 100644 autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_grade.php create mode 100644 autograde-service/src/main/java/ch/epfl/autograde/model/response/SubmissionFeedbackResponse.java diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 16c9e31e..fd2cd31f 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -5,7 +5,7 @@ <driver-ref>mysql.8</driver-ref> <synchronize>true</synchronize> <jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver> - <jdbc-url>jdbc:mysql://localhost:3306/moodle</jdbc-url> + <jdbc-url>jdbc:mysql://localhost:3305/moodle</jdbc-url> <jdbc-additional-properties> <property name="com.intellij.clouds.kubernetes.db.host.port" /> <property name="com.intellij.clouds.kubernetes.db.enabled" value="false" /> diff --git a/autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_grade.php b/autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_grade.php new file mode 100644 index 00000000..90ae436a --- /dev/null +++ b/autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_grade.php @@ -0,0 +1,95 @@ +<?php +// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>. +/** + * This file defines the function to get the grade of a submission + * + * @see https://moodledev.io/docs/apis/subsystems/external/functions + * @author ??? + * @package ??? + * @copyright 2023 AUTOGRADE-EPFL <autograde-support@groupes.epfl.ch> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace assignsubmission_autograde\external; + +defined('MOODLE_INTERNAL') || die(); + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use context_course; +use moodle_exception; +use stdClass; +use assign_submission; +use function assignsubmission_autograde\utils\assignsubmission_autograde_get_assignment; + +require_once(__DIR__.'/../../lib.php'); +require_once(__DIR__.'/../../utils.php'); +require_once($CFG->libdir . '/externallib.php'); +require_once($CFG->dirroot . '/mod/assign/locallib.php'); + + +final class autograde_get_grade extends external_api { + + /** + * Define the function parameters + * @return external_function_parameters + */ + public static function get_grade_parameters(): external_function_parameters { + return new external_function_parameters([ + 'sid' => new external_value(PARAM_INT, 'The ID of the submission'), + ]); + } + + /** + * Retrieve the grade for a submission + * @param int $sid + * @return stdClass + * @throws moodle_exception + */ + public static function get_grade($sid): stdClass { + global $DB, $USER; + + self::validate_parameters(self::get_grade_parameters(), ['sid' => $sid]); + + $submission = $DB->get_record('assign_submission', [ 'id' => $sid ]); + $assignment = $DB->get_record('assign', [ 'id' => $submission->assignment ]); + + require_capability('assignsubmission/autograde:viewgrades', + context_course::instance($assignment->course), $USER->id, false); + + $grade = $DB->get_field('assign_grades', 'grade', ['assignment' => $assignment->id, 'attemptnumber' => $submission->attemptnumber]); + + if (!$grade) { + throw new moodle_exception('Grade not found'); + } + + $result = new stdClass(); + $result->grade = $grade; + return $result; + } + + /** + * Define the return structure + * @return external_single_structure + */ + public static function get_grade_returns(): external_single_structure { + return new external_single_structure([ + 'grade' => new external_value(PARAM_FLOAT, 'The grade'), + ]); + } +} \ No newline at end of file diff --git a/autograde-plugins/assignsubmission_autograde/src/db/access.php b/autograde-plugins/assignsubmission_autograde/src/db/access.php index e7f8db95..8cb1dfb5 100644 --- a/autograde-plugins/assignsubmission_autograde/src/db/access.php +++ b/autograde-plugins/assignsubmission_autograde/src/db/access.php @@ -36,6 +36,12 @@ $capabilities = array( 'clonepermissionsfrom' => 'mod/assign/submission/autograde:read_submission' ), + 'assignsubmission/autograde:viewgrades' => array( + 'riskbitmask' => RISK_PERSONAL, + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => array(), + ), // HR : Capability to write a feedback 'assignsubmission/autograde:grade' => array( 'riskbitmask' => RISK_SPAM | RISK_XSS, diff --git a/autograde-plugins/assignsubmission_autograde/src/db/services.php b/autograde-plugins/assignsubmission_autograde/src/db/services.php index 103aa3cf..6bffb64a 100644 --- a/autograde-plugins/assignsubmission_autograde/src/db/services.php +++ b/autograde-plugins/assignsubmission_autograde/src/db/services.php @@ -52,6 +52,12 @@ $functions = [ 'description' => 'List all of the information for a given submission', 'capabilities' => 'assignsubmission/autograde:viewsubmission' ], + 'mod_assignsubmission_autograde_get_grade' => [ + 'classname' => 'assignsubmission_autograde\external\autograde_get_grade' , + 'methodname' => 'get_grade', + 'description' => 'Get the Grade of a Given Submission', + 'capabilities' => 'assignsubmission/autograde:viewgrades' + ], 'mod_assignsubmission_autograde_download_all_submissions' => [ 'classname' => 'assignsubmission_autograde\external\autograde_download_all_submissions' , 'methodname' => 'download_all_submissions', @@ -66,6 +72,7 @@ $services = [ 'mod_assignsubmission_autograde_download_submission', 'mod_assignsubmission_autograde_list_submissions', 'mod_assignsubmission_autograde_submission_info', + 'mod_assignsubmission_autograde_get_grade', 'mod_assignsubmission_autograde_download_all_submissions', ], 'restrictedusers' => 1, diff --git a/autograde-service/src/main/java/ch/epfl/autograde/controller/api/v1/SubmissionController.java b/autograde-service/src/main/java/ch/epfl/autograde/controller/api/v1/SubmissionController.java index 95a81760..423d1d51 100644 --- a/autograde-service/src/main/java/ch/epfl/autograde/controller/api/v1/SubmissionController.java +++ b/autograde-service/src/main/java/ch/epfl/autograde/controller/api/v1/SubmissionController.java @@ -2,6 +2,7 @@ package ch.epfl.autograde.controller.api.v1; import ch.epfl.autograde.model.request.CreateSubmissionRequest; import ch.epfl.autograde.model.request.UploadFeedbackRequest; +import ch.epfl.autograde.model.response.SubmissionFeedbackResponse; import ch.epfl.autograde.model.response.SubmissionInfoResponse; import ch.epfl.autograde.model.response.SubmissionStatusResponse; import ch.epfl.autograde.model.response.CreateSubmissionResponse; @@ -184,9 +185,13 @@ public final class SubmissionController { * </ul> */ @GetMapping(value = "/{id}/feedback", consumes = MediaType.ALL_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<?> getFeedback(final @PathVariable int id) { - // TODO: Request the feedback from Moodle - return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build(); + public ResponseEntity<SubmissionFeedbackResponse> getFeedback(final @PathVariable int id) { + return ResponseEntity + .status(HttpStatus.OK) + .body(new SubmissionFeedbackResponse( + moodle.getGradeForSubmission(id), + String.format("%s/api/v1/submission/%d/feedback/files", autograde.getUrl() , id) + )); } /** @@ -207,27 +212,9 @@ public final class SubmissionController { @GetMapping(value = "/{id}/feedback/files", consumes = MediaType.ALL_VALUE, produces = "application/zip") public ResponseEntity<?> getFeedbackFiles(final @PathVariable int id) { // TODO: Request the feedback files from Moodle - return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build(); - } - - /** - * REST Endpoint to request the grade of a given submission - * - * @param id The unique id of the requested submission - * @since 1.3.0 - * @return - * <ul> - * <li>{@link HttpStatus.OK} if the request is successful</li> - * <li>{@link HttpStatus.UNAUTHORIZED} if the user is not authenticated</li> - * <li>{@link HttpStatus.FORBIDDEN} if the user is authenticated but not allowed to perform this action</li> - * <li>{@link HttpStatus.NOT_FOUND} if the submission is missing</li> - * <li>{@link HttpStatus.INTERNAL_SERVER_ERROR} in case of a server failure</li> - * </ul> - */ - @GetMapping(value = "/{id}/feedback/grade", consumes = MediaType.ALL_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) - public ResponseEntity<?> getGrade(final @PathVariable int id) { - // TODO: Request the grade from Moodle - return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build(); + return ResponseEntity + .status(HttpStatus.NOT_IMPLEMENTED) + .build(); } } diff --git a/autograde-service/src/main/java/ch/epfl/autograde/model/response/SubmissionFeedbackResponse.java b/autograde-service/src/main/java/ch/epfl/autograde/model/response/SubmissionFeedbackResponse.java new file mode 100644 index 00000000..a8ba8d59 --- /dev/null +++ b/autograde-service/src/main/java/ch/epfl/autograde/model/response/SubmissionFeedbackResponse.java @@ -0,0 +1,7 @@ +package ch.epfl.autograde.model.response; + +public record SubmissionFeedbackResponse( + float grade, + String files +) { +} diff --git a/autograde-service/src/main/java/ch/epfl/autograde/service/MoodleWebService.java b/autograde-service/src/main/java/ch/epfl/autograde/service/MoodleWebService.java index 6cc96bfe..8d150f3b 100644 --- a/autograde-service/src/main/java/ch/epfl/autograde/service/MoodleWebService.java +++ b/autograde-service/src/main/java/ch/epfl/autograde/service/MoodleWebService.java @@ -222,4 +222,27 @@ public final class MoodleWebService { } + public float getGradeForSubmission(int sid) { + record Grade(float grade) {} + // ---- + log.info("Fetching grade for submission {}", sid); + + final var FUNCTION_NAME = "mod_assignsubmission_autograde_get_grade"; + final var params = Map.of("sid", sid); + + try { + var response = call(FUNCTION_NAME, params, ofString(UTF_8)); + if (response.statusCode() != HttpStatus.OK.value()) { + log.error("Failed to fetch grade for submission {}", sid); + throw new IllegalStateException(); + } + + System.out.println(response.body()); + Grade result = mapper.readValue(response.body(), new TypeReference<Grade>() {}); + return result.grade(); + } catch (Exception e) { + log.error("Error fetching grade for submission {}: {}", sid, e.getMessage()); + throw new RuntimeException(e); + } + } } -- GitLab