Skip to content

Commit

Permalink
Athena: Add badge to display remaining AI feedback requests (#10234)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmetsenturk authored Feb 2, 2025
1 parent 99e61e1 commit 923f16c
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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> athenaFeedbackSuggestionsService, SubmissionService submissionService, ResultService resultService,
ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository, ResultRepository resultRepository,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ <h6 class="fw-medium">{{ 'artemisApp.exercise.assessmentDueDate' | artemisTransl
<jhi-submission-result-status [exercise]="exercise" [studentParticipation]="participation" />
</div>
}
<div class="mt-2">
<div class="mt-2 d-md-flex gap-1 justify-content-end align-items-center">
<ng-content select="[submitbutton]" />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
>
<fa-icon [icon]="faPenSquare" [fixedWidth]="true" />
<span class="d-none d-md-inline" jhiTranslate="artemisApp.exerciseActions.requestAutomaticFeedback"></span>
<span
class="badge"
[class.bg-warning]="this.currentFeedbackRequestCount < this.feedbackRequestLimit"
[class.bg-danger]="this.currentFeedbackRequestCount >= this.feedbackRequestLimit"
>
{{ this.currentFeedbackRequestCount }} / {{ this.feedbackRequestLimit }}
</span>
</button>
} @else {
<a
Expand All @@ -22,6 +29,13 @@
>
<fa-icon [icon]="faPenSquare" [fixedWidth]="true" />
<span class="d-none d-md-inline" jhiTranslate="artemisApp.exerciseActions.requestAutomaticFeedback"></span>
<span
class="badge"
[class.bg-warning]="this.currentFeedbackRequestCount < this.feedbackRequestLimit"
[class.bg-danger]="this.currentFeedbackRequestCount >= this.feedbackRequestLimit"
>
{{ this.currentFeedbackRequestCount }} / {{ this.feedbackRequestLimit }}
</span>
</a>
}
} @else {
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<boolean>();
pendingChanges = input<boolean>(false);
Expand All @@ -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;

Expand All @@ -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<ExerciseDetailsType>) => {
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}`);
Expand All @@ -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;
Expand Down

0 comments on commit 923f16c

Please sign in to comment.