Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development: Introduce text module API #10043

Open
wants to merge 22 commits into
base: chore/introduce-atlas-profile
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9032dcd
add AbstractTextApi
ole-ve Dec 17, 2024
3330431
add TextExerciseImportApi and use in LearningObjectImportService
ole-ve Dec 17, 2024
a606870
use TextExerciseImportApi in ExamImportService
ole-ve Dec 17, 2024
3088275
add TextExerciseSubmissionApi and use in ExampleSubmissionService
ole-ve Dec 17, 2024
5fd08ee
use TextExerciseSubmissionApi in StudentExamService
ole-ve Dec 17, 2024
50da32e
add TextSubmissionExportApi and use in TextPlagiarismDetectionService
ole-ve Dec 17, 2024
0bb8619
rename TextExerciseSubmissionApi to TextSubmissionApi
ole-ve Dec 17, 2024
7964cda
use TextSubmissionExportApi in CourseExamExportService
ole-ve Dec 17, 2024
8264208
use TextSubmissionApi in ParticipationTeamWebsocketService
ole-ve Dec 17, 2024
b231a73
change ExampleSubmissionService
ole-ve Dec 17, 2024
2c87290
replace with api in AthenaDTOConverterService
ole-ve Dec 17, 2024
c57172c
fix related test
ole-ve Dec 17, 2024
fa062f2
make TextApi#findById return an optional
ole-ve Dec 17, 2024
a52fde4
move import functionality to TextSubmissionImportApi
ole-ve Dec 17, 2024
84780b6
replace with api in ExampleSubmissionResource
ole-ve Dec 17, 2024
fdb396c
replace with api for iris module
ole-ve Dec 17, 2024
283440c
replace remaining occurrences with API usage
ole-ve Dec 17, 2024
fbced3e
implement AbstractModuleAccessArchitectureTest in text
ole-ve Dec 17, 2024
990e967
instantiate TextExerciseService lazily in TextApi
ole-ve Dec 17, 2024
cd4613f
Use BadRequestAlertException over IllegalArgumentException
ole-ve Dec 17, 2024
02e28b8
add javadoc
ole-ve Dec 17, 2024
c1ab348
fix missing call to TextSubmissionApi#saveTextSubmission
ole-ve Dec 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import de.tum.cit.aet.artemis.assessment.repository.ExampleSubmissionRepository;
import de.tum.cit.aet.artemis.assessment.repository.GradingCriterionRepository;
import de.tum.cit.aet.artemis.assessment.repository.TutorParticipationRepository;
import de.tum.cit.aet.artemis.core.exception.ApiNotPresentException;
import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException;
import de.tum.cit.aet.artemis.exercise.domain.Exercise;
import de.tum.cit.aet.artemis.exercise.domain.Submission;
Expand All @@ -23,10 +24,10 @@
import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise;
import de.tum.cit.aet.artemis.modeling.domain.ModelingSubmission;
import de.tum.cit.aet.artemis.modeling.service.ModelingExerciseImportService;
import de.tum.cit.aet.artemis.text.api.TextSubmissionApi;
import de.tum.cit.aet.artemis.text.api.TextSubmissionImportApi;
import de.tum.cit.aet.artemis.text.domain.TextExercise;
import de.tum.cit.aet.artemis.text.domain.TextSubmission;
import de.tum.cit.aet.artemis.text.repository.TextSubmissionRepository;
import de.tum.cit.aet.artemis.text.service.TextExerciseImportService;

@Profile(PROFILE_CORE)
@Service
Expand All @@ -40,25 +41,22 @@ public class ExampleSubmissionService {

private final ExerciseRepository exerciseRepository;

private final TextExerciseImportService textExerciseImportService;

private final ModelingExerciseImportService modelingExerciseImportService;

private final TextSubmissionRepository textSubmissionRepository;
private final Optional<TextSubmissionImportApi> textSubmissionImportApi;

private final GradingCriterionRepository gradingCriterionRepository;

private final TutorParticipationRepository tutorParticipationRepository;

public ExampleSubmissionService(ExampleSubmissionRepository exampleSubmissionRepository, SubmissionRepository submissionRepository, ExerciseRepository exerciseRepository,
TextExerciseImportService textExerciseImportService, ModelingExerciseImportService modelingExerciseImportService, TextSubmissionRepository textSubmissionRepository,
ModelingExerciseImportService modelingExerciseImportService, Optional<TextSubmissionImportApi> textSubmissionImportApi,
GradingCriterionRepository gradingCriterionRepository, TutorParticipationRepository tutorParticipationRepository) {
this.exampleSubmissionRepository = exampleSubmissionRepository;
this.submissionRepository = submissionRepository;
this.exerciseRepository = exerciseRepository;
this.modelingExerciseImportService = modelingExerciseImportService;
this.textExerciseImportService = textExerciseImportService;
this.textSubmissionRepository = textSubmissionRepository;
this.textSubmissionImportApi = textSubmissionImportApi;
this.gradingCriterionRepository = gradingCriterionRepository;
this.tutorParticipationRepository = tutorParticipationRepository;
}
Expand Down Expand Up @@ -136,11 +134,9 @@ public ExampleSubmission importStudentSubmissionAsExampleSubmission(Long submiss
newExampleSubmission.setSubmission(modelingExerciseImportService.copySubmission(modelingSubmission, gradingInstructionCopyTracker));
}
if (exercise instanceof TextExercise) {
TextSubmission textSubmission = textSubmissionRepository.findByIdWithEagerResultsAndFeedbackAndTextBlocksElseThrow(submissionId);
checkGivenExerciseIdSameForSubmissionParticipation(exercise.getId(), textSubmission.getParticipation().getExercise().getId());
// example submission does not need participation
textSubmission.setParticipation(null);
newExampleSubmission.setSubmission(textExerciseImportService.copySubmission(textSubmission, gradingInstructionCopyTracker));
var api = textSubmissionImportApi.orElseThrow(() -> new ApiNotPresentException(TextSubmissionApi.class, PROFILE_CORE));
TextSubmission textSubmission = api.importStudentSubmission(submissionId, exercise.getId(), gradingInstructionCopyTracker);
newExampleSubmission.setSubmission(textSubmission);
}
return exampleSubmissionRepository.save(newExampleSubmission);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import de.tum.cit.aet.artemis.assessment.domain.ExampleSubmission;
import de.tum.cit.aet.artemis.assessment.repository.ExampleSubmissionRepository;
import de.tum.cit.aet.artemis.assessment.service.ExampleSubmissionService;
import de.tum.cit.aet.artemis.core.exception.ApiNotPresentException;
import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException;
import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException;
import de.tum.cit.aet.artemis.core.security.Role;
Expand All @@ -35,10 +36,9 @@
import de.tum.cit.aet.artemis.exercise.domain.Exercise;
import de.tum.cit.aet.artemis.exercise.domain.ExerciseType;
import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository;
import de.tum.cit.aet.artemis.text.api.TextSubmissionExportApi;
import de.tum.cit.aet.artemis.text.domain.TextExercise;
import de.tum.cit.aet.artemis.text.domain.TextSubmission;
import de.tum.cit.aet.artemis.text.repository.TextSubmissionRepository;
import de.tum.cit.aet.artemis.text.service.TextBlockService;

/**
* REST controller for managing ExampleSubmission.
Expand All @@ -63,19 +63,15 @@ public class ExampleSubmissionResource {

private final ExerciseRepository exerciseRepository;

private final TextSubmissionRepository textSubmissionRepository;

private final TextBlockService textBlockService;
private final Optional<TextSubmissionExportApi> textSubmissionExportApi;

public ExampleSubmissionResource(ExampleSubmissionService exampleSubmissionService, ExampleSubmissionRepository exampleSubmissionRepository,
AuthorizationCheckService authCheckService, ExerciseRepository exerciseRepository, TextSubmissionRepository textSubmissionRepository,
TextBlockService textBlockService) {
AuthorizationCheckService authCheckService, ExerciseRepository exerciseRepository, Optional<TextSubmissionExportApi> textSubmissionExportApi) {
this.exampleSubmissionService = exampleSubmissionService;
this.exampleSubmissionRepository = exampleSubmissionRepository;
this.authCheckService = authCheckService;
this.exerciseRepository = exerciseRepository;
this.textSubmissionRepository = textSubmissionRepository;
this.textBlockService = textBlockService;
this.textSubmissionExportApi = textSubmissionExportApi;
}

/**
Expand Down Expand Up @@ -135,13 +131,8 @@ public ResponseEntity<Void> prepareExampleAssessment(@PathVariable Long exercise

// Prepare text blocks for fresh assessment
if (exampleSubmission.getExercise().getExerciseType() == ExerciseType.TEXT && exampleSubmission.getSubmission() != null) {
Optional<TextSubmission> textSubmission = textSubmissionRepository.findWithEagerResultsAndFeedbackAndTextBlocksById(exampleSubmission.getSubmission().getId());
if (textSubmission.isPresent() && textSubmission.get().getLatestResult() == null
&& (textSubmission.get().getBlocks() == null || textSubmission.get().getBlocks().isEmpty())) {
TextSubmission submission = textSubmission.get();
textBlockService.computeTextBlocksForSubmissionBasedOnSyntax(submission);
textBlockService.saveAll(submission.getBlocks());
}
textSubmissionExportApi.orElseThrow(() -> new ApiNotPresentException(TextSubmissionExportApi.class, PROFILE_CORE))
.prepareTextBlockForExampleSubmission(exampleSubmission.getSubmission().getId());
}

return ResponseEntity.ok(null);
Expand Down Expand Up @@ -172,7 +163,8 @@ public ResponseEntity<ExampleSubmission> getExampleSubmission(@PathVariable Long

// For TextExercise, we need to load the text blocks as well
if (exampleSubmission.getExercise().getExerciseType() == ExerciseType.TEXT && exampleSubmission.getSubmission() != null) {
Optional<TextSubmission> textSubmission = textSubmissionRepository.findWithEagerResultsAndFeedbackAndTextBlocksById(exampleSubmission.getSubmission().getId());
Optional<TextSubmission> textSubmission = textSubmissionExportApi.orElseThrow(() -> new ApiNotPresentException(TextSubmissionExportApi.class, PROFILE_CORE))
.getSubmissionForExampleSubmission(exampleSubmission.getSubmission().getId());
textSubmission.ifPresent(exampleSubmission::setSubmission);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package de.tum.cit.aet.artemis.athena.service;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_ATHENA;
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import de.tum.cit.aet.artemis.assessment.domain.Feedback;
import de.tum.cit.aet.artemis.assessment.repository.GradingCriterionRepository;
import de.tum.cit.aet.artemis.assessment.repository.TextBlockRepository;
import de.tum.cit.aet.artemis.athena.dto.ExerciseBaseDTO;
import de.tum.cit.aet.artemis.athena.dto.FeedbackBaseDTO;
import de.tum.cit.aet.artemis.athena.dto.ModelingExerciseDTO;
Expand All @@ -21,15 +23,16 @@
import de.tum.cit.aet.artemis.athena.dto.TextExerciseDTO;
import de.tum.cit.aet.artemis.athena.dto.TextFeedbackDTO;
import de.tum.cit.aet.artemis.athena.dto.TextSubmissionDTO;
import de.tum.cit.aet.artemis.core.exception.ApiNotPresentException;
import de.tum.cit.aet.artemis.exercise.domain.Exercise;
import de.tum.cit.aet.artemis.exercise.domain.Submission;
import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise;
import de.tum.cit.aet.artemis.modeling.domain.ModelingSubmission;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingSubmission;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository;
import de.tum.cit.aet.artemis.text.api.TextApi;
import de.tum.cit.aet.artemis.text.domain.TextBlock;
import de.tum.cit.aet.artemis.text.domain.TextSubmission;
import de.tum.cit.aet.artemis.text.repository.TextExerciseRepository;

/**
* Service to convert exercises, submissions and feedback to DTOs for Athena.
Expand All @@ -41,18 +44,15 @@ public class AthenaDTOConverterService {
@Value("${server.url}")
private String artemisServerUrl;

private final TextBlockRepository textBlockRepository;

private final TextExerciseRepository textExerciseRepository;
private final Optional<TextApi> textApi;

private final ProgrammingExerciseRepository programmingExerciseRepository;

private final GradingCriterionRepository gradingCriterionRepository;

public AthenaDTOConverterService(TextBlockRepository textBlockRepository, TextExerciseRepository textExerciseRepository,
ProgrammingExerciseRepository programmingExerciseRepository, GradingCriterionRepository gradingCriterionRepository) {
this.textBlockRepository = textBlockRepository;
this.textExerciseRepository = textExerciseRepository;
public AthenaDTOConverterService(Optional<TextApi> textApi, ProgrammingExerciseRepository programmingExerciseRepository,
GradingCriterionRepository gradingCriterionRepository) {
this.textApi = textApi;
this.programmingExerciseRepository = programmingExerciseRepository;
this.gradingCriterionRepository = gradingCriterionRepository;
}
Expand All @@ -67,7 +67,7 @@ public ExerciseBaseDTO ofExercise(Exercise exercise) {
switch (exercise.getExerciseType()) {
case TEXT -> {
// Fetch text exercise with grade criteria
var textExercise = textExerciseRepository.findWithGradingCriteriaByIdElseThrow(exercise.getId());
var textExercise = textApi.orElseThrow(() -> new ApiNotPresentException(TextApi.class, PROFILE_CORE)).findWithGradingCriteriaByIdElseThrow(exercise.getId());
return TextExerciseDTO.of(textExercise);
}
case PROGRAMMING -> {
Expand Down Expand Up @@ -117,8 +117,8 @@ public FeedbackBaseDTO ofFeedback(Exercise exercise, long submissionId, Feedback
switch (exercise.getExerciseType()) {
case TEXT -> {
TextBlock feedbackTextBlock = null;
if (feedback.getReference() != null) {
feedbackTextBlock = textBlockRepository.findById(feedback.getReference()).orElse(null);
if (feedback.getReference() != null && textApi.isPresent()) {
feedbackTextBlock = textApi.get().findById(feedback.getReference()).orElse(null);
}
return TextFeedbackDTO.of(exercise.getId(), submissionId, feedback, feedbackTextBlock);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package de.tum.cit.aet.artemis.athena.web;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_ATHENA;
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

import org.slf4j.Logger;
Expand All @@ -27,6 +29,7 @@
import de.tum.cit.aet.artemis.athena.service.AthenaRepositoryExportService;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.exception.AccessForbiddenException;
import de.tum.cit.aet.artemis.core.exception.ApiNotPresentException;
import de.tum.cit.aet.artemis.core.exception.InternalServerErrorException;
import de.tum.cit.aet.artemis.core.exception.NetworkingException;
import de.tum.cit.aet.artemis.core.repository.CourseRepository;
Expand All @@ -45,8 +48,8 @@
import de.tum.cit.aet.artemis.programming.domain.RepositoryType;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingSubmissionRepository;
import de.tum.cit.aet.artemis.text.repository.TextExerciseRepository;
import de.tum.cit.aet.artemis.text.repository.TextSubmissionRepository;
import de.tum.cit.aet.artemis.text.api.TextApi;
import de.tum.cit.aet.artemis.text.api.TextSubmissionApi;

/**
* REST controller for Athena feedback suggestions.
Expand All @@ -63,9 +66,9 @@ public class AthenaResource {

private final CourseRepository courseRepository;

private final TextExerciseRepository textExerciseRepository;
private final Optional<TextApi> textApi;

private final TextSubmissionRepository textSubmissionRepository;
private final Optional<TextSubmissionApi> textSubmissionApi;

private final ProgrammingExerciseRepository programmingExerciseRepository;

Expand All @@ -86,14 +89,14 @@ public class AthenaResource {
/**
* The AthenaResource provides an endpoint for the client to fetch feedback suggestions from Athena.
*/
public AthenaResource(CourseRepository courseRepository, TextExerciseRepository textExerciseRepository, TextSubmissionRepository textSubmissionRepository,
public AthenaResource(CourseRepository courseRepository, Optional<TextApi> textApi, Optional<TextSubmissionApi> textSubmissionApi,
ProgrammingExerciseRepository programmingExerciseRepository, ProgrammingSubmissionRepository programmingSubmissionRepository,
ModelingExerciseRepository modelingExerciseRepository, ModelingSubmissionRepository modelingSubmissionRepository, AuthorizationCheckService authCheckService,
AthenaFeedbackSuggestionsService athenaFeedbackSuggestionsService, AthenaRepositoryExportService athenaRepositoryExportService,
AthenaModuleService athenaModuleService) {
this.courseRepository = courseRepository;
this.textExerciseRepository = textExerciseRepository;
this.textSubmissionRepository = textSubmissionRepository;
this.textSubmissionApi = textSubmissionApi;
this.textApi = textApi;
this.programmingExerciseRepository = programmingExerciseRepository;
this.programmingSubmissionRepository = programmingSubmissionRepository;
this.modelingExerciseRepository = modelingExerciseRepository;
Expand Down Expand Up @@ -162,7 +165,10 @@ private ResponseEntity<List<String>> getAvailableModules(long courseId, Exercise
@GetMapping("athena/text-exercises/{exerciseId}/submissions/{submissionId}/feedback-suggestions")
@EnforceAtLeastTutor
public ResponseEntity<List<TextFeedbackDTO>> getTextFeedbackSuggestions(@PathVariable long exerciseId, @PathVariable long submissionId) {
return getFeedbackSuggestions(exerciseId, submissionId, textExerciseRepository::findByIdElseThrow, textSubmissionRepository::findByIdElseThrow,
var api = textApi.orElseThrow(() -> new ApiNotPresentException(TextApi.class, PROFILE_CORE));
var submissionApi = textSubmissionApi.orElseThrow(() -> new ApiNotPresentException(TextSubmissionApi.class, PROFILE_CORE));

return getFeedbackSuggestions(exerciseId, submissionId, api::findByIdElseThrow, submissionApi::findByIdElseThrow,
athenaFeedbackSuggestionsService::getTextFeedbackSuggestions);
}

Expand Down
Loading
Loading