Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
5fdb3ad
add previous submission to athena feedback request dto
ahmetsenturk Jul 3, 2025
9b02073
remove debugging prints
ahmetsenturk Jul 7, 2025
bdb02e2
add the skeleton modal for preference selection
ahmetsenturk Jul 9, 2025
2a2d7e4
add the navigation logic to the modal
ahmetsenturk Jul 9, 2025
e7576e3
add a persistent flag for onboarding logic
ahmetsenturk Jul 10, 2025
0eced8f
update folder structure
ahmetsenturk Jul 10, 2025
c260a64
emit results from the modal
ahmetsenturk Jul 11, 2025
c890ac2
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 11, 2025
764661d
Merge branch 'develop' into feature/athena/incorporate-prev-submission
ahmetsenturk Jul 11, 2025
bba14b4
fix last merge conflict issues
ahmetsenturk Jul 11, 2025
51cfeee
Merge branch 'develop' into feature/athena/incorporate-prev-submission
ahmetsenturk Jul 14, 2025
29ade1e
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 14, 2025
bfbc238
add translations
ahmetsenturk Jul 14, 2025
7a3190c
update the feedback dimensions
ahmetsenturk Jul 14, 2025
4f74f2e
update feedback profile options
ahmetsenturk Jul 15, 2025
f4515fd
update segmented-toggle focus color
ahmetsenturk Jul 15, 2025
d4d863f
fix learner profile fetching
ahmetsenturk Jul 15, 2025
84b0c45
update test cases
ahmetsenturk Jul 15, 2025
b018089
Merge branch 'develop' into feature/athena/incorporate-prev-submission
ahmetsenturk Jul 15, 2025
8ff0484
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 15, 2025
192e78f
update get to get or create
ahmetsenturk Jul 16, 2025
3e134a8
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 18, 2025
b8a9973
add neutral option to feedback preferences
ahmetsenturk Jul 18, 2025
7dcf297
optimize db calls
ahmetsenturk Jul 21, 2025
f46c4f5
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 21, 2025
705fc98
Merge branch 'develop' into feature/athena/incorporate-prev-submission
ahmetsenturk Jul 21, 2025
3e690be
fix client tests
ahmetsenturk Jul 21, 2025
754cc4d
address Konstantins review
ahmetsenturk Jul 21, 2025
c3bd5ce
fix client tests
ahmetsenturk Jul 22, 2025
7b04fa6
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 22, 2025
460e12c
remove redundant post endpoint
ahmetsenturk Jul 22, 2025
f95cbcc
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 22, 2025
5d8e7c5
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 25, 2025
a95db58
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 25, 2025
4d2755e
convert hardcoded color codes
ahmetsenturk Jul 26, 2025
940112e
address tobias feedback
ahmetsenturk Jul 26, 2025
a4e11ac
convert int to tinyint
ahmetsenturk Jul 26, 2025
b17dd92
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 27, 2025
a06e270
fix failing client tests
ahmetsenturk Jul 27, 2025
3cf742a
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 27, 2025
f64042e
update segmented button component
ahmetsenturk Jul 29, 2025
73af80d
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Jul 30, 2025
09f8b2f
Merge branch 'develop' into feature/athena/incorporate-prev-submission
ahmetsenturk Jul 30, 2025
30496f9
Merge branch 'develop' into feature/athena/incorporate-prev-submission
ahmetsenturk Jul 30, 2025
1f16c51
fix feedback suggestion tests
ahmetsenturk Jul 30, 2025
7adb51b
convert get to getOrCreate
ahmetsenturk Jul 30, 2025
05ccb06
update learner profile test
ahmetsenturk Jul 30, 2025
32b20d5
addressing Florian's feedback
ahmetsenturk Jul 31, 2025
4bece5c
Merge branch 'develop' into feature/athena/incorporate-prev-submission
ahmetsenturk Jul 31, 2025
7abe155
Merge branch 'develop' into feature/athena/incorporate-prev-submission
ahmetsenturk Aug 2, 2025
79f99d9
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Aug 4, 2025
ef6c47b
Merge branch 'feature/athena/incorporate-prev-submission' into featur…
ahmetsenturk Aug 5, 2025
620d3be
refactor test suite
ahmetsenturk Aug 5, 2025
1f3eb9a
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Aug 11, 2025
0f2e228
update dark mode compatibility
ahmetsenturk Aug 11, 2025
61e20e3
convert null to undefined
ahmetsenturk Aug 13, 2025
01f0ac3
address coderabbit feedback
ahmetsenturk Aug 14, 2025
8fd61c3
Merge branch 'develop' into feature/adaptive-learning/onboarding-feed…
ahmetsenturk Aug 14, 2025
3398eaf
remove unused service from test
ahmetsenturk Aug 14, 2025
dc1712e
update learner profile creation logic
ahmetsenturk Aug 15, 2025
d8ddd5f
safe call create learner profile
ahmetsenturk Aug 15, 2025
f89cb25
update max beans
ahmetsenturk Aug 15, 2025
37c0a33
Revert "update max beans"
ahmetsenturk Aug 16, 2025
0ef72da
Revert "safe call create learner profile"
ahmetsenturk Aug 16, 2025
9801337
Revert "update learner profile creation logic"
ahmetsenturk Aug 16, 2025
cb0edee
fix duplicate learner profile due to race condition
ahmetsenturk Aug 18, 2025
34832ff
fix feedback preferences interface
ahmetsenturk Aug 22, 2025
f8533d3
fix course learner profile repository error
ahmetsenturk Aug 22, 2025
1753769
Merge branch 'develop' into bugfix/learner-profile-settings
ahmetsenturk Aug 29, 2025
3e3a129
update segmented toggle signals
ahmetsenturk Aug 29, 2025
0046adc
fix segmented toggle tests
ahmetsenturk Aug 29, 2025
8f0a92f
Merge branch 'develop' into bugfix/learner-profile-settings
ekayandan Sep 3, 2025
a60a616
Merge branch 'develop' into bugfix/learner-profile-settings
HawKhiem Sep 4, 2025
1c194a7
Merge branch 'develop' into bugfix/learner-profile-settings
ahmetsenturk Sep 17, 2025
bc9aa84
fix hibernate no session with left join fetch
ahmetsenturk Sep 17, 2025
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 @@ -48,6 +48,7 @@ public interface CourseLearnerProfileRepository extends ArtemisJpaRepository<Cou
@Query("""
SELECT clp
FROM CourseLearnerProfile clp
LEFT JOIN FETCH clp.course
WHERE clp.learnerProfile.user.login = :login AND clp.course = :course
""")
Optional<CourseLearnerProfile> findByLoginAndCourse(@Param("login") String login, @Param("course") Course course);
Comment on lines 49 to 54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Ensure data integrity: enforce uniqueness for (user, course).

This method returns Optional<CourseLearnerProfile>, but without a DB-level unique constraint, concurrent creates can still yield duplicates and a NonUniqueResultException later. Add a unique constraint on the join columns (e.g., (learner_profile_id, course_id) or (user_id, course_id) depending on your model) plus a migration.

Run to check if such a constraint already exists:


🏁 Script executed:

#!/bin/bash
# Find entity and DDL hints for CourseLearnerProfile
fd -a 'CourseLearnerProfile.*\.java$' | xargs -I{} sh -c 'echo "=== {} ==="; sed -n "1,200p" "{}" | sed -n "1,200p"'
rg -n --pretty -C2 '@Table\(|uniqueConstraints|UniqueConstraint|@Unique' --glob 'src/main/java/**'
# Search Liquibase/Flyway migrations for unique indexes on course_learner_profile
rg -n -i -C2 'unique|unique index|unique constraint|course_learner_profile' --glob 'src/main/resources/**'

Length of output: 65380


Add DB-level UNIQUE constraint on course_learner_profile (learner_profile_id, course_id)

  • The entity src/main/java/de/tum/cit/aet/artemis/atlas/domain/profile/CourseLearnerProfile.java has @table(name = "course_learner_profile") with no uniqueConstraints and a search found no migration creating such a constraint — add a DB-level UNIQUE(learner_profile_id, course_id) (update @table or add a DB migration via Liquibase/Flyway) to prevent concurrent-creation duplicates.
  • Make creation resilient to races: catch DataIntegrityViolationException (or similar) around save and re-query findByLoginAndCourse (or retry) to return the existing record instead of failing.
🤖 Prompt for AI Agents
In
src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseLearnerProfileRepository.java
around lines 49-54: add a DB-level UNIQUE constraint on course_learner_profile
for (learner_profile_id, course_id) and make creation resilient to races by
updating either the JPA entity @Table annotation to include uniqueConstraints =
@UniqueConstraint(columnNames = {"learner_profile_id","course_id"}) or adding a
Liquibase/Flyway migration that creates the UNIQUE index; additionally, when
creating a CourseLearnerProfile catch DataIntegrityViolationException (or the
specific DB exception), and on that exception re-query using
findByLoginAndCourse (or retry) to return the existing record instead of
propagating the error.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.tum.cit.aet.artemis.atlas.service.profile;

import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -35,13 +36,20 @@ public CourseLearnerProfileService(CourseLearnerProfileRepository courseLearnerP

/**
* Create a course learner profile for a user and saves it in the database
* If a profile already exists for this user and course, it returns the existing profile.
*
* @param course the course for which the profile is created
* @param user the user for which the profile is created
* @return Saved CourseLearnerProfile
*/
public CourseLearnerProfile createCourseLearnerProfile(Course course, User user) {

// Check if a profile already exists for this user and course
Optional<CourseLearnerProfile> existingProfile = courseLearnerProfileRepository.findByLoginAndCourse(user.getLogin(), course);
if (existingProfile.isPresent()) {
return existingProfile.get();
}

// Ensure that the user has a learner profile (lazy creation)
if (user.getLearnerProfile() == null) {
learnerProfileService.createProfile(user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ <h2 jhiTranslate="artemisApp.learnerProfile.feedbackLearnerProfile.title"></h2>
<!-- Profile settings section -->
<button class="btn btn-outline-secondary mt-3" (click)="openOnboardingModal()" jhiTranslate="artemisApp.learnerProfile.feedbackLearnerProfile.replayOnboardingButton"></button>
<div [ngClass]="{ 'd-none': disabled }">
<div class="row row-cols-1 row-cols-xl-2 gx-5 align-items-end">
<div class="row row-cols-1 row-cols-xl-2 gx-5">
<!-- Brief vs. Detailed setting -->
<div class="col">
<div class="col d-flex flex-column">
<div class="title" jhiTranslate="artemisApp.learnerProfile.feedbackLearnerProfile.feedbackDetail.label"></div>
<div class="description" jhiTranslate="artemisApp.learnerProfile.feedbackLearnerProfile.feedbackDetail.description"></div>
<jhi-segmented-toggle [options]="feedbackDetailOptions" [(selected)]="feedbackDetail" (selectedChange)="onToggleChange()" />
<jhi-segmented-toggle class="mt-auto" [options]="feedbackDetailOptions" [(selected)]="feedbackDetail" (selectedChange)="onToggleChange()" />
</div>
<div class="col">
<div class="col d-flex flex-column">
<div class="title" jhiTranslate="artemisApp.learnerProfile.feedbackLearnerProfile.feedbackFormality.label"></div>
<div class="description" jhiTranslate="artemisApp.learnerProfile.feedbackLearnerProfile.feedbackFormality.description"></div>
<jhi-segmented-toggle [options]="feedbackFormalityOptions" [(selected)]="feedbackFormality" (selectedChange)="onToggleChange()" />
<jhi-segmented-toggle class="mt-auto" [options]="feedbackFormalityOptions" [(selected)]="feedbackFormality" (selectedChange)="onToggleChange()" />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<div class="segmented-toggle-container">
<div class="btn-group w-100">
@for (option of options; track option.value) {
<button type="button" class="btn" [ngClass]="selected === option.value ? 'btn-primary' : 'btn-outline-secondary'" (click)="select(option.value)">
{{ option.label }}
@for (option of options(); track option.value) {
<button type="button" class="btn" [ngClass]="selected() === option.value ? 'btn-primary' : 'btn-outline-secondary'" (click)="select(option.value)">
<span class="label" [attr.title]="option.label">{{ option.label }}</span>
</button>
}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,26 @@
.btn-group {
display: flex;
width: 100%;
align-items: stretch;
}

.btn-group .btn {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding-top: 0.625rem; /* 10px */
padding-bottom: 0.625rem; /* 10px */
}

.btn-group .btn .label {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal; /* allow wrap */
line-height: 1.2;
-webkit-line-clamp: 2; /* default clamp to 2 lines */
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,29 @@ describe('SegmentedToggleComponent', () => {

fixture = TestBed.createComponent(SegmentedToggleComponent<number>);
component = fixture.componentInstance;
component.options = mockOptions;
fixture.componentRef.setInput('options', mockOptions);
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
expect(component.options).toEqual(mockOptions);
expect(component.options()).toEqual(mockOptions);
});

it('should bind selected value correctly', () => {
component.selected = 2;
component.selected.set(2);
fixture.detectChanges();
expect(component.selected).toBe(2);
expect(component.selected()).toBe(2);
});

it('should emit selectedChange event when an option is selected', () => {
const selectedValue = 1;
const spy = jest.spyOn(component.selectedChange, 'emit');

component.select(selectedValue);

expect(spy).toHaveBeenCalledWith(selectedValue);
expect(component.selected).toBe(selectedValue);
expect(component.selected()).toBe(selectedValue);
});

it('should handle empty options array', () => {
component.options = [];
fixture.componentRef.setInput('options', []);
fixture.detectChanges();

const compiled = fixture.nativeElement;
Expand All @@ -57,7 +53,7 @@ describe('SegmentedToggleComponent', () => {
});

it('should render all options correctly', () => {
component.options = mockOptions;
fixture.componentRef.setInput('options', mockOptions);
fixture.detectChanges();

const compiled = fixture.nativeElement;
Expand All @@ -70,8 +66,8 @@ describe('SegmentedToggleComponent', () => {
});

it('should apply selected class to the active option', () => {
component.options = mockOptions;
component.selected = 2;
fixture.componentRef.setInput('options', mockOptions);
component.selected.set(2);
fixture.detectChanges();

const compiled = fixture.nativeElement;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, input, model } from '@angular/core';
import { NgClass } from '@angular/common';

@Component({
Expand All @@ -9,12 +9,11 @@ import { NgClass } from '@angular/common';
styleUrls: ['./segmented-toggle.component.scss'],
})
export class SegmentedToggleComponent<T> {
@Input() options: { label: string; value: T }[] = [];
@Input() selected: T;
@Output() selectedChange = new EventEmitter<T>();
options = input<{ label: string; value: T }[]>([]);
selected = model<T>();
maxLines = input(2);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this input used anywhere? if not, maybe can be deleted

select(value: T) {
this.selected = value;
this.selectedChange.emit(value);
this.selected.set(value);
}
}
2 changes: 1 addition & 1 deletion src/main/webapp/i18n/de/learnerProfile.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"friendly": "Freundlich"
},
"profileSaved": "Feedback-Lernprofil gespeichert",
"invalidRange": "Werte müssen true oder false sein",
"invalidRange": "Werte müssen zwischen 1 und 3 liegen",
"error": "Feedback-Lernprofil konnte nicht gespeichert werden",
"setPreferencesButton": "Feedback-Präferenzen festlegen",
"replayOnboardingButton": "Onboarding wiederholen"
Expand Down
2 changes: 1 addition & 1 deletion src/main/webapp/i18n/en/learnerProfile.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"friendly": "Friendly"
},
"profileSaved": "Feedback learner profile saved",
"invalidRange": "Values must be true or false",
"invalidRange": "Values must be between 1 and 3",
"error": "Feedback learner profile could not be saved",
"setPreferencesButton": "Set your feedback preferences",
"replayOnboardingButton": "Replay onboarding"
Expand Down
Loading