diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/repository/SubmissionRepository.java b/src/main/java/de/tum/cit/aet/artemis/exercise/repository/SubmissionRepository.java index e4341e9e25d1..26925f2ccbf4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/repository/SubmissionRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/repository/SubmissionRepository.java @@ -81,13 +81,13 @@ public interface SubmissionRepository extends ArtemisJpaRepository findAllWithResultsAndAssessorByParticipationId(Long participationId); /** - * Get all submissions of a participation and eagerly load results and assessor ordered by submission date in ascending order + * Get all submissions of a participation and eagerly load results ordered by submission date in ascending order * * @param participationId the id of the participation * @return a list of the participation's submissions */ - @EntityGraph(type = LOAD, attributePaths = { "results", "results.assessor" }) - List findAllWithResultsAndAssessorByParticipationIdOrderBySubmissionDateAsc(Long participationId); + @EntityGraph(type = LOAD, attributePaths = { "results" }) + List findAllWithResultsByParticipationIdOrderBySubmissionDateAsc(Long participationId); /** * Get all submissions with their results by the submission ids diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisEventService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisEventService.java index 974351bb1800..58f0b8a069b8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisEventService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisEventService.java @@ -1,5 +1,7 @@ package de.tum.cit.aet.artemis.iris.service.pyris; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; @@ -17,7 +19,7 @@ * Service to handle Pyris events. */ @Service -@Profile("iris") +@Profile(PROFILE_IRIS) public class PyrisEventService { private static final Logger log = LoggerFactory.getLogger(PyrisEventService.class); diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisPipelineService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisPipelineService.java index deef716f122a..62d7f4cc99dc 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisPipelineService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisPipelineService.java @@ -205,7 +205,8 @@ private void executeCourseChatPipeline(String variant, IrisCourseChatSess executionDto.initialStages() ); }, - stages -> irisChatWebsocketService.sendStatusUpdate(session, stages)); + stages -> irisChatWebsocketService.sendStatusUpdate(session, stages) + ); // @formatter:on } @@ -307,9 +308,9 @@ private PyrisEventDTO generateEventPayloadFromObjectType(Class dtoC * * @param dtoClass the class of the DTO * @param object the object to generate the DTO from + * @param the type of the object + * @param the type of the DTO * @return Method the 'of' method - * @param the type of the object - * @param the type of the DTO */ private static Method getOfMethod(Class dtoClass, T object) { Method ofMethod = null; @@ -318,11 +319,9 @@ private static Method getOfMethod(Class dtoClass, T object) { // Traverse up the class hierarchy while (currentClass != null && ofMethod == null) { for (Method method : dtoClass.getMethods()) { - if (method.getName().equals("of") && method.getParameterCount() == 1) { - if (method.getParameters()[0].getType().isAssignableFrom(currentClass)) { - ofMethod = method; - break; - } + if (method.getName().equals("of") && method.getParameterCount() == 1 && method.getParameters()[0].getType().isAssignableFrom(currentClass)) { + ofMethod = method; + break; } } currentClass = currentClass.getSuperclass(); diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisExerciseChatSessionService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisExerciseChatSessionService.java index f9ae4b05017c..f049767a1897 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisExerciseChatSessionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/session/IrisExerciseChatSessionService.java @@ -76,10 +76,6 @@ public class IrisExerciseChatSessionService extends AbstractIrisChatSessionServi private final SubmissionRepository submissionRepository; - private final double SUCCESS_THRESHOLD = 100.0; // TODO: Retrieve configuration from Iris settings - - private final int INTERVAL_SIZE = 3; // TODO: Retrieve configuration from Iris settings - public IrisExerciseChatSessionService(IrisMessageService irisMessageService, LLMTokenUsageService llmTokenUsageService, IrisSettingsService irisSettingsService, IrisChatWebsocketService irisChatWebsocketService, AuthorizationCheckService authCheckService, IrisSessionRepository irisSessionRepository, ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository, ProgrammingSubmissionRepository programmingSubmissionRepository, @@ -221,16 +217,18 @@ public void onNewResult(Result result) { irisSettingsService.isActivatedForElseThrow(IrisEventType.PROGRESS_STALLED, exercise); - var recentSubmissions = submissionRepository.findAllWithResultsAndAssessorByParticipationIdOrderBySubmissionDateAsc(studentParticipation.getId()); + var recentSubmissions = submissionRepository.findAllWithResultsByParticipationIdOrderBySubmissionDateAsc(studentParticipation.getId()); + + double successThreshold = 100.0; // TODO: Retrieve configuration from Iris settings // Check if the user has already successfully submitted before var successfulSubmission = recentSubmissions.stream() - .anyMatch(submission -> submission.getLatestResult() != null && submission.getLatestResult().getScore() == SUCCESS_THRESHOLD); + .anyMatch(submission -> submission.getLatestResult() != null && submission.getLatestResult().getScore() == successThreshold); if (!successfulSubmission && recentSubmissions.size() >= 3) { var listOfScores = recentSubmissions.stream().map(Submission::getLatestResult).filter(Objects::nonNull).map(Result::getScore).toList(); // Check if the student needs intervention based on their recent score trajectory - var needsIntervention = needsIntervention(listOfScores, INTERVAL_SIZE); + var needsIntervention = needsIntervention(listOfScores); if (needsIntervention) { log.info("Scores in the last 3 submissions did not improve for user {}", studentParticipation.getParticipant().getName()); var participant = ((ProgrammingExerciseStudentParticipation) participation).getParticipant(); @@ -280,11 +278,11 @@ private boolean hasOverallImprovement(List scores, int i, int j) { /** * Checks if the student needs intervention based on their recent score trajectory. * - * @param scores The list of all scores for the student. - * @param intervalSize The number of recent submissions to consider. + * @param scores The list of all scores for the student. * @return true if intervention is needed, false otherwise. */ - private boolean needsIntervention(List scores, int intervalSize) { + private boolean needsIntervention(List scores) { + int intervalSize = 3; // TODO: Retrieve configuration from Iris settings if (scores.size() < intervalSize) { return false; // Not enough data to make a decision } diff --git a/src/main/webapp/app/iris/exercise-chatbot/exercise-chatbot-button.component.ts b/src/main/webapp/app/iris/exercise-chatbot/exercise-chatbot-button.component.ts index 1b9cba99cded..b36b9edea7cb 100644 --- a/src/main/webapp/app/iris/exercise-chatbot/exercise-chatbot-button.component.ts +++ b/src/main/webapp/app/iris/exercise-chatbot/exercise-chatbot-button.component.ts @@ -71,7 +71,7 @@ export class IrisExerciseChatbotButtonComponent implements OnInit, OnDestroy { ) {} ngOnInit() { - // Subscribes to route params and gets the exerciseId from the router + // Subscribes to route params and gets the exerciseId from the route this.paramsSubscription = this.route.params.subscribe((params) => { const exerciseId = parseInt(params['exerciseId'], 10); this.chatService.switchTo(this.mode, exerciseId); diff --git a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html index 611fc23b08de..69c3c88da691 100644 --- a/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html +++ b/src/main/webapp/app/iris/settings/iris-settings-update/iris-common-sub-settings-update/iris-common-sub-settings-update.component.html @@ -61,11 +61,7 @@

- @if ( - this.parentSubSettings && - !subSettings?.disabledProactiveEvents?.includes(event) && - (this.parentSubSettings.disabledProactiveEvents?.includes(event) || !this.parentSubSettings.enabled) - ) { + @if (eventInParentDisabledStatusMap.get(event)) { (); + availableVariants: IrisVariant[] = []; allowedVariants: IrisVariant[] = []; @@ -90,6 +92,9 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { if (changes.subSettings) { this.enabled = this.subSettings?.enabled ?? false; } + if (changes.parentSubSettings || changes.subSettings) { + this.updateEventDisabledStatus(); + } } loadCategories() { @@ -206,4 +211,19 @@ export class IrisCommonSubSettingsUpdateComponent implements OnInit, OnChanges { get isSettingsSwitchDisabled() { return this.inheritDisabled || (!this.isAdmin && this.settingsType !== this.EXERCISE); } + + /** + * Updates the event disabled status map based on the parent settings + * @private + */ + private updateEventDisabledStatus(): void { + this.exerciseChatEvents.forEach((event) => { + const isDisabled = + !this.subSettings?.enabled || + (this.parentSubSettings && + !this.subSettings?.disabledProactiveEvents?.includes(event) && + (this.parentSubSettings.disabledProactiveEvents?.includes(event) || !this.parentSubSettings.enabled)); + this.eventInParentDisabledStatusMap.set(event, isDisabled); + }); + } } diff --git a/src/main/webapp/i18n/de/iris.json b/src/main/webapp/i18n/de/iris.json index 8a7daa6cd1fd..19a2664aa3c8 100644 --- a/src/main/webapp/i18n/de/iris.json +++ b/src/main/webapp/i18n/de/iris.json @@ -42,8 +42,8 @@ }, "proactivitySettings": { "title": "Proaktivitätseinstellungen", - "tooltip": "Wenn eine der unten aufgeführten Optionen aktiviert ist, wird Iris basierend auf den ausgewählten Bedingungen proaktiv Hilfe-Nachrichten an Studenten senden.", - "parentDisabled": "Iris wird keine proaktiven Nachrichten für diese Option senden, da entweder dieselbe Option oder Iris in den übergeordneten Einstellungen deaktiviert ist." + "tooltip": "Aktiviere die untenstehenden Optionen, damit Iris proaktiv Studierende kontaktiert, wenn bestimmte Bedingungen erfüllt sind.", + "parentDisabled": "Iris sendet keine proaktiven Nachrichten für diese Option, da entweder die Option selbst oder Iris in den aktuellen oder übergeordneten Einstellungen deaktiviert sind." }, "enabled-disabled": "Aktiviert/Deaktiviert", "enabledForCategories": "Automatisch aktivieren für Kategorien", diff --git a/src/main/webapp/i18n/en/iris.json b/src/main/webapp/i18n/en/iris.json index ae99e0b5a451..a8e2fb79d88f 100644 --- a/src/main/webapp/i18n/en/iris.json +++ b/src/main/webapp/i18n/en/iris.json @@ -42,8 +42,8 @@ }, "proactivitySettings": { "title": "Proactivity Settings", - "tooltip": "When any of options listed below are activated, Iris will proactively send help messages to students based on the selected conditions.", - "parentDisabled": "Iris won't send proactive messages for this option because either the same option or the Iris is disabled in the parent settings." + "tooltip": "Enable options below to allow Iris to proactively reach out to students when specific conditions are met.", + "parentDisabled": "Iris won't send proactive messages for this option because either option itself or Iris are disabled in the current or parent settings." }, "enabled-disabled": "Enabled/Disabled", "enabledForCategories": "Automatically enable for categories", diff --git a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisEventSystemTest.java b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisEventSystemIntegrationTest.java similarity index 99% rename from src/test/java/de/tum/cit/aet/artemis/iris/PyrisEventSystemTest.java rename to src/test/java/de/tum/cit/aet/artemis/iris/PyrisEventSystemIntegrationTest.java index 2756c625022d..62bc019b38a8 100644 --- a/src/test/java/de/tum/cit/aet/artemis/iris/PyrisEventSystemTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/iris/PyrisEventSystemIntegrationTest.java @@ -55,9 +55,9 @@ import de.tum.cit.aet.artemis.programming.domain.TemplateProgrammingExerciseParticipation; import de.tum.cit.aet.artemis.programming.util.ProgrammingExerciseUtilService; -class PyrisEventSystemTest extends AbstractIrisIntegrationTest { +class PyrisEventSystemIntegrationTest extends AbstractIrisIntegrationTest { - private static final String TEST_PREFIX = "pyriseventsystemtest"; + private static final String TEST_PREFIX = "pyriseventsystemintegration"; @Autowired protected PyrisStatusUpdateService pyrisStatusUpdateService;