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: Add group feedback feature to feedback analysis table #9884

Open
wants to merge 60 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
2aef101
added tests and documentation
az108 Nov 10, 2024
862e9b0
fix
az108 Nov 10, 2024
0fdcfe9
Merge remote-tracking branch 'origin/develop' into feature/programmin…
az108 Nov 11, 2024
8fb7be1
johannes feedback
az108 Nov 11, 2024
ae13615
johannes feedback
az108 Nov 11, 2024
15d3fba
johannes feedback
az108 Nov 11, 2024
7f32c6f
tests fix
az108 Nov 11, 2024
3703ca0
johannes feedback
az108 Nov 11, 2024
3b32032
bilel feedback
az108 Nov 12, 2024
f0b99a3
florian feedback
az108 Nov 12, 2024
4d5a609
client test feedback
az108 Nov 12, 2024
cfd9442
client test feedback
az108 Nov 12, 2024
30ebcb7
client test feedback
az108 Nov 12, 2024
dcfd0a8
Merge branch 'develop' into feature/programming-exercises/add-affecte…
az108 Nov 12, 2024
72b482e
client test
az108 Nov 12, 2024
06c2462
Merge remote-tracking branch 'origin/feature/programming-exercises/ad…
az108 Nov 12, 2024
be28769
ramona feedback
az108 Nov 14, 2024
fe3cb3b
ramona feedback
az108 Nov 14, 2024
3dee150
Merge branch 'develop' into feature/programming-exercises/add-affecte…
az108 Nov 14, 2024
4594289
ramona feedback
az108 Nov 14, 2024
6fa20f9
Merge remote-tracking branch 'origin/feature/programming-exercises/ad…
az108 Nov 14, 2024
7d1b039
server style
az108 Nov 14, 2024
a8f17b7
communication feature added
az108 Nov 16, 2024
bee159f
Merge remote-tracking branch 'origin/develop' into feature/programmin…
az108 Nov 16, 2024
8795e5e
tests added
az108 Nov 17, 2024
60b0ff3
Merge remote-tracking branch 'origin/develop' into feature/programmin…
az108 Nov 17, 2024
45835c4
added translation field
az108 Nov 17, 2024
71a4cdb
server style
az108 Nov 17, 2024
31fa70e
removed functionality for non communication courses
az108 Nov 18, 2024
319b1ca
added missing translation and testfix
az108 Nov 18, 2024
7233d3c
flo feedback
az108 Nov 19, 2024
5d944d7
flo feedback
az108 Nov 19, 2024
bf0611e
flo feedback
az108 Nov 19, 2024
edd9ffd
Merge branch 'develop' into feature/programming-exercises/add-communi…
az108 Nov 19, 2024
ec4dc71
server style
az108 Nov 19, 2024
ffb4ded
Merge remote-tracking branch 'origin/feature/programming-exercises/ad…
az108 Nov 19, 2024
32cf023
markus feedback
az108 Nov 19, 2024
23988ca
Merge branch 'develop' into feature/programming-exercises/add-communi…
az108 Nov 19, 2024
fdbc64c
ramona feedback
az108 Nov 25, 2024
8f81d0a
Merge remote-tracking branch 'origin/feature/programming-exercises/ad…
az108 Nov 25, 2024
d24f50f
Merge branch 'develop' into feature/programming-exercises/add-communi…
az108 Nov 25, 2024
72e849e
translation
az108 Nov 25, 2024
4847f02
Merge remote-tracking branch 'origin/feature/programming-exercises/ad…
az108 Nov 25, 2024
551c964
refactored
az108 Nov 25, 2024
34a7805
levinstein example
az108 Nov 25, 2024
fc5546e
levinstein example
az108 Nov 26, 2024
0153fbd
levinstein added
az108 Nov 27, 2024
332a8fc
Merge remote-tracking branch 'origin/develop' into feature/programmin…
az108 Nov 27, 2024
53d4032
tests
az108 Nov 27, 2024
dbcf03d
change implementation of detailtext to string
az108 Nov 30, 2024
0ea5d26
implementation first draft with levenshtein
az108 Nov 30, 2024
2c559fc
implementation levenshtein
az108 Nov 30, 2024
83c986e
icon colour updated
az108 Nov 30, 2024
deed6fa
Merge branch 'develop' into feature/programming-exercises/add-groupin…
az108 Nov 30, 2024
1702e7a
documentation update
az108 Nov 30, 2024
a1f60af
Merge remote-tracking branch 'origin/feature/programming-exercises/ad…
az108 Nov 30, 2024
cdf3f38
studentparticipation merged
az108 Nov 30, 2024
873f880
studentparticipation merged
az108 Nov 30, 2024
ee7e9a1
raffi feedback
az108 Nov 30, 2024
025cd07
Merge branch 'develop' into feature/programming-exercises/add-groupin…
az108 Nov 30, 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 @@ -9,5 +9,5 @@

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record FeedbackAnalysisResponseDTO(SearchResultPageDTO<FeedbackDetailDTO> feedbackDetails, long totalItems, Set<String> taskNames, List<String> testCaseNames,
List<String> errorCategories) {
List<String> errorCategories, long levenshteinMaxCount) {
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package de.tum.cit.aet.artemis.assessment.dto;

import java.util.Arrays;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record FeedbackDetailDTO(List<Long> concatenatedFeedbackIds, long count, double relativeCount, String detailText, String testCaseName, String taskName,
String errorCategory) {
public record FeedbackDetailDTO(long count, double relativeCount, List<String> detailText, String testCaseName, String taskName, String errorCategory) {
az108 marked this conversation as resolved.
Show resolved Hide resolved

public FeedbackDetailDTO(String concatenatedFeedbackIds, long count, double relativeCount, String detailText, String testCaseName, String taskName, String errorCategory) {
this(Arrays.stream(concatenatedFeedbackIds.split(",")).map(Long::valueOf).toList(), count, relativeCount, detailText, testCaseName, taskName, errorCategory);
public FeedbackDetailDTO(long count, double relativeCount, String detailText, String testCaseName, String taskName, String errorCategory) {
this(count, relativeCount, List.of(detailText), testCaseName, taskName, errorCategory);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -26,6 +26,7 @@
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import de.tum.cit.aet.artemis.assessment.domain.AssessmentType;
Expand All @@ -49,6 +50,7 @@
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.dto.SortingOrder;
import de.tum.cit.aet.artemis.core.dto.pageablesearch.PageableSearchDTO;
import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
Expand All @@ -64,6 +66,7 @@
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.modeling.service.compass.strategy.NameSimilarity;
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;
Expand Down Expand Up @@ -570,18 +573,20 @@ private Result shouldSaveResult(@NotNull Result result, boolean shouldSave) {
* Pagination and sorting:
* - Sorting is applied based on the specified column and order (ascending or descending).
* - The result is paginated according to the provided page number and page size.
* Additionally one can group by the Levenshtein distance of the feedback detail text.
*
* @param exerciseId The ID of the exercise for which feedback details should be retrieved.
* @param data The {@link FeedbackPageableDTO} containing page number, page size, search term, sorting options, and filtering parameters
* (task names, test cases, occurrence range, error categories).
* @param exerciseId The ID of the exercise for which feedback details should be retrieved.
* @param data The {@link FeedbackPageableDTO} containing page number, page size, search term, sorting options, and filtering parameters
* (task names, test cases, occurrence range, error categories).
* @param levenshtein The flag to enable Levenshtein-based grouping and aggregation of feedback details.
* @return A {@link FeedbackAnalysisResponseDTO} object containing:
* - A {@link SearchResultPageDTO} of paginated feedback details.
* - The total number of distinct results for the exercise.
* - A set of task names, including "Not assigned to a task" if applicable.
* - A list of active test case names used in the feedback.
* - A list of predefined error categories ("Student Error," "Ares Error," "AST Error") available for filtering.
*/
public FeedbackAnalysisResponseDTO getFeedbackDetailsOnPage(long exerciseId, FeedbackPageableDTO data) {
public FeedbackAnalysisResponseDTO getFeedbackDetailsOnPage(long exerciseId, FeedbackPageableDTO data, String levenshtein) {
az108 marked this conversation as resolved.
Show resolved Hide resolved
az108 marked this conversation as resolved.
Show resolved Hide resolved

// 1. Fetch programming exercise with associated test cases
ProgrammingExercise programmingExercise = programmingExerciseRepository.findWithTestCasesByIdElseThrow(exerciseId);
Expand Down Expand Up @@ -616,20 +621,106 @@ public FeedbackAnalysisResponseDTO getFeedbackDetailsOnPage(long exerciseId, Fee
// 8. Set up pagination and sorting based on input data
final var pageable = PageUtil.createDefaultPageRequest(data, PageUtil.ColumnMapping.FEEDBACK_ANALYSIS);

// 9. Query the database to retrieve paginated and filtered feedback
final Page<FeedbackDetailDTO> feedbackDetailPage = studentParticipationRepository.findFilteredFeedbackByExerciseId(exerciseId,
StringUtils.isBlank(data.getSearchTerm()) ? "" : data.getSearchTerm().toLowerCase(), data.getFilterTestCases(), includeUnassignedTasks, minOccurrence,
maxOccurrence, filterErrorCategories, pageable);
// 9. Query the database based on levenshtein attribute to retrieve paginated and filtered feedback
final Page<FeedbackDetailDTO> feedbackDetailPage;
List<FeedbackDetailDTO> processedDetails;
int totalPages = 0;
long levenshteinMaxCount = 0;
if (levenshtein.equals("false")) {
az108 marked this conversation as resolved.
Show resolved Hide resolved
feedbackDetailPage = studentParticipationRepository.findFilteredFeedbackByExerciseId(exerciseId,
StringUtils.isBlank(data.getSearchTerm()) ? "" : data.getSearchTerm().toLowerCase(), data.getFilterTestCases(), includeUnassignedTasks, minOccurrence,
maxOccurrence, filterErrorCategories, pageable);

// Process and map feedback details, calculating relative count and assigning task names
processedDetails = feedbackDetailPage.getContent().stream().map(detail -> new FeedbackDetailDTO(detail.count(), (detail.count() * 100.00) / distinctResultCount,
detail.detailText(), detail.testCaseName(), detail.taskName(), detail.errorCategory())).toList();
totalPages = feedbackDetailPage.getTotalPages();
}
else {
feedbackDetailPage = studentParticipationRepository.findFilteredFeedbackByExerciseId(exerciseId,
StringUtils.isBlank(data.getSearchTerm()) ? "" : data.getSearchTerm().toLowerCase(), data.getFilterTestCases(), includeUnassignedTasks, minOccurrence,
maxOccurrence, filterErrorCategories, Pageable.unpaged());
az108 marked this conversation as resolved.
Show resolved Hide resolved

// Fetch all feedback details
List<FeedbackDetailDTO> allFeedbackDetails = feedbackDetailPage.getContent();

// Apply Levenshtein-based grouping and aggregation with a similarity threshold of 90%
List<FeedbackDetailDTO> aggregatedFeedbackDetails = aggregateUsingLevenshtein(allFeedbackDetails, 0.9);

levenshteinMaxCount = aggregatedFeedbackDetails.stream().mapToLong(FeedbackDetailDTO::count).max().orElse(0);
// Apply manual pagination
int page = data.getPage();
int pageSize = data.getPageSize();
Comparator<FeedbackDetailDTO> comparator = getComparatorForFeedbackDetails(data);
List<FeedbackDetailDTO> processedDetailsPreSort = new ArrayList<>(aggregatedFeedbackDetails);
processedDetailsPreSort.sort(comparator);
int start = Math.max(0, (page - 1) * pageSize);
int end = Math.min(start + pageSize, processedDetailsPreSort.size());
processedDetails = processedDetailsPreSort.subList(start, end);
processedDetails = processedDetails.stream().map(detail -> new FeedbackDetailDTO(detail.count(), (detail.count() * 100.00) / distinctResultCount, detail.detailText(),
detail.testCaseName(), detail.taskName(), detail.errorCategory())).toList();
totalPages = (int) Math.ceil((double) processedDetailsPreSort.size() / pageSize);
}
az108 marked this conversation as resolved.
Show resolved Hide resolved

// 10. Process and map feedback details, calculating relative count and assigning task names
List<FeedbackDetailDTO> processedDetails = feedbackDetailPage.getContent().stream().map(detail -> new FeedbackDetailDTO(detail.concatenatedFeedbackIds(), detail.count(),
(detail.count() * 100.00) / distinctResultCount, detail.detailText(), detail.testCaseName(), detail.taskName(), detail.errorCategory())).toList();
// 11. Predefined error categories available for filtering on the client side
// 10. Predefined error categories available for filtering on the client side
final List<String> ERROR_CATEGORIES = List.of("Student Error", "Ares Error", "AST Error");

// 12. Return response containing processed feedback details, task names, active test case names, and error categories
return new FeedbackAnalysisResponseDTO(new SearchResultPageDTO<>(processedDetails, feedbackDetailPage.getTotalPages()), feedbackDetailPage.getTotalElements(), taskNames,
activeTestCaseNames, ERROR_CATEGORIES);
// 11. Return response containing processed feedback details, task names, active test case names, and error categories
return new FeedbackAnalysisResponseDTO(new SearchResultPageDTO<>(processedDetails, totalPages), feedbackDetailPage.getTotalElements(), taskNames, activeTestCaseNames,
ERROR_CATEGORIES, levenshteinMaxCount);
}

private Comparator<FeedbackDetailDTO> getComparatorForFeedbackDetails(FeedbackPageableDTO search) {
Map<String, Comparator<FeedbackDetailDTO>> comparators = Map.of("count", Comparator.comparingLong(FeedbackDetailDTO::count), "detailTexts",
Comparator.comparing(detail -> detail.detailText().isEmpty() ? "" : detail.detailText().getFirst(), // Sort by the first element of the list
String.CASE_INSENSITIVE_ORDER),
"testCaseName", Comparator.comparing(FeedbackDetailDTO::testCaseName, String.CASE_INSENSITIVE_ORDER), "taskName",
Comparator.comparing(FeedbackDetailDTO::taskName, String.CASE_INSENSITIVE_ORDER), "relativeCount", Comparator.comparingDouble(FeedbackDetailDTO::relativeCount));

Comparator<FeedbackDetailDTO> comparator = comparators.getOrDefault(search.getSortedColumn(), (a, b) -> 0);
return search.getSortingOrder() == SortingOrder.ASCENDING ? comparator : comparator.reversed();
az108 marked this conversation as resolved.
Show resolved Hide resolved
}

private List<FeedbackDetailDTO> aggregateUsingLevenshtein(List<FeedbackDetailDTO> feedbackDetails, double similarityThreshold) {
List<FeedbackDetailDTO> aggregatedList = new ArrayList<>();
Set<Integer> processedIndices = new HashSet<>();

for (int i = 0; i < feedbackDetails.size(); i++) {
if (processedIndices.contains(i)) {
continue; // Skip already aggregated feedback
}

FeedbackDetailDTO base = feedbackDetails.get(i);
List<String> aggregatedTexts = new ArrayList<>();
aggregatedTexts.add(base.detailText().getFirst());
long totalCount = base.count();

az108 marked this conversation as resolved.
Show resolved Hide resolved
for (int j = i + 1; j < feedbackDetails.size(); j++) {
if (processedIndices.contains(j)) {
continue;
}

FeedbackDetailDTO compare = feedbackDetails.get(j);

// Ensure feedbacks have the same testCaseName and taskName
if (base.testCaseName().equals(compare.testCaseName()) && base.taskName().equals(compare.taskName())) {
double similarity = NameSimilarity.levenshteinSimilarity(base.detailText().getFirst(), compare.detailText().getFirst());

if (similarity > similarityThreshold) {
// Merge the feedbacks
aggregatedTexts.add(compare.detailText().getFirst());
totalCount += compare.count();
processedIndices.add(j);
}
}
}

// Add aggregated feedback entry
aggregatedList.add(new FeedbackDetailDTO(totalCount, 0, aggregatedTexts, base.testCaseName(), base.taskName(), base.errorCategory()));
processedIndices.add(i);
}

return aggregatedList;
az108 marked this conversation as resolved.
Show resolved Hide resolved
az108 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -653,15 +744,15 @@ public long getMaxCountForExercise(long exerciseId) {
* datasets.
* <br>
*
* @param exerciseId for which the affected student participation data is requested.
* @param feedbackIds used to filter the participation to only those affected by specific feedback entries.
* @param data A {@link PageableSearchDTO} object containing pagination and sorting parameters.
* @param exerciseId for which the affected student participation data is requested.
* @param detailText used to filter the participation to only those affected by specific feedback entries.
* @param data A {@link PageableSearchDTO} object containing pagination and sorting parameters.
* @param testCaseName used to filter the participation to only those feedbacks for a specific testCaseName.
* @return A {@link Page} of {@link FeedbackAffectedStudentDTO} objects, each representing a student affected by the feedback.
*/
public Page<FeedbackAffectedStudentDTO> getAffectedStudentsWithFeedbackId(long exerciseId, String feedbackIds, PageableSearchDTO<String> data) {
List<Long> feedbackIdLongs = Arrays.stream(feedbackIds.split(",")).map(Long::valueOf).toList();
public Page<FeedbackAffectedStudentDTO> getAffectedStudentsWithFeedbackText(long exerciseId, List<String> detailText, String testCaseName, PageableSearchDTO<String> data) {
PageRequest pageRequest = PageUtil.createDefaultPageRequest(data, PageUtil.ColumnMapping.AFFECTED_STUDENTS);
return studentParticipationRepository.findAffectedStudentsByFeedbackId(exerciseId, feedbackIdLongs, pageRequest);
return studentParticipationRepository.findAffectedStudentsByFeedbackText(exerciseId, detailText, testCaseName, pageRequest);
}

/**
Expand Down Expand Up @@ -692,15 +783,4 @@ public void deleteLongFeedback(List<Feedback> feedbackList, Result result) {
List<Feedback> feedbacks = new ArrayList<>(feedbackList);
result.updateAllFeedbackItems(feedbacks, true);
}

/**
* Retrieves the number of students affected by a specific feedback detail text for a given exercise.
*
* @param exerciseId for which the affected student count is requested.
* @param detailText used to filter affected students.
* @return the total number of distinct students affected by the feedback detail text.
*/
public long getAffectedStudentCountByFeedbackDetailText(long exerciseId, String detailText) {
return studentParticipationRepository.countAffectedStudentsByFeedbackDetailText(exerciseId, detailText);
}
}
Loading
Loading