Skip to content

Commit

Permalink
Merge branch 'develop' into feature/programming-exercises/add-simple-…
Browse files Browse the repository at this point in the history
…mode

# Conflicts:
#	src/main/java/de/tum/cit/aet/artemis/core/web/CourseResource.java
#	src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java
#	src/test/java/de/tum/cit/aet/artemis/programming/CourseGitlabJenkinsIntegrationTest.java
  • Loading branch information
florian-glombik committed Oct 24, 2024
2 parents 7384861 + caea966 commit b910eed
Show file tree
Hide file tree
Showing 192 changed files with 4,154 additions and 857 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ Refer to [Using JHipster in production](http://www.jhipster.tech/production) for
The following command can automate the deployment to a server. The example shows the deployment to the main Artemis test server (which runs a virtual machine):

```shell
./artemis-server-cli deploy [email protected] -w build/libs/Artemis-7.6.2.war
./artemis-server-cli deploy [email protected] -w build/libs/Artemis-7.6.3.war
```

## Architecture
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ plugins {
}

group = "de.tum.cit.aet.artemis"
version = "7.6.2"
version = "7.6.3"
description = "Interactive Learning with Individual Feedback"

java {
Expand Down
5 changes: 5 additions & 0 deletions docs/admin/setup/distributed.rst
Original file line number Diff line number Diff line change
Expand Up @@ -617,8 +617,13 @@ These credentials are used to clone repositories via HTTPS. You must also add th
container-cleanup:
expiry-minutes: 5 # Time after a hanging container will automatically be removed
cleanup-schedule-minutes: 60 # Schedule for container cleanup
build-agent:
short-name: "artemis-build-agent-X" # Short name of the build agent. This should be unique for each build agent. Only lowercase letters, numbers and hyphens are allowed.
display-name: "Artemis Build Agent X" # This value is optional. If omitted, the short name will be used as display name. Display name of the build agent. This is shown in the Artemis UI.
Please note that ``artemis.continuous-integration.build-agent.short-name`` must be provided. Otherwise, the build agent will not start.

Build agents run as `Hazelcast Lite Members <https://docs.hazelcast.com/hazelcast/5.3/maintain-cluster/lite-members>`__ and require a full member, in our case a core node, to be running.
Thus, before starting a build agent make sure that at least the primary node is running. You can then add and remove build agents to the cluster as desired.

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "artemis",
"version": "7.6.2",
"version": "7.6.3",
"description": "Interactive Learning with Individual Feedback",
"private": true,
"license": "MIT",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package de.tum.cit.aet.artemis.assessment.dto;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.core.dto.SearchResultPageDTO;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record FeedbackAnalysisResponseDTO(SearchResultPageDTO<FeedbackDetailDTO> feedbackDetails, long totalItems, int totalAmountOfTasks, List<String> testCaseNames) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record FeedbackDetailDTO(long count, double relativeCount, String detailText, String testCaseName, int taskNumber) {
public record FeedbackDetailDTO(long count, double relativeCount, String detailText, String testCaseName, String taskNumber, String errorCategory) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package de.tum.cit.aet.artemis.assessment.dto;

import java.util.List;

import de.tum.cit.aet.artemis.core.dto.pageablesearch.PageableSearchDTO;

public class FeedbackPageableDTO extends PageableSearchDTO<String> {

private List<String> filterTasks;

private List<String> filterTestCases;

private String[] filterOccurrence;

private String searchTerm;

public List<String> getFilterTasks() {
return filterTasks;
}

public void setFilterTasks(List<String> filterTasks) {
this.filterTasks = filterTasks;
}

public List<String> getFilterTestCases() {
return filterTestCases;
}

public void setFilterTestCases(List<String> filterTestCases) {
this.filterTestCases = filterTestCases;
}

public String[] getFilterOccurrence() {
return filterOccurrence;
}

public void setFilterOccurrence(String[] filterOccurrence) {
this.filterOccurrence = filterOccurrence;
}

public String getSearchTerm() {
return searchTerm != null ? searchTerm : "";
}

public void setSearchTerm(String searchTerm) {
this.searchTerm = searchTerm;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@
import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotNull;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.Hibernate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;

import de.tum.cit.aet.artemis.assessment.domain.AssessmentType;
import de.tum.cit.aet.artemis.assessment.domain.Feedback;
import de.tum.cit.aet.artemis.assessment.domain.FeedbackType;
import de.tum.cit.aet.artemis.assessment.domain.LongFeedbackText;
import de.tum.cit.aet.artemis.assessment.domain.Result;
import de.tum.cit.aet.artemis.assessment.dto.FeedbackAnalysisResponseDTO;
import de.tum.cit.aet.artemis.assessment.dto.FeedbackDetailDTO;
import de.tum.cit.aet.artemis.assessment.dto.FeedbackPageableDTO;
import de.tum.cit.aet.artemis.assessment.repository.ComplaintRepository;
import de.tum.cit.aet.artemis.assessment.repository.ComplaintResponseRepository;
import de.tum.cit.aet.artemis.assessment.repository.FeedbackRepository;
Expand All @@ -40,10 +44,12 @@
import de.tum.cit.aet.artemis.buildagent.dto.ResultBuildJob;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.core.dto.SearchResultPageDTO;
import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
import de.tum.cit.aet.artemis.core.security.Role;
import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService;
import de.tum.cit.aet.artemis.core.util.PageUtil;
import de.tum.cit.aet.artemis.exam.domain.Exam;
import de.tum.cit.aet.artemis.exam.repository.StudentExamRepository;
import de.tum.cit.aet.artemis.exercise.domain.Exercise;
Expand All @@ -53,11 +59,14 @@
import de.tum.cit.aet.artemis.exercise.repository.StudentParticipationRepository;
import de.tum.cit.aet.artemis.exercise.service.ExerciseDateService;
import de.tum.cit.aet.artemis.lti.service.LtiNewResultService;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseParticipation;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseStudentParticipation;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase;
import de.tum.cit.aet.artemis.programming.domain.build.BuildPlanType;
import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTask;
import de.tum.cit.aet.artemis.programming.repository.BuildJobRepository;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseStudentParticipationRepository;
import de.tum.cit.aet.artemis.programming.repository.SolutionProgrammingExerciseParticipationRepository;
import de.tum.cit.aet.artemis.programming.repository.TemplateProgrammingExerciseParticipationRepository;
Expand Down Expand Up @@ -110,6 +119,8 @@ public class ResultService {

private final ProgrammingExerciseTaskService programmingExerciseTaskService;

private final ProgrammingExerciseRepository programmingExerciseRepository;

public ResultService(UserRepository userRepository, ResultRepository resultRepository, Optional<LtiNewResultService> ltiNewResultService,
ResultWebsocketService resultWebsocketService, ComplaintResponseRepository complaintResponseRepository, RatingRepository ratingRepository,
FeedbackRepository feedbackRepository, LongFeedbackTextRepository longFeedbackTextRepository, ComplaintRepository complaintRepository,
Expand All @@ -118,7 +129,7 @@ public ResultService(UserRepository userRepository, ResultRepository resultRepos
SolutionProgrammingExerciseParticipationRepository solutionProgrammingExerciseParticipationRepository,
ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository, StudentExamRepository studentExamRepository,
BuildJobRepository buildJobRepository, BuildLogEntryService buildLogEntryService, StudentParticipationRepository studentParticipationRepository,
ProgrammingExerciseTaskService programmingExerciseTaskService) {
ProgrammingExerciseTaskService programmingExerciseTaskService, ProgrammingExerciseRepository programmingExerciseRepository) {
this.userRepository = userRepository;
this.resultRepository = resultRepository;
this.ltiNewResultService = ltiNewResultService;
Expand All @@ -139,6 +150,7 @@ public ResultService(UserRepository userRepository, ResultRepository resultRepos
this.buildLogEntryService = buildLogEntryService;
this.studentParticipationRepository = studentParticipationRepository;
this.programmingExerciseTaskService = programmingExerciseTaskService;
this.programmingExerciseRepository = programmingExerciseRepository;
}

/**
Expand Down Expand Up @@ -530,31 +542,85 @@ private Result shouldSaveResult(@NotNull Result result, boolean shouldSave) {
}

/**
* Retrieves aggregated feedback details for a given exercise, calculating relative counts based on the total number of distinct results.
* The task numbers are assigned based on the associated test case names, using the set of tasks fetched from the database.
* Retrieves paginated and filtered aggregated feedback details for a given exercise.
* <br>
* For each feedback detail:
* 1. The relative count is calculated as a percentage of the total number of distinct results for the exercise.
* 2. The task number is determined by matching the test case name with the tasks.
* 2. The task numbers are assigned based on the associated test case names. A mapping between test cases and tasks is created using the set of tasks retrieved from the
* database.
* <br>
* Filtering:
* - **Search term**: Filters feedback details by the search term (case-insensitive).
* - **Test case names**: Filters feedback based on specific test case names (if provided).
* - **Task names**: Maps provided task numbers to task names and filters feedback based on the test cases associated with those tasks.
* - **Occurrences**: Filters feedback where the number of occurrences (COUNT) is between the provided minimum and maximum values (inclusive).
* <br>
* Pagination and sorting:
* - Sorting is applied based on the specified column and order (ascending or descending).
* - The result is paginated based on the provided page number and page size.
*
* @param exerciseId The ID of the exercise for which feedback details should be retrieved.
* @return A list of FeedbackDetailDTO objects, each containing:
* - feedback count,
* - relative count (as a percentage of distinct results),
* - detail text,
* - test case name,
* - determined task number (based on the test case name).
* @param data The {@link FeedbackPageableDTO} containing page number, page size, search term, sorting options, and filtering parameters (task names, test cases,
* occurrence range).
* @return A {@link FeedbackAnalysisResponseDTO} object containing:
* - A {@link SearchResultPageDTO} of paginated feedback details.
* - The total number of distinct results for the exercise.
* - The total number of tasks associated with the feedback.
* - A list of test case names included in the feedback.
*/
public List<FeedbackDetailDTO> findAggregatedFeedbackByExerciseId(long exerciseId) {
public FeedbackAnalysisResponseDTO getFeedbackDetailsOnPage(long exerciseId, FeedbackPageableDTO data) {

// 1. Fetch programming exercise with associated test cases
ProgrammingExercise programmingExercise = programmingExerciseRepository.findWithTestCasesByIdElseThrow(exerciseId);

long distinctResultCount = studentParticipationRepository.countDistinctResultsByExerciseId(exerciseId);
Set<ProgrammingExerciseTask> tasks = programmingExerciseTaskService.getTasksWithUnassignedTestCases(exerciseId);
List<FeedbackDetailDTO> feedbackDetails = studentParticipationRepository.findAggregatedFeedbackByExerciseId(exerciseId);

return feedbackDetails.stream().map(detail -> {
double relativeCount = (detail.count() * 100.0) / distinctResultCount;
int taskNumber = tasks.stream().filter(task -> task.getTestCases().stream().anyMatch(tc -> tc.getTestName().equals(detail.testCaseName()))).findFirst()
.map(task -> tasks.stream().toList().indexOf(task) + 1).orElse(0);
return new FeedbackDetailDTO(detail.count(), relativeCount, detail.detailText(), detail.testCaseName(), taskNumber);

// 2. Extract test case names using streams
List<String> testCaseNames = programmingExercise.getTestCases().stream().map(ProgrammingExerciseTestCase::getTestName).toList();

List<ProgrammingExerciseTask> tasks = programmingExerciseTaskService.getTasksWithUnassignedTestCases(exerciseId);

// 3. Generate filter task names directly
List<String> filterTaskNames = data.getFilterTasks().stream().map(index -> {
int idx = Integer.parseInt(index);
return (idx > 0 && idx <= tasks.size()) ? tasks.get(idx - 1).getTaskName() : null;
}).filter(Objects::nonNull).toList();

// 4. Set minOccurrence and maxOccurrence based on filterOccurrence
long minOccurrence = data.getFilterOccurrence().length == 2 ? Long.parseLong(data.getFilterOccurrence()[0]) : 0;
long maxOccurrence = data.getFilterOccurrence().length == 2 ? Long.parseLong(data.getFilterOccurrence()[1]) : Integer.MAX_VALUE;

// 5. Create pageable object for pagination
final var pageable = PageUtil.createDefaultPageRequest(data, PageUtil.ColumnMapping.FEEDBACK_ANALYSIS);

// 6. Fetch filtered feedback from the repository
final Page<FeedbackDetailDTO> feedbackDetailPage = studentParticipationRepository.findFilteredFeedbackByExerciseId(exerciseId,
StringUtils.isBlank(data.getSearchTerm()) ? "" : data.getSearchTerm().toLowerCase(), data.getFilterTestCases(), filterTaskNames, minOccurrence, maxOccurrence,
pageable);

// 7. Process feedback details
// Map to index (+1 for 1-based indexing)
List<FeedbackDetailDTO> processedDetails = feedbackDetailPage.getContent().stream().map(detail -> {
String taskIndex = tasks.stream().filter(task -> task.getTaskName().equals(detail.taskNumber())).findFirst().map(task -> String.valueOf(tasks.indexOf(task) + 1))
.orElse("0");
return new FeedbackDetailDTO(detail.count(), (detail.count() * 100.00) / distinctResultCount, detail.detailText(), detail.testCaseName(), taskIndex, "StudentError");
}).toList();

// 8. Return the response DTO containing feedback details, total elements, and test case/task info
return new FeedbackAnalysisResponseDTO(new SearchResultPageDTO<>(processedDetails, feedbackDetailPage.getTotalPages()), feedbackDetailPage.getTotalElements(), tasks.size(),
testCaseNames);
}

/**
* Retrieves the maximum feedback count for a given exercise.
* <br>
* This method calls the repository to fetch the maximum number of feedback occurrences across all feedback items for a specific exercise.
* This is used for filtering feedback based on the number of occurrences.
*
* @param exerciseId The ID of the exercise for which the maximum feedback count is to be retrieved.
* @return The maximum count of feedback occurrences for the given exercise.
*/
public long getMaxCountForExercise(long exerciseId) {
return studentParticipationRepository.findMaxCountForExercise(exerciseId);
}
}
Loading

0 comments on commit b910eed

Please sign in to comment.