Skip to content

Commit

Permalink
Programming exercises: Add group feedback feature to feedback analysi…
Browse files Browse the repository at this point in the history
…s table (#9884)
  • Loading branch information
az108 authored Dec 20, 2024
1 parent 88c9be6 commit 8fafdee
Show file tree
Hide file tree
Showing 27 changed files with 529 additions and 462 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.tum.cit.aet.artemis.assessment.dto;

public record FeedbackAffectedStudentDTO(long courseId, long participationId, String firstName, String lastName, String login, String repositoryURI) {
public record FeedbackAffectedStudentDTO(long participationId, String firstName, String lastName, String login, String repositoryURI) {
}
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 highestOccurrenceOfGroupedFeedback) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
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(List<Long> feedbackIds, long count, double relativeCount, List<String> detailTexts, String testCaseName, String taskName, String errorCategory) {

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(String feedbackId, long count, double relativeCount, String detailText, String testCaseName, String taskName, String errorCategory) {
// Feedback IDs are gathered in the query using a comma separator, and the detail texts are stored in a list because, in case aggregation is applied, the detail texts are
// grouped together
this(Arrays.stream(feedbackId.split(",")).map(Long::valueOf).toList(), count, relativeCount, List.of(detailText), testCaseName, taskName, errorCategory);
}

}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand All @@ -23,7 +24,6 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -39,7 +39,6 @@
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.pageablesearch.PageableSearchDTO;
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;
Expand Down Expand Up @@ -297,7 +296,7 @@ public ResponseEntity<Result> createResultForExternalSubmission(@PathVariable Lo
* Pagination, sorting, and filtering options allow flexible data retrieval:
* <ul>
* <li><b>Pagination:</b> Based on page number and page size, as specified in the request.</li>
* <li><b>Sorting:</b> By column (e.g., "count" or "detailText") and sorting order (ASCENDING or DESCENDING).
* <li><b>Sorting:</b> By column (e.g., "count" or "detailTexts") and sorting order (ASCENDING or DESCENDING).
* If the specified column is not valid for sorting, the default sorting column is "count".</li>
* <li><b>Filtering:</b>
* <ul>
Expand All @@ -310,18 +309,19 @@ public ResponseEntity<Result> createResultForExternalSubmission(@PathVariable Lo
* </li>
* </ul>
*
* @param exerciseId The unique identifier of the exercise for which feedback details are requested.
* @param data A {@link FeedbackPageableDTO} object containing pagination, sorting, and filtering parameters, including:
* <ul>
* <li>Page number and page size</li>
* <li>Search term (optional)</li>
* <li>Sorting order (ASCENDING or DESCENDING)</li>
* <li>Sorted column</li>
* <li>Filter task names (optional)</li>
* <li>Filter test case names (optional)</li>
* <li>Occurrence range (optional)</li>
* <li>Error categories (optional)</li>
* </ul>
* @param exerciseId The unique identifier of the exercise for which feedback details are requested.
* @param groupFeedback Should the feedback be grouped
* @param data A {@link FeedbackPageableDTO} object containing pagination, sorting, and filtering parameters, including:
* <ul>
* <li>Page number and page size</li>
* <li>Search term (optional)</li>
* <li>Sorting order (ASCENDING or DESCENDING)</li>
* <li>Sorted column</li>
* <li>Filter task names (optional)</li>
* <li>Filter test case names (optional)</li>
* <li>Occurrence range (optional)</li>
* <li>Error categories (optional)</li>
* </ul>
* @return A {@link ResponseEntity} containing a {@link FeedbackAnalysisResponseDTO}, which includes:
* <ul>
* <li>{@link SearchResultPageDTO < FeedbackDetailDTO >} feedbackDetails: Paginated and filtered feedback details for the exercise.</li>
Expand All @@ -333,8 +333,9 @@ public ResponseEntity<Result> createResultForExternalSubmission(@PathVariable Lo
*/
@GetMapping("exercises/{exerciseId}/feedback-details")
@EnforceAtLeastEditorInExercise
public ResponseEntity<FeedbackAnalysisResponseDTO> getFeedbackDetailsPaged(@PathVariable long exerciseId, @ModelAttribute FeedbackPageableDTO data) {
FeedbackAnalysisResponseDTO response = resultService.getFeedbackDetailsOnPage(exerciseId, data);
public ResponseEntity<FeedbackAnalysisResponseDTO> getFeedbackDetailsPaged(@PathVariable long exerciseId, @RequestParam("groupFeedback") boolean groupFeedback,
@ModelAttribute FeedbackPageableDTO data) {
FeedbackAnalysisResponseDTO response = resultService.getFeedbackDetailsOnPage(exerciseId, data, groupFeedback);
return ResponseEntity.ok(response);
}

Expand All @@ -359,32 +360,24 @@ public ResponseEntity<Long> getMaxCount(@PathVariable long exerciseId) {
* and participation details.
* <br>
*
* @param exerciseId for which the participation data is requested.
* @param feedbackIdsHeader to filter affected students by specific feedback entries.
* @param data A {@link PageableSearchDTO} object containing pagination and sorting parameters.
* @return A {@link ResponseEntity} containing a {@link Page} of {@link FeedbackAffectedStudentDTO}, each representing a student affected by the feedback entries.
* @param exerciseId for which the participation data is requested.
* @param feedbackId1 Optional first detail text id to filter affected students by specific feedback entries.
* @param feedbackId2 Optional second detail text id to filter affected students by specific feedback entries.
* @param feedbackId3 Optional third detail text id to filter affected students by specific feedback entries.
* @param feedbackId4 Optional fourth detail text id to filter affected students by specific feedback entries.
* @param feedbackId5 Optional fifth detail text id to filter affected students by specific feedback entries.
* @return A {@link ResponseEntity} containing a {@link List} of {@link FeedbackAffectedStudentDTO}, each representing a student affected by the feedback entries.
*/
@GetMapping("exercises/{exerciseId}/feedback-details-participation")
@EnforceAtLeastEditorInExercise
public ResponseEntity<Page<FeedbackAffectedStudentDTO>> getAffectedStudentsWithFeedback(@PathVariable long exerciseId, @RequestHeader("feedbackIds") String feedbackIdsHeader,
@ModelAttribute PageableSearchDTO<String> data) {
public ResponseEntity<List<FeedbackAffectedStudentDTO>> getAffectedStudentsWithFeedback(@PathVariable long exerciseId,
@RequestParam(value = "feedbackId1", required = false) Long feedbackId1, @RequestParam(value = "feedbackId2", required = false) Long feedbackId2,
@RequestParam(value = "feedbackId3", required = false) Long feedbackId3, @RequestParam(value = "feedbackId4", required = false) Long feedbackId4,
@RequestParam(value = "feedbackId5", required = false) Long feedbackId5) {

Page<FeedbackAffectedStudentDTO> participation = resultService.getAffectedStudentsWithFeedbackId(exerciseId, feedbackIdsHeader, data);
List<Long> feedbackIds = Stream.of(feedbackId1, feedbackId2, feedbackId3, feedbackId4, feedbackId5).filter(Objects::nonNull).toList();

List<FeedbackAffectedStudentDTO> participation = resultService.getAffectedStudentsWithFeedbackIds(exerciseId, feedbackIds);
return ResponseEntity.ok(participation);
}

/**
* GET /exercises/{exerciseId}/feedback-detail/affected-students : Retrieves the count of students affected by a specific feedback detail text.
*
* @param exerciseId The ID of the exercise for which affected students are counted.
* @param detailText The feedback detail text to filter by.
* @return A {@link ResponseEntity} containing the count of affected students.
*/
@GetMapping("exercises/{exerciseId}/feedback-detail/affected-students")
@EnforceAtLeastEditorInExercise
public ResponseEntity<Long> countAffectedStudentsByFeedbackDetailText(@PathVariable long exerciseId, @RequestParam("detailText") String detailText) {
long affectedStudentCount = resultService.getAffectedStudentCountByFeedbackDetailText(exerciseId, detailText);
return ResponseEntity.ok(affectedStudentCount);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package de.tum.cit.aet.artemis.communication.dto;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record FeedbackChannelRequestDTO(ChannelDTO channel, String feedbackDetailText) {
public record FeedbackChannelRequestDTO(ChannelDTO channel, List<String> feedbackDetailTexts, String testCaseName) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -418,15 +418,16 @@ private static String generateChannelNameFromTitle(@NotNull String prefix, Optio
/**
* Creates a feedback-specific channel for an exercise within a course.
*
* @param course in which the channel is being created.
* @param exerciseId of the exercise associated with the feedback channel.
* @param channelDTO containing the properties of the channel to be created, such as name, description, and visibility.
* @param feedbackDetailText used to identify the students affected by the feedback.
* @param requestingUser initiating the channel creation request.
* @param course in which the channel is being created.
* @param exerciseId of the exercise associated with the feedback channel.
* @param channelDTO containing the properties of the channel to be created, such as name, description, and visibility.
* @param feedbackDetailTexts used to identify the students affected by the feedback.
* @param requestingUser initiating the channel creation request.
* @param testCaseName to filter student submissions according to a specific feedback
* @return the created {@link Channel} object with its properties.
* @throws BadRequestAlertException if the channel name starts with an invalid prefix (e.g., "$").
*/
public Channel createFeedbackChannel(Course course, Long exerciseId, ChannelDTO channelDTO, String feedbackDetailText, User requestingUser) {
public Channel createFeedbackChannel(Course course, Long exerciseId, ChannelDTO channelDTO, List<String> feedbackDetailTexts, String testCaseName, User requestingUser) {
Channel channelToCreate = new Channel();
channelToCreate.setName(channelDTO.getName());
channelToCreate.setIsPublic(channelDTO.getIsPublic());
Expand All @@ -440,7 +441,7 @@ public Channel createFeedbackChannel(Course course, Long exerciseId, ChannelDTO

Channel createdChannel = createChannel(course, channelToCreate, Optional.of(requestingUser));

List<String> userLogins = studentParticipationRepository.findAffectedLoginsByFeedbackDetailText(exerciseId, feedbackDetailText);
List<String> userLogins = studentParticipationRepository.findAffectedLoginsByFeedbackDetailText(exerciseId, feedbackDetailTexts, testCaseName);

if (userLogins != null && !userLogins.isEmpty()) {
var registeredUsers = registerUsersToChannel(false, false, false, userLogins, course, createdChannel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,12 +486,13 @@ public ResponseEntity<ChannelDTO> createFeedbackChannel(@PathVariable Long cours
log.debug("REST request to create feedback channel for course {} and exercise {} with properties: {}", courseId, exerciseId, feedbackChannelRequest);

ChannelDTO channelDTO = feedbackChannelRequest.channel();
String feedbackDetailText = feedbackChannelRequest.feedbackDetailText();
List<String> feedbackDetailTexts = feedbackChannelRequest.feedbackDetailTexts();
String testCaseName = feedbackChannelRequest.testCaseName();

User requestingUser = userRepository.getUserWithGroupsAndAuthorities();
Course course = courseRepository.findByIdElseThrow(courseId);
checkCommunicationEnabledElseThrow(course);
Channel createdChannel = channelService.createFeedbackChannel(course, exerciseId, channelDTO, feedbackDetailText, requestingUser);
Channel createdChannel = channelService.createFeedbackChannel(course, exerciseId, channelDTO, feedbackDetailTexts, testCaseName, requestingUser);

return ResponseEntity.created(new URI("/api/channels/" + createdChannel.getId())).body(conversationDTOService.convertChannelToDTO(requestingUser, createdChannel));
}
Expand Down
5 changes: 1 addition & 4 deletions src/main/java/de/tum/cit/aet/artemis/core/util/PageUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public enum ColumnMapping {
)),
FEEDBACK_ANALYSIS(Map.of(
"count", "COUNT(f.id)",
"detailText", "f.detailText",
"detailTexts", "f.detailText",
"testCaseName", "f.testCase.testName",
"taskName", """
COALESCE((
Expand All @@ -82,9 +82,6 @@ SELECT MAX(t.taskName)
JOIN t.testCases tct
WHERE t.exercise.id = :exerciseId AND tct.testName = f.testCase.testName
), '')"""
)),
AFFECTED_STUDENTS(Map.of(
"participationId", "p.id"
));
// @formatter:on

Expand Down
Loading

0 comments on commit 8fafdee

Please sign in to comment.