Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adaptive learning: Improve competency student view #9916

Merged
merged 7 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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 @@ -295,5 +295,14 @@ default CourseCompetency findByIdWithLectureUnitsAndExercisesElseThrow(long comp

List<CourseCompetency> findByCourseIdOrderById(long courseId);

@Query("""
SELECT c
FROM CourseCompetency c
WHERE c.course.id = :courseId
AND (SIZE(c.lectureUnitLinks) > 0 OR SIZE(c.exerciseLinks) > 0)
ORDER BY c.id
""")
List<CourseCompetency> findByCourseIdAndLinkedToLearningObjectOrderById(@Param("courseId") long courseId);

boolean existsByIdAndCourseId(long competencyId, long courseId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,17 @@ public CourseCompetency findCompetencyWithExercisesAndLectureUnitsAndProgressFor
*
* @param courseId The id of the course for which to fetch the competencies
* @param userId The id of the user for which to fetch the progress
* @param filter Whether to filter out competencies that are not linked to any learning objects
* @return The found competency
*/
public List<CourseCompetency> findCourseCompetenciesWithProgressForUserByCourseId(Long courseId, Long userId) {
List<CourseCompetency> competencies = courseCompetencyRepository.findByCourseIdOrderById(courseId);
public List<CourseCompetency> findCourseCompetenciesWithProgressForUserByCourseId(long courseId, long userId, boolean filter) {
List<CourseCompetency> competencies;
if (filter) {
competencies = courseCompetencyRepository.findByCourseIdAndLinkedToLearningObjectOrderById(courseId);
}
else {
competencies = courseCompetencyRepository.findByCourseIdOrderById(courseId);
}
return findProgressForCompetenciesAndUser(competencies, userId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,10 @@ public ResponseEntity<CourseCompetency> getCourseCompetency(@PathVariable long c
*/
@GetMapping("courses/{courseId}/course-competencies")
@EnforceAtLeastStudentInCourse
public ResponseEntity<List<CourseCompetency>> getCourseCompetenciesWithProgress(@PathVariable long courseId) {
public ResponseEntity<List<CourseCompetency>> getCourseCompetenciesWithProgress(@PathVariable long courseId, @RequestParam(defaultValue = "false") boolean filter) {
log.debug("REST request to get competencies for course with id: {}", courseId);
User user = userRepository.getUserWithGroupsAndAuthorities();
final var competencies = courseCompetencyService.findCourseCompetenciesWithProgressForUserByCourseId(courseId, user.getId());
final var competencies = courseCompetencyService.findCourseCompetenciesWithProgressForUserByCourseId(courseId, user.getId(), filter);
return ResponseEntity.ok(competencies);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,13 @@ export class CourseCompetencyService {
.set('semester', pageable.semester);
}

getAllForCourse(courseId: number): Observable<EntityArrayResponseType> {
return this.httpClient.get<CourseCompetency[]>(`${this.resourceURL}/courses/${courseId}/course-competencies`, { observe: 'response' }).pipe(
map((res: EntityArrayResponseType) => this.convertArrayResponseDatesFromServer(res)),
tap((res: EntityArrayResponseType) => res?.body?.forEach(this.sendTitlesToEntityTitleService.bind(this))),
);
getAllForCourse(courseId: number, filtered = false): Observable<EntityArrayResponseType> {
return this.httpClient
.get<CourseCompetency[]>(`${this.resourceURL}/courses/${courseId}/course-competencies${filtered ? '?filter=true' : ''}`, { observe: 'response' })
.pipe(
map((res: EntityArrayResponseType) => this.convertArrayResponseDatesFromServer(res)),
tap((res: EntityArrayResponseType) => res?.body?.forEach(this.sendTitlesToEntityTitleService.bind(this))),
);
}

getProgress(competencyId: number, courseId: number, refresh = false) {
Expand Down
21 changes: 21 additions & 0 deletions src/main/webapp/app/entities/competency.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,24 @@ export function getMastery(competencyProgress: CompetencyProgress | undefined):
// clamp the value between 0 and 100
return Math.min(100, Math.max(0, Math.round(getProgress(competencyProgress) * getConfidence(competencyProgress))));
}

/**
* Simple comparator for sorting competencies by their soft due date
* @param a The first competency
* @param b The second competency
*/
export function compareSoftDueDate(a: CourseCompetency, b: CourseCompetency): number {
if (a.softDueDate) {
if (b.softDueDate) {
if (a.softDueDate.isSame(b.softDueDate)) {
return 0;
}
return a.softDueDate.isBefore(b.softDueDate) ? -1 : 1;
}
return -1;
}
if (b.softDueDate) {
return 1;
}
return 0;
}
MaximilianAnzinger marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router';
import { AlertService } from 'app/core/util/alert.service';
import { onError } from 'app/shared/util/global.utils';
import { HttpErrorResponse } from '@angular/common/http';
import { Competency, CompetencyJol, CourseCompetencyType, getMastery } from 'app/entities/competency.model';
import { Competency, CompetencyJol, CourseCompetencyType, compareSoftDueDate, getMastery } from 'app/entities/competency.model';
import { Subscription, forkJoin, of } from 'rxjs';
import { Course } from 'app/entities/course.model';
import { faAngleDown, faAngleUp } from '@fortawesome/free-solid-svg-icons';
Expand Down Expand Up @@ -86,13 +86,13 @@ export class CourseCompetenciesComponent implements OnInit, OnDestroy {
loadData() {
this.isLoading = true;

const courseCompetencyObservable = this.courseCompetencyService.getAllForCourse(this.courseId);
const courseCompetencyObservable = this.courseCompetencyService.getAllForCourse(this.courseId, true);
const competencyJolObservable = this.judgementOfLearningEnabled ? this.courseCompetencyService.getJoLAllForCourse(this.courseId) : of(undefined);

forkJoin([courseCompetencyObservable, competencyJolObservable]).subscribe({
next: ([courseCompetencies, judgementOfLearningMap]) => {
const courseCompetenciesResponse = courseCompetencies.body ?? [];
this.competencies = courseCompetenciesResponse.filter((competency) => competency.type === CourseCompetencyType.COMPETENCY);
this.competencies = courseCompetenciesResponse.filter((competency) => competency.type === CourseCompetencyType.COMPETENCY).sort(compareSoftDueDate);
this.prerequisites = courseCompetenciesResponse.filter((competency) => competency.type === CourseCompetencyType.PREREQUISITE);

if (judgementOfLearningMap !== undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,20 @@
assertThat(competenciesOfCourse.stream().filter(l -> l.getId().equals(newCompetency.getId())).findFirst().orElseThrow().getLectureUnitLinks()).isEmpty();
}

abstract List<? extends CourseCompetency> getAllFilteredCall(long courseId, HttpStatus expectedStatus) throws Exception;

// Test
void shouldReturnCompetenciesForCourseFiltered(CourseCompetency newCompetency) throws Exception {
newCompetency.setTitle("Title");
newCompetency.setDescription("Description");
newCompetency.setCourse(course);
courseCompetencyRepository.save(newCompetency);

List<? extends CourseCompetency> competenciesOfCourse = getAllFilteredCall(course.getId(), HttpStatus.OK);

assertThat(competenciesOfCourse).noneMatch(c -> c.getId().equals(courseCompetency.getId()));

Check failure on line 239 in src/test/java/de/tum/cit/aet/artemis/atlas/competency/AbstractCompetencyPrerequisiteIntegrationTest.java

View workflow job for this annotation

GitHub Actions / H2 Tests

de.tum.cit.aet.artemis.atlas.competency.CourseCompetencyIntegrationTest ► shouldReturnCompetenciesForStudentOfCourseFiltered()

Failed test found in: build/test-results/test/TEST-de.tum.cit.aet.artemis.atlas.competency.CourseCompetencyIntegrationTest.xml Error: java.lang.AssertionError:
Raw output
java.lang.AssertionError: 
Expecting no elements of:
  [de.tum.cit.aet.artemis.atlas.domain.competency.Competency@8a,
    de.tum.cit.aet.artemis.atlas.domain.competency.Competency@8b]
to match given predicate but this element did:
  de.tum.cit.aet.artemis.atlas.domain.competency.Competency@8a
	at de.tum.cit.aet.artemis.atlas.competency.AbstractCompetencyPrerequisiteIntegrationTest.shouldReturnCompetenciesForCourseFiltered(AbstractCompetencyPrerequisiteIntegrationTest.java:239)
	at de.tum.cit.aet.artemis.atlas.competency.CourseCompetencyIntegrationTest.shouldReturnCompetenciesForStudentOfCourseFiltered(CourseCompetencyIntegrationTest.java:163)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)
}

// Test
void testShouldReturnForbiddenForStudentNotInCourse() throws Exception {
getAllCall(course.getId(), HttpStatus.FORBIDDEN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ void shouldReturnCompetenciesForStudentOfCourse() throws Exception {
super.shouldReturnCompetenciesForCourse(new Competency());
}

@Override
List<? extends CourseCompetency> getAllFilteredCall(long courseId, HttpStatus expectedStatus) throws Exception {
throw new UnsupportedOperationException("Not implemented for competencies");
}

@Test
@WithMockUser(username = TEST_PREFIX + "student42", roles = "USER")
void testShouldReturnForbiddenForStudentNotInCourse() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,17 @@ void shouldReturnCompetenciesForStudentOfCourse() throws Exception {
super.shouldReturnCompetenciesForCourse(new Competency());
}

@Override
List<? extends CourseCompetency> getAllFilteredCall(long courseId, HttpStatus expectedStatus) throws Exception {
return request.getList("/api/courses/" + courseId + "/course-competencies?filtered=true", expectedStatus, CourseCompetency.class);
}

@Test
@WithMockUser(username = TEST_PREFIX + "student1", roles = "USER")
void shouldReturnCompetenciesForStudentOfCourseFiltered() throws Exception {
super.shouldReturnCompetenciesForCourseFiltered(new Competency());
}

@Test
@WithMockUser(username = TEST_PREFIX + "student42", roles = "USER")
void testShouldReturnForbiddenForStudentNotInCourse() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ void shouldReturnCompetenciesForStudentOfCourse() throws Exception {
super.shouldReturnCompetenciesForCourse(new Prerequisite());
}

@Override
List<? extends CourseCompetency> getAllFilteredCall(long courseId, HttpStatus expectedStatus) throws Exception {
throw new UnsupportedOperationException("Not implemented for prerequisites");
}

@Test
@WithMockUser(username = TEST_PREFIX + "student42", roles = "USER")
void shouldReturnCompetenciesForStudentNotInCourse() throws Exception {
Expand Down
Loading