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: Fix multiple small bugs #9756

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package de.tum.cit.aet.artemis.atlas.dto;

import java.time.ZonedDateTime;

import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyTaxonomy;
import de.tum.cit.aet.artemis.atlas.domain.competency.CourseCompetency;
import de.tum.cit.aet.artemis.atlas.service.competency.CompetencyProgressService;

public record CompetencyStudentProgressDTO(long id, String title, String description, CompetencyTaxonomy taxonomy, ZonedDateTime softDueDate, boolean optional, String type,
long numberOfStudents, long numberOfMasteredStudents) {

public static CompetencyStudentProgressDTO of(CourseCompetency courseCompetency) {
return new CompetencyStudentProgressDTO(courseCompetency.getId(), courseCompetency.getTitle(), courseCompetency.getDescription(), courseCompetency.getTaxonomy(),
courseCompetency.getSoftDueDate(), courseCompetency.isOptional(), courseCompetency.getClass().getSimpleName(), courseCompetency.getUserProgress().size(),
courseCompetency.getUserProgress().stream().filter(CompetencyProgressService::isMastered).count());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ SELECT COUNT(cp)
Set<CompetencyProgress> findAllPriorByCompetencyId(@Param("competency") CourseCompetency competency, @Param("user") User userId);

@Query("""
SELECT COALESCE(GREATEST(0.0, LEAST(1.0, AVG(cp.progress * cp.confidence / com.masteryThreshold))), 0.0)
SELECT COALESCE(GREATEST(0.0, LEAST(100.0, AVG(cp.progress * cp.confidence / com.masteryThreshold * 100))), 0.0)
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
FROM CompetencyProgress cp
LEFT JOIN cp.competency com
LEFT JOIN com.course c
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import de.tum.cit.aet.artemis.atlas.domain.LearningObject;
import de.tum.cit.aet.artemis.atlas.domain.competency.CourseCompetency;
import de.tum.cit.aet.artemis.atlas.dto.CompetencyStudentProgressDTO;
import de.tum.cit.aet.artemis.atlas.dto.metrics.CompetencyExerciseMasteryCalculationDTO;
import de.tum.cit.aet.artemis.atlas.dto.metrics.CompetencyLectureUnitMasteryCalculationDTO;
import de.tum.cit.aet.artemis.core.domain.User;
Expand Down Expand Up @@ -295,5 +296,24 @@ default CourseCompetency findByIdWithLectureUnitsAndExercisesElseThrow(long comp

List<CourseCompetency> findByCourseIdOrderById(long courseId);

@Query("""
SELECT new de.tum.cit.aet.artemis.atlas.dto.CompetencyStudentProgressDTO(
c.id,
c.title,
c.description,
c.taxonomy,
c.softDueDate,
c.optional,
CASE WHEN TYPE(c) = Competency THEN 'competency' ELSE 'prerequisite' END,
COUNT(up),
COUNT(CASE WHEN up.progress * up.confidence >= c.masteryThreshold THEN 1 ELSE 0 END)
)
FROM CourseCompetency c
LEFT JOIN c.userProgress up
WHERE c.course.id = :courseId
GROUP BY c
""")
List<CompetencyStudentProgressDTO> findWithStudentProgressByCourseId(@Param("courseId") long courseId);

boolean existsByIdAndCourseId(long competencyId, long courseId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import de.tum.cit.aet.artemis.atlas.domain.competency.StandardizedCompetency;
import de.tum.cit.aet.artemis.atlas.dto.CompetencyImportOptionsDTO;
import de.tum.cit.aet.artemis.atlas.dto.CompetencyRelationDTO;
import de.tum.cit.aet.artemis.atlas.dto.CompetencyStudentProgressDTO;
import de.tum.cit.aet.artemis.atlas.dto.CompetencyWithTailRelationDTO;
import de.tum.cit.aet.artemis.atlas.dto.UpdateCourseCompetencyRelationDTO;
import de.tum.cit.aet.artemis.atlas.repository.CompetencyProgressRepository;
Expand Down Expand Up @@ -131,6 +132,18 @@ public List<CourseCompetency> findCourseCompetenciesWithProgressForUserByCourseI
return findProgressForCompetenciesAndUser(competencies, userId);
}

/**
* Finds competencies within a course and fetch progress for the provided user.
* <p>
* As Spring Boot 3 doesn't support conditional JOIN FETCH statements, we have to retrieve the data manually.
*
* @param courseId The id of the course for which to fetch the competencies
* @return The found competency
*/
public List<CompetencyStudentProgressDTO> findCourseCompetenciesWithStudentProgressByCourseId(Long courseId) {
return courseCompetencyRepository.findWithStudentProgressByCourseId(courseId);
}

/**
* Updates the type of a course competency relation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import de.tum.cit.aet.artemis.atlas.dto.CompetencyImportOptionsDTO;
import de.tum.cit.aet.artemis.atlas.dto.CompetencyJolPairDTO;
import de.tum.cit.aet.artemis.atlas.dto.CompetencyRelationDTO;
import de.tum.cit.aet.artemis.atlas.dto.CompetencyStudentProgressDTO;
import de.tum.cit.aet.artemis.atlas.dto.CompetencyWithTailRelationDTO;
import de.tum.cit.aet.artemis.atlas.dto.UpdateCourseCompetencyRelationDTO;
import de.tum.cit.aet.artemis.atlas.repository.CompetencyProgressRepository;
Expand All @@ -56,6 +57,7 @@
import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastEditorInCourse;
import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastInstructorInCourse;
import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastStudentInCourse;
import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastTutorInCourse;
import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService;
import de.tum.cit.aet.artemis.core.service.feature.Feature;
import de.tum.cit.aet.artemis.core.service.feature.FeatureToggle;
Expand Down Expand Up @@ -167,13 +169,27 @@ 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>> getCourseCompetenciesWithOwnProgress(@PathVariable long courseId) {
log.debug("REST request to get competencies for course with id: {}", courseId);
User user = userRepository.getUserWithGroupsAndAuthorities();
final var competencies = courseCompetencyService.findCourseCompetenciesWithProgressForUserByCourseId(courseId, user.getId());
return ResponseEntity.ok(competencies);
}

/**
* GET courses/:courseId/course-competencies : gets all the course competencies of a course
*
* @param courseId the id of the course for which the competencies should be fetched
* @return the ResponseEntity with status 200 (OK) and with body the found competencies
*/
@GetMapping("courses/{courseId}/course-competencies-student-progress")
@EnforceAtLeastTutorInCourse
public ResponseEntity<List<CompetencyStudentProgressDTO>> getCourseCompetenciesWithStudentProgress(@PathVariable long courseId) {
log.debug("REST request to get competencies for course with id: {}", courseId);
final var competencies = courseCompetencyService.findCourseCompetenciesWithStudentProgressByCourseId(courseId);
return ResponseEntity.ok(competencies);
}

/**
* GET courses/:courseId/course-competencies/:competencyId/student-progress gets the course competency progress for a user
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
<div>
<div class="d-flex flex-wrap mt-4">
<div class="d-flex justify-content-center align-items-center">
<h2 class="m-0" jhiTranslate="artemisApp.competency.manage.title"></h2>
<h4 class="m-0" jhiTranslate="artemisApp.competency.manage.title"></h4>
<button class="ms-3 btn btn-sm btn-warning" (click)="openCourseCompetencyExplanation()">
<fa-icon [icon]="faCircleQuestion" />
<span jhiTranslate="artemisApp.courseCompetency.manage.helpButton"></span>
</button>
</div>
<div class="ms-auto justify-content-end">
@if (irisCompetencyGenerationEnabled()) {
<a class="btn btn-primary" id="generateButton" [routerLink]="['/course-management', courseId(), 'competency-management', 'generate']">
<a class="btn btn-primary btn-sm" id="generateButton" [routerLink]="['/course-management', courseId(), 'competency-management', 'generate']">
<fa-icon [icon]="faRobot" />
<span jhiTranslate="artemisApp.competency.manage.generateButton"></span>
</a>
}
<button class="btn btn-primary" id="openCourseCompetencyRelationsButton" (click)="openCourseCompetenciesRelationModal()">
<button class="btn btn-primary btn-sm ms-2" id="openCourseCompetencyRelationsButton" (click)="openCourseCompetenciesRelationModal()">
<fa-icon [icon]="faEdit" />
<span jhiTranslate="artemisApp.courseCompetency.manage.editRelationsButton"></span>
</button>
<button class="btn btn-primary" id="courseCompetencyImportAllButton" (click)="openImportAllModal()">
<button class="btn btn-primary btn-sm ms-2" id="courseCompetencyImportAllButton" (click)="openImportAllModal()">
<fa-icon [icon]="faFileImport" />
<span jhiTranslate="artemisApp.courseCompetency.manage.importAllButton"></span>
</button>
Expand All @@ -31,20 +31,20 @@ <h2 class="m-0" jhiTranslate="artemisApp.competency.manage.title"></h2>
</div>
</div>
}
<jhi-competency-management-table
[courseId]="courseId()"
[(allCompetencies)]="courseCompetencies"
<jhi-course-competencies-management-table
[courseCompetencies]="competencies()"
[competencyType]="CourseCompetencyType.COMPETENCY"
[courseCompetencyType]="CourseCompetencyType.COMPETENCY"
[courseId]="courseId()"
[standardizedCompetenciesEnabled]="standardizedCompetenciesEnabled()"
(competencyDeleted)="onRemoveCompetency($event)"
(onCourseCompetencyDeletion)="onRemoveCompetency($event)"
(onCourseCompetenciesImport)="onCourseCompetenciesImport($event)"
/>
<jhi-competency-management-table
[courseId]="courseId()"
[(allCompetencies)]="courseCompetencies"
<jhi-course-competencies-management-table
[courseCompetencies]="prerequisites()"
[competencyType]="CourseCompetencyType.PREREQUISITE"
[courseCompetencyType]="CourseCompetencyType.PREREQUISITE"
[courseId]="courseId()"
[standardizedCompetenciesEnabled]="standardizedCompetenciesEnabled()"
(competencyDeleted)="onRemoveCompetency($event)"
(onCourseCompetencyDeletion)="onRemoveCompetency($event)"
(onCourseCompetenciesImport)="onCourseCompetenciesImport($event)"
/>
</div>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, OnInit, computed, effect, inject, signal, untracked } from '@angular/core';
import { Component, computed, effect, inject, signal, untracked } from '@angular/core';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { AlertService } from 'app/core/util/alert.service';
import { CompetencyWithTailRelationDTO, CourseCompetency, CourseCompetencyType, getIcon } from 'app/entities/competency.model';
import { CompetencyWithTailRelationDTO, CourseCompetency, CourseCompetencyStudentProgressDTO, CourseCompetencyType, getIcon } from 'app/entities/competency.model';
import { firstValueFrom, map } from 'rxjs';
import { faCircleQuestion, faEdit, faFileImport, faPencilAlt, faPlus, faRobot, faTrash } from '@fortawesome/free-solid-svg-icons';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
Expand All @@ -22,14 +22,15 @@ import { ArtemisSharedComponentModule } from 'app/shared/components/shared-compo
import { CourseCompetenciesRelationModalComponent } from 'app/course/competencies/components/course-competencies-relation-modal/course-competencies-relation-modal.component';
import { CourseCompetencyExplanationModalComponent } from 'app/course/competencies/components/course-competency-explanation-modal/course-competency-explanation-modal.component';
import { toSignal } from '@angular/core/rxjs-interop';
import { CourseCompetenciesManagementTableComponent } from 'app/course/competencies/components/course-competencies-management-table/course-competencies-management-table.component';

@Component({
selector: 'jhi-competency-management',
templateUrl: './competency-management.component.html',
standalone: true,
imports: [CompetencyManagementTableComponent, TranslateDirective, FontAwesomeModule, RouterModule, ArtemisSharedComponentModule],
imports: [CompetencyManagementTableComponent, TranslateDirective, FontAwesomeModule, RouterModule, ArtemisSharedComponentModule, CourseCompetenciesManagementTableComponent],
})
export class CompetencyManagementComponent implements OnInit {
export class CompetencyManagementComponent {
protected readonly faEdit = faEdit;
protected readonly faPlus = faPlus;
protected readonly faFileImport = faFileImport;
Expand All @@ -53,7 +54,7 @@ export class CompetencyManagementComponent implements OnInit {
readonly courseId = toSignal(this.activatedRoute.parent!.params.pipe(map((params) => Number(params.courseId))), { requireSync: true });
readonly isLoading = signal<boolean>(false);

readonly courseCompetencies = signal<CourseCompetency[]>([]);
readonly courseCompetencies = signal<CourseCompetencyStudentProgressDTO[]>([]);
competencies = computed(() => this.courseCompetencies().filter((cc) => cc.type === CourseCompetencyType.COMPETENCY));
prerequisites = computed(() => this.courseCompetencies().filter((cc) => cc.type === CourseCompetencyType.PREREQUISITE));

Expand All @@ -77,20 +78,19 @@ export class CompetencyManagementComponent implements OnInit {
}
});
});
}

ngOnInit(): void {
const lastVisit = sessionStorage.getItem('lastTimeVisitedCourseCompetencyExplanation');
if (!lastVisit) {
this.openCourseCompetencyExplanation();
}
sessionStorage.setItem('lastTimeVisitedCourseCompetencyExplanation', Date.now().toString());
effect(() => {
const lastVisit = localStorage.getItem('lastTimeVisitedCourseCompetencyExplanation');
if (!lastVisit) {
this.openCourseCompetencyExplanation();
}
localStorage.setItem('lastTimeVisitedCourseCompetencyExplanation', Date.now().toString());
});
}
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved

private async loadIrisEnabled() {
try {
const combinedCourseSettings = await firstValueFrom(this.irisSettingsService.getCombinedCourseSettings(this.courseId()));
this.irisCompetencyGenerationEnabled.set(combinedCourseSettings?.irisCompetencyGenerationSettings?.enabled ?? false);
this.irisCompetencyGenerationEnabled.set(!!combinedCourseSettings?.irisCompetencyGenerationSettings?.enabled);
} catch (error) {
this.alertService.error(error);
}
Expand All @@ -99,7 +99,7 @@ export class CompetencyManagementComponent implements OnInit {
private async loadCourseCompetencies(courseId: number) {
try {
this.isLoading.set(true);
const courseCompetencies = await this.courseCompetencyApiService.getCourseCompetenciesByCourseId(courseId);
const courseCompetencies = await this.courseCompetencyApiService.getCourseCompetenciesWithStudentProgressByCourseId(courseId);
this.courseCompetencies.set(courseCompetencies);
} catch (error) {
this.alertService.error(error);
Expand Down Expand Up @@ -154,7 +154,22 @@ export class CompetencyManagementComponent implements OnInit {
* @private
*/
updateDataAfterImportAll(res: Array<CompetencyWithTailRelationDTO>) {
const importedCourseCompetencies = res.map((dto) => dto.competency!);
const importedCourseCompetencies = res
.map((dto) => dto.competency!)
.map(
(courseCompetency) =>
<CourseCompetencyStudentProgressDTO>{
id: courseCompetency.id,
title: courseCompetency.title,
description: courseCompetency.description,
numberOfMasteredStudents: 0,
numberOfStudents: 0,
optional: courseCompetency.optional,
softDueDate: courseCompetency.softDueDate,
taxonomy: courseCompetency.taxonomy,
type: courseCompetency.type,
},
);
const newCourseCompetencies = importedCourseCompetencies.filter(
JohannesStoehr marked this conversation as resolved.
Show resolved Hide resolved
(competency) => !this.courseCompetencies().some((existingCompetency) => existingCompetency.id === competency.id),
);
Expand All @@ -165,6 +180,10 @@ export class CompetencyManagementComponent implements OnInit {
this.courseCompetencies.update((courseCompetencies) => courseCompetencies.filter((cc) => cc.id !== competencyId));
}

onCourseCompetenciesImport(importedCompetencies: CourseCompetencyStudentProgressDTO[]) {
this.courseCompetencies.update((courseCompetencies) => [...courseCompetencies, ...importedCompetencies]);
}

openCourseCompetencyExplanation(): void {
this.modalService.open(CourseCompetencyExplanationModalComponent, {
size: 'xl',
Expand Down
Loading
Loading