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

Programming exercises: Enhance filtering and sorting for error analysis #9315

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
0b1023e
first approach
az108 Sep 13, 2024
e14b18c
first working valid approach
az108 Sep 13, 2024
8e4c046
final approach and server side cleaned with server side tests adapted
az108 Sep 13, 2024
bd0ff93
cleaned client side
az108 Sep 13, 2024
5a4800c
cleaned client side and added new client tests
az108 Sep 13, 2024
0d087aa
changed server side naming
az108 Sep 13, 2024
ec9fe4d
fixed i18n files
az108 Sep 13, 2024
e7d16c7
Merge branch 'develop' into feature/programming-exercises/refactor-an…
az108 Sep 13, 2024
289d2ea
coderabbit and fixed client and server style
az108 Sep 14, 2024
f03def0
Merge branch 'develop' into feature/programming-exercises/refactor-an…
az108 Sep 14, 2024
c4b5503
Patrik feedback
az108 Sep 15, 2024
e6f41e1
Merge remote-tracking branch 'origin/feature/programming-exercises/re…
az108 Sep 15, 2024
b6ad90e
Patrik feedback
az108 Sep 16, 2024
4592cfe
Patrik feedback
az108 Sep 16, 2024
afffba3
Patrik feedback
az108 Sep 16, 2024
e0df0a8
Patrik feedback
az108 Sep 16, 2024
0b6c5fa
Patrik feedback
az108 Sep 16, 2024
4547ae5
Merge branch 'develop' into feature/programming-exercises/refactor-an…
az108 Sep 16, 2024
5a00250
Johannes feedback
az108 Sep 16, 2024
46270e9
Merge remote-tracking branch 'origin/feature/programming-exercises/re…
az108 Sep 16, 2024
fc1f68f
client test fixed
az108 Sep 16, 2024
06b0c5e
sorting removed
az108 Sep 24, 2024
c68a23e
using Page now
az108 Sep 26, 2024
3b0a4f1
working slider for modal
az108 Sep 30, 2024
857f8ab
working slider for modal
az108 Sep 30, 2024
89b2434
single option filter working
az108 Oct 1, 2024
c532903
working modal
az108 Oct 2, 2024
9e00651
added header sorting for occurrence and feedback again
az108 Oct 2, 2024
dcdacc0
Merge remote-tracking branch 'origin/develop' into feature/programmin…
az108 Oct 2, 2024
80df1c6
partially working
az108 Oct 6, 2024
02ce3cf
working but clean up missing
az108 Oct 6, 2024
0f83c13
Merge remote-tracking branch 'origin/develop' into feature/programmin…
az108 Oct 6, 2024
b1e1f23
seperated maxCount call
az108 Oct 6, 2024
b44caca
occurrence slider adjustments
az108 Oct 6, 2024
f13e75a
server tests adjusted and added
az108 Oct 6, 2024
b94ed3b
client side code adjusted
az108 Oct 6, 2024
3c479bd
client side tests added / adjusted
az108 Oct 6, 2024
02f7709
client side tests added / adjusted and coderabbit
az108 Oct 7, 2024
713c060
server style
az108 Oct 7, 2024
f74a15d
Merge branch 'develop' into feature/programming-exercises/refactor-an…
az108 Oct 7, 2024
3531d70
server style
az108 Oct 7, 2024
a4af219
Merge remote-tracking branch 'origin/feature/programming-exercises/re…
az108 Oct 7, 2024
0ca27db
client code coverage
az108 Oct 7, 2024
45f04b1
client test fixed
az108 Oct 7, 2024
38a97f6
client test adjusted
az108 Oct 7, 2024
d91b2a5
range slider adjusted
az108 Oct 7, 2024
6f2f989
range slider adjusted
az108 Oct 7, 2024
6275926
more request changes handled
az108 Oct 8, 2024
5808bf9
more request changes handled
az108 Oct 8, 2024
691fff2
client test adjusted
az108 Oct 8, 2024
2898348
Merge remote-tracking branch 'origin/develop' into feature/programmin…
az108 Oct 8, 2024
c6a55b7
server style adjusted
az108 Oct 8, 2024
cdb6442
server style adjusted
az108 Oct 8, 2024
23f045d
server tests adjusted
az108 Oct 8, 2024
098e8f7
fixed small % _ interaction in searchterm
az108 Oct 9, 2024
34d6fe6
rounded relative count for modal
az108 Oct 9, 2024
e8dd9d3
Merge branch 'develop' into feature/programming-exercises/refactor-an…
az108 Oct 15, 2024
a3de668
ramona feedback
az108 Oct 18, 2024
5ca3241
removed method call from template
az108 Oct 19, 2024
5fb62da
removed form group
az108 Oct 19, 2024
3ca814e
removed form group
az108 Oct 19, 2024
400cc2c
Merge remote-tracking branch 'origin/feature/programming-exercises/re…
az108 Oct 19, 2024
ba080f5
Merge branch 'develop' into feature/programming-exercises/refactor-an…
az108 Oct 19, 2024
4f6d62c
fixed client test
az108 Oct 19, 2024
9660f87
Merge remote-tracking branch 'origin/feature/programming-exercises/re…
az108 Oct 19, 2024
92aa0cd
fixed client test
az108 Oct 19, 2024
2426e0d
fixed client test
az108 Oct 19, 2024
c06a81b
Merge branch 'develop' into feature/programming-exercises/refactor-an…
krusche Oct 19, 2024
b23693d
stephan feedback
az108 Oct 19, 2024
573572a
Merge remote-tracking branch 'origin/feature/programming-exercises/re…
az108 Oct 19, 2024
634c3f3
stephan feedback
az108 Oct 19, 2024
71cd81f
Merge branch 'develop' into feature/programming-exercises/refactor-an…
az108 Oct 19, 2024
5e0076b
comments impproved patrik
az108 Oct 21, 2024
6e20397
Merge remote-tracking branch 'origin/feature/programming-exercises/re…
az108 Oct 21, 2024
9d879c5
Merge branch 'develop' into feature/programming-exercises/refactor-an…
az108 Oct 21, 2024
4bda1ef
Merge remote-tracking branch 'origin/develop' into feature/programmin…
az108 Oct 22, 2024
082343a
merge conflict
az108 Oct 22, 2024
c9182c8
Merge remote-tracking branch 'origin/feature/programming-exercises/re…
az108 Oct 22, 2024
8c97aae
merge conflict
az108 Oct 22, 2024
ef82bff
Merge branch 'develop' into feature/programming-exercises/refactor-an…
az108 Oct 23, 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
@@ -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) {
az108 marked this conversation as resolved.
Show resolved Hide resolved
}
az108 marked this conversation as resolved.
Show resolved Hide resolved
az108 marked this conversation as resolved.
Show resolved Hide resolved
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) {
az108 marked this conversation as resolved.
Show resolved Hide resolved
az108 marked this conversation as resolved.
Show resolved Hide resolved
}
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;
}
az108 marked this conversation as resolved.
Show resolved Hide resolved

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;
az108 marked this conversation as resolved.
Show resolved Hide resolved
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;

az108 marked this conversation as resolved.
Show resolved Hide resolved
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();
az108 marked this conversation as resolved.
Show resolved Hide resolved

// 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);
az108 marked this conversation as resolved.
Show resolved Hide resolved

// 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");
az108 marked this conversation as resolved.
Show resolved Hide resolved
az108 marked this conversation as resolved.
Show resolved Hide resolved
}).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(),
az108 marked this conversation as resolved.
Show resolved Hide resolved
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);
az108 marked this conversation as resolved.
Show resolved Hide resolved
}
az108 marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading