diff --git a/build.gradle b/build.gradle index b377b2b..a95e764 100644 --- a/build.gradle +++ b/build.gradle @@ -116,6 +116,7 @@ dependencies { implementation 'de.unistuttgart.iste.meitrex:course_service:1.1.0rc2' implementation 'de.unistuttgart.iste.meitrex:user_service:1.0.0rc1' implementation 'com.google.code.gson:gson:2.13.1' + implementation 'com.github.slugify:slugify:3.0.7' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-graphql' implementation 'org.springframework.boot:spring-boot-starter-validation' diff --git a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/controller/AssignmentController.java b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/controller/AssignmentController.java index c38f4b3..7d1bfd8 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/controller/AssignmentController.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/controller/AssignmentController.java @@ -54,6 +54,8 @@ public ExternalCourse getExternalCourse(@Argument final UUID courseId, @ContextV @QueryMapping public List getGradingsForAssignment(@Argument final UUID assessmentId, @ContextValue final LoggedInUser currentUser) { + log.info("[GRADING-FLOW] GraphQL query received: getGradingsForAssignment for assessmentId={}, userId={}", + assessmentId, currentUser.getId()); return gradingService.getGradingsForAssignment(assessmentId, currentUser); } diff --git a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/GradingService.java b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/GradingService.java index 334e32b..b6e1da6 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/GradingService.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/GradingService.java @@ -69,13 +69,17 @@ public class GradingService { private final ExternalCourseRepository externalCourseRepository; public List getGradingsForAssignment(final UUID assignmentId, final LoggedInUser currentUser) { + log.info("[GRADING-FLOW] Starting getGradingsForAssignment for assignmentId={}, userId={}", + assignmentId, currentUser.getId()); + final AssignmentEntity assignment = assignmentService.requireAssignmentExists(assignmentId); LoggedInUser.CourseMembership courseMembership = currentUser.getCourseMemberships().stream() .filter(membership -> membership.getCourseId().equals(assignment.getCourseId())).findFirst() .orElseThrow(() -> new NoAccessToCourseException(assignment.getCourseId(), "User is not a member of the course.")); - + if (assignment.getAssignmentType() == AssignmentType.CODE_ASSIGNMENT){ + log.info("[GRADING-FLOW] Processing CODE_ASSIGNMENT type"); List gradings = new ArrayList<>(); // Always get the current student's grading so that they see it in student's view (even if the user is a tutor/admin) @@ -189,6 +193,9 @@ private List getCodeAssignmentGradingForAdmin(final AssignmentEntity as private List getCodeAssignmentGradingForStudent(final AssignmentEntity assignment, final LoggedInUser currentUser) { + log.info("[GRADING-FLOW] >>> getCodeAssignmentGradingForStudent START - assignmentId={}, studentId={}", + assignment.getId(), currentUser.getId()); + GradingEntity gradingEntity = ensureGradingEntityExists(assignment.getId(), currentUser.getId()); findAndSetRepositoryLinkIfMissing(gradingEntity, assignment, currentUser); @@ -197,7 +204,9 @@ private List getCodeAssignmentGradingForStudent(final AssignmentEntity syncAndUpdateGrading(gradingEntity, assignment, currentUser); } + log.info("[GRADING-FLOW] Saving grading entity to database"); gradingEntity = gradingRepository.save(gradingEntity); + log.info("[GRADING-FLOW] <<< getCodeAssignmentGradingForStudent END - returning grading"); return List.of(assignmentMapper.gradingEntityToDto(gradingEntity)); } @@ -208,8 +217,9 @@ private List getCodeAssignmentGradingForStudent(final AssignmentEntity private GradingEntity ensureGradingEntityExists(final UUID assignmentId, final UUID studentId) { final GradingEntity.PrimaryKey pk = new GradingEntity.PrimaryKey(assignmentId, studentId); GradingEntity gradingEntity = gradingRepository.findById(pk).orElse(null); - + if (gradingEntity == null) { + log.info("[GRADING-FLOW] Creating new grading entity for first-time access"); gradingEntity = GradingEntity.builder().primaryKey(pk).build(); CodeAssignmentGradingMetadataEntity metadata = CodeAssignmentGradingMetadataEntity.builder() @@ -243,6 +253,7 @@ private void findAndSetRepositoryLinkIfMissing(final GradingEntity gradingEntity return; } + log.info("[GRADING-FLOW] Repository link not found, attempting to find student repository"); try { String assignmentName = contentServiceClient.queryContentsOfCourse(currentUser.getId(), assignment.getCourseId()).stream() .filter(assignmentDto -> assignmentDto.getId().equals(assignment.getId())) @@ -253,11 +264,14 @@ private void findAndSetRepositoryLinkIfMissing(final GradingEntity gradingEntity String courseTitle = courseServiceClient.queryCourseById(assignment.getCourseId()).getTitle(); String organizationName = externalCourseRepository.findById(courseTitle).get().getOrganizationName(); + log.info("[GRADING-FLOW] Calling findRepository with assignmentName={}, organizationName={}", + assignmentName, organizationName); String repoLink = codeAssessmentProvider.findRepository(assignmentName, organizationName, currentUser); + log.info("[GRADING-FLOW] Repository link found: {}", repoLink != null ? repoLink : "NULL"); gradingEntity.getCodeAssignmentGradingMetadata().setRepoLink(repoLink); } catch (ExternalPlatformConnectionException | UserServiceConnectionException | ContentServiceConnectionException | CourseServiceConnectionException e) { - log.error("Failed to find repository for assignment {} and student {}: {}", + log.error("[GRADING-FLOW] ERROR: Failed to find repository for assignment {} and student {}: {}", assignment.getId(), currentUser.getId(), e.toString()); } } @@ -268,12 +282,16 @@ private void findAndSetRepositoryLinkIfMissing(final GradingEntity gradingEntity private void syncAndUpdateGrading(final GradingEntity gradingEntity, final AssignmentEntity assignment, final LoggedInUser currentUser) { + log.info("[GRADING-FLOW] Repository link exists: {}", + gradingEntity.getCodeAssignmentGradingMetadata().getRepoLink()); ExternalGrading externalGrading; try { externalGrading = codeAssessmentProvider.syncGradeForStudent( gradingEntity.getCodeAssignmentGradingMetadata().getRepoLink(), currentUser); + log.info("[GRADING-FLOW] syncGradeForStudent completed - achievedPoints={}, totalPoints={}, status={}", + externalGrading.achievedPoints(), externalGrading.totalPoints(), externalGrading.status()); } catch (ExternalPlatformConnectionException | UserServiceConnectionException e) { - log.error("Failed to sync student grade for assignment {} and student {}: {}", + log.error("[GRADING-FLOW] ERROR: Failed to sync student grade for assignment {} and student {}: {}", assignment.getId(), currentUser.getId(), e.toString()); return; } @@ -318,20 +336,32 @@ private void handleCodeSubmissionEvent(final GradingEntity gradingEntity, return; } + log.info("[GRADING-FLOW] Attempting to fetch student code from repository"); try { if (codeAssessmentProvider instanceof de.unistuttgart.iste.meitrex.assignment_service.service.code_assignment.GithubClassroom githubClassroom) { + log.info("[GRADING-FLOW] Calling fetchStudentCode for repoLink={}", + metadata.getRepoLink()); + de.unistuttgart.iste.meitrex.assignment_service.service.code_assignment.StudentCodeSubmission codeSubmission = githubClassroom.fetchStudentCode(metadata.getRepoLink(), currentUser); + log.info("[GRADING-FLOW] fetchStudentCode completed successfully - files count: {}, commit: {}", + codeSubmission.getFiles().size(), codeSubmission.getCommitSha()); + codeSubmission.setAssignmentId(assignment.getId()); codeSubmission.setCourseId(assignment.getCourseId()); + log.info("[GRADING-FLOW] Publishing StudentCodeSubmittedEvent"); publishStudentCodeSubmittedEvent(codeSubmission); + log.info("[GRADING-FLOW] StudentCodeSubmittedEvent published successfully"); metadata.setLastProcessedCommitSha(currentCommit != null ? currentCommit : "NO_COMMIT_SHA_PROCESSED"); + } else { + log.warn("[GRADING-FLOW] Code assessment provider is not GithubClassroom instance: {}", + codeAssessmentProvider.getClass().getName()); } } catch (ExternalPlatformConnectionException | UserServiceConnectionException e) { - log.error("Failed to fetch student code for assignment {} and student {}: {}", + log.error("[GRADING-FLOW] ERROR: Failed to fetch student code for assignment {} and student {}: {}", assignment.getId(), currentUser.getId(), e.toString()); } } @@ -365,6 +395,7 @@ private boolean shouldSendCodeSubmissionEvent(final String lastProcessedCommit, } } } + } /** * Returns gradings for the current user on the given assignment. diff --git a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/code_assignment/GithubClassroom.java b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/code_assignment/GithubClassroom.java index 178ec0c..6b1b0f4 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/code_assignment/GithubClassroom.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/code_assignment/GithubClassroom.java @@ -1,6 +1,7 @@ package de.unistuttgart.iste.meitrex.assignment_service.service.code_assignment; import com.google.gson.*; +import com.github.slugify.Slugify; import de.unistuttgart.iste.meitrex.assignment_service.exception.ExternalPlatformConnectionException; import de.unistuttgart.iste.meitrex.assignment_service.persistence.entity.assignment.ExternalCodeAssignmentEntity; import de.unistuttgart.iste.meitrex.assignment_service.persistence.repository.AssignmentRepository; @@ -511,13 +512,17 @@ private String extractGradingTableAsHtml(String logs) { @Override public String findRepository(final String assignmentName, final String organizationName, final LoggedInUser currentUser) throws ExternalPlatformConnectionException, UserServiceConnectionException { + log.info("[GITHUB-API] >>> findRepository START - assignmentName={}, organizationName={}, userId={}", + assignmentName, organizationName, currentUser.getId()); try { AccessToken queryTokenResponse = userServiceClient.queryAccessToken(currentUser, NAME); String token = queryTokenResponse.getAccessToken(); String githubUsername = queryTokenResponse.getExternalUserId(); - String slug = assignmentName.toLowerCase().replaceAll("\\s+", "-"); + final Slugify slg = Slugify.builder().build(); + String slug = slg.slugify(assignmentName); String repoName = slug + "-" + githubUsername; + log.info("[GITHUB-API] Constructed repository name: {}", repoName); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(basePath + "/repos/" + organizationName + "/" + repoName)) @@ -528,6 +533,7 @@ public String findRepository(final String assignmentName, final String organizat .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + log.info("[GITHUB-API] GitHub API response status: {}", response.statusCode()); if (response.statusCode() == 404) { return null; @@ -542,8 +548,10 @@ public String findRepository(final String assignmentName, final String organizat } catch (InterruptedException e) { Thread.currentThread().interrupt(); + log.error("[GITHUB-API] Interrupted while fetching student repo link"); throw new ExternalPlatformConnectionException("Interrupted while fetching student repo link", e); } catch (IOException e) { + log.error("[GITHUB-API] IOException while fetching student repo link"); throw new ExternalPlatformConnectionException("Error fetching student repo link", e); } @@ -612,6 +620,7 @@ public ExternalCourse getExternalCourse(final String courseTitle, final LoggedIn */ public StudentCodeSubmission fetchStudentCode(String repoLink, LoggedInUser currentUser) throws ExternalPlatformConnectionException, UserServiceConnectionException { + log.info("[GITHUB-API] >>> fetchStudentCode START - repoLink={}, userId={}", repoLink, currentUser.getId()); try { AccessToken tokenResponse = userServiceClient.queryAccessToken(currentUser, NAME); String token = tokenResponse.getAccessToken(); @@ -619,10 +628,12 @@ public StudentCodeSubmission fetchStudentCode(String repoLink, LoggedInUser curr URI uri = URI.create(repoLink); String[] parts = uri.getPath().split("/"); if (parts.length < 3) { + log.error("[GITHUB-API] Invalid repo URL format: {}, parts count: {}", repoLink, parts.length); throw new ExternalPlatformConnectionException("Invalid repo URL: " + repoLink); } String owner = parts[1]; String repo = parts[2]; + log.info("[GITHUB-API] Parsed repository - owner={}, repo={}", owner, repo); HttpRequest repoRequest = HttpRequest.newBuilder() .uri(URI.create(basePath + "/repos/" + owner + "/" + repo)) @@ -633,7 +644,11 @@ public StudentCodeSubmission fetchStudentCode(String repoLink, LoggedInUser curr .build(); HttpResponse repoResponse = client.send(repoRequest, HttpResponse.BodyHandlers.ofString()); + log.info("[GITHUB-API] Repository info response status: {}", repoResponse.statusCode()); + if (repoResponse.statusCode() != 200) { + log.error("[GITHUB-API] Failed to fetch repository info: status={}, body={}", + repoResponse.statusCode(), repoResponse.body()); throw new ExternalPlatformConnectionException("Failed to fetch repository info: " + repoResponse.body()); } @@ -649,7 +664,11 @@ public StudentCodeSubmission fetchStudentCode(String repoLink, LoggedInUser curr .build(); HttpResponse commitResponse = client.send(commitRequest, HttpResponse.BodyHandlers.ofString()); + log.info("[GITHUB-API] Commit info response status: {}", commitResponse.statusCode()); + if (commitResponse.statusCode() != 200) { + log.error("[GITHUB-API] Failed to fetch commit info: status={}, body={}", + commitResponse.statusCode(), commitResponse.body()); throw new ExternalPlatformConnectionException("Failed to fetch commit info: " + commitResponse.body()); } @@ -661,7 +680,9 @@ public StudentCodeSubmission fetchStudentCode(String repoLink, LoggedInUser curr OffsetDateTime commitDate = OffsetDateTime.parse(commitDateStr); Map files = new HashMap<>(); + log.info("[GITHUB-API] Starting recursive file fetch from repository"); fetchFilesRecursively(owner, repo, defaultBranch, "", token, files); + log.info("[GITHUB-API] Finished fetching files - total files retrieved: {}", files.size()); return StudentCodeSubmission.builder() .studentId(currentUser.getId()) @@ -674,9 +695,14 @@ public StudentCodeSubmission fetchStudentCode(String repoLink, LoggedInUser curr } catch (InterruptedException e) { Thread.currentThread().interrupt(); + log.error("[GITHUB-API] Interrupted while fetching student code from {}", repoLink); throw new ExternalPlatformConnectionException("Interrupted while fetching student code", e); } catch (IOException e) { + log.error("[GITHUB-API] IOException while fetching student code from {}", repoLink); throw new ExternalPlatformConnectionException("Error fetching student code", e); + } catch (Exception e) { + log.error("[GITHUB-API] Unexpected error while fetching student code from {}", repoLink); + throw new ExternalPlatformConnectionException("Unexpected error fetching student code: " + e.getMessage()); } } @@ -705,7 +731,8 @@ private void fetchFilesRecursively(String owner, String repo, String branch, Str HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { - log.warn("Failed to fetch contents for path '{}': {}", path, response.body()); + log.warn("[GITHUB-API] Failed to fetch contents for path '{}': status={}, body={}", + path, response.statusCode(), response.body()); return; } @@ -728,7 +755,7 @@ private void fetchFilesRecursively(String owner, String repo, String branch, Str if (fileResponse.statusCode() == 200) { files.put(itemPath, fileResponse.body()); } else { - log.warn("Failed to fetch file content for: {}", itemPath); + log.warn("[GITHUB-API] Failed to fetch file content for: {}, status={}", itemPath, fileResponse.statusCode()); } } else if ("dir".equals(type)) { fetchFilesRecursively(owner, repo, branch, itemPath, token, files);