diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java index 1f994918ffac..7f9942cc4dba 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseCodeReviewFeedbackService.java @@ -12,7 +12,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; @@ -60,9 +59,6 @@ public class ProgrammingExerciseCodeReviewFeedbackService { private final ProgrammingMessagingService programmingMessagingService; - @Value("${artemis.athena.allowed-feedback-attempts:20}") - private int allowedFeedbackAttempts; - public ProgrammingExerciseCodeReviewFeedbackService(GroupNotificationService groupNotificationService, Optional athenaFeedbackSuggestionsService, SubmissionService submissionService, ResultService resultService, ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository, ResultRepository resultRepository, diff --git a/src/main/webapp/app/exercises/shared/exercise-headers/header-participation-page.component.html b/src/main/webapp/app/exercises/shared/exercise-headers/header-participation-page.component.html index af31df28d183..69656c28f0e2 100644 --- a/src/main/webapp/app/exercises/shared/exercise-headers/header-participation-page.component.html +++ b/src/main/webapp/app/exercises/shared/exercise-headers/header-participation-page.component.html @@ -66,7 +66,7 @@
{{ 'artemisApp.exercise.assessmentDueDate' | artemisTransl } -
+
diff --git a/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.html b/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.html index 8becafe3e2a9..5fe67ccf8ee2 100644 --- a/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.html +++ b/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.html @@ -11,6 +11,13 @@ > + + {{ this.currentFeedbackRequestCount }} / {{ this.feedbackRequestLimit }} + } @else { + + {{ this.currentFeedbackRequestCount }} / {{ this.feedbackRequestLimit }} + } } @else { diff --git a/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.ts b/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.ts index 2262fa569da5..1519eafbb997 100644 --- a/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.ts +++ b/src/main/webapp/app/overview/exercise-details/request-feedback-button/request-feedback-button.component.ts @@ -1,5 +1,5 @@ -import { Component, OnInit, inject, input, output } from '@angular/core'; - +import { Component, OnDestroy, OnInit, inject, input, output } from '@angular/core'; +import { Subscription, filter, skip } from 'rxjs'; import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { faPenSquare } from '@fortawesome/free-solid-svg-icons'; @@ -15,18 +15,23 @@ import { isExamExercise } from 'app/shared/util/utils'; import { ExerciseDetailsType, ExerciseService } from 'app/exercises/shared/exercise/exercise.service'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { ParticipationService } from 'app/exercises/shared/participation/participation.service'; +import { AssessmentType } from 'app/entities/assessment-type.model'; +import { ParticipationWebsocketService } from 'app/overview/participation-websocket.service'; +import { Result } from 'app/entities/result.model'; @Component({ selector: 'jhi-request-feedback-button', imports: [ArtemisSharedCommonModule, NgbTooltipModule, FontAwesomeModule], templateUrl: './request-feedback-button.component.html', }) -export class RequestFeedbackButtonComponent implements OnInit { +export class RequestFeedbackButtonComponent implements OnInit, OnDestroy { faPenSquare = faPenSquare; athenaEnabled = false; requestFeedbackEnabled = false; isExamExercise: boolean; participation?: StudentParticipation; + currentFeedbackRequestCount = 0; + feedbackRequestLimit = 10; // remark: this will be defined by the instructor and fetched isSubmitted = input(); pendingChanges = input(false); @@ -42,6 +47,9 @@ export class RequestFeedbackButtonComponent implements OnInit { private translateService = inject(TranslateService); private exerciseService = inject(ExerciseService); private participationService = inject(ParticipationService); + private participationWebsocketService = inject(ParticipationWebsocketService); + + private athenaResultUpdateListener?: Subscription; protected readonly ExerciseType = ExerciseType; @@ -56,12 +64,20 @@ export class RequestFeedbackButtonComponent implements OnInit { this.requestFeedbackEnabled = this.exercise().allowFeedbackRequests ?? false; this.updateParticipation(); } + ngOnDestroy(): void { + this.athenaResultUpdateListener?.unsubscribe(); + } private updateParticipation() { if (this.exercise().id) { this.exerciseService.getExerciseDetails(this.exercise().id!).subscribe({ next: (exerciseResponse: HttpResponse) => { this.participation = this.participationService.getSpecificStudentParticipation(exerciseResponse.body!.exercise.studentParticipations ?? [], false); + if (this.participation) { + this.currentFeedbackRequestCount = + this.participation.results?.filter((result) => result.assessmentType == AssessmentType.AUTOMATIC_ATHENA && result.successful == true).length ?? 0; + this.subscribeToResultUpdates(); + } }, error: (error: HttpErrorResponse) => { this.alertService.error(`artemisApp.${error.error.entityName}.errors.${error.error.errorKey}`); @@ -70,6 +86,28 @@ export class RequestFeedbackButtonComponent implements OnInit { } } + private subscribeToResultUpdates() { + if (!this.participation?.id) { + return; + } + + // Subscribe to result updates for this participation + this.athenaResultUpdateListener = this.participationWebsocketService + .subscribeForLatestResultOfParticipation(this.participation.id, true) + .pipe( + skip(1), // Skip initial value + filter((result): result is Result => !!result), + filter((result) => result.assessmentType === AssessmentType.AUTOMATIC_ATHENA), + ) + .subscribe(this.handleAthenaAssessment.bind(this)); + } + + private handleAthenaAssessment(result: Result) { + if (result.completionDate && result.successful) { + this.currentFeedbackRequestCount += 1; + } + } + requestFeedback() { if (!this.assureConditionsSatisfied()) { return;