Skip to content

Commit

Permalink
Adaptive learning: Allow creating and updating prerequisite competenc…
Browse files Browse the repository at this point in the history
…ies (#8765)
  • Loading branch information
rstief authored Jun 24, 2024
1 parent 30725cd commit 62f4132
Show file tree
Hide file tree
Showing 34 changed files with 1,214 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ public interface CourseCompetencyRepository extends JpaRepository<CourseCompeten
""")
List<CourseCompetency> findAllByIdAndUserIsAtLeastEditorInCourse(@Param("courseCompetencyIds") List<Long> courseCompetencyIds, @Param("groups") Set<String> groups);

@Query("""
SELECT c.title
FROM CourseCompetency c
WHERE c.course.id = :courseId
""")
List<String> findAllTitlesByCourseId(@Param("courseId") long courseId);

/**
* Finds a list of competencies by id and verifies that the user is at least editor in the respective courses.
* If any of the competencies are not accessible, throws a {@link EntityNotFoundException}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/
public interface PrerequisiteRepository extends JpaRepository<Prerequisite, Long> {

List<Prerequisite> findByCourseIdOrderById(long courseId);
List<Prerequisite> findAllByCourseIdOrderById(long courseId);

Optional<Prerequisite> findByIdAndCourseId(long prerequisiteId, long courseId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import de.tum.in.www1.artemis.repository.PrerequisiteRepository;
import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.web.rest.dto.competency.PrerequisiteRequestDTO;

/**
* Service for managing {@link Prerequisite} competencies.
Expand Down Expand Up @@ -45,6 +46,44 @@ public PrerequisiteService(PrerequisiteRepository prerequisiteRepository, Course
this.authorizationCheckService = authorizationCheckService;
}

/**
* Creates a new prerequisite with the given values in the given course
*
* @param prerequisiteValues the values of the prerequisite to create
* @param courseId the id of the course to create the prerequisite in
* @return the created prerequisite
*/
public Prerequisite createPrerequisite(PrerequisiteRequestDTO prerequisiteValues, long courseId) {
var course = courseRepository.findByIdElseThrow(courseId);

var prerequisiteToCreate = new Prerequisite(prerequisiteValues.title(), prerequisiteValues.description(), prerequisiteValues.softDueDate(),
prerequisiteValues.masteryThreshold(), prerequisiteValues.taxonomy(), prerequisiteValues.optional());
prerequisiteToCreate.setCourse(course);

return prerequisiteRepository.save(prerequisiteToCreate);
}

/**
* Updates an existing prerequisite with the given values if it is part of the given course
*
* @param prerequisiteValues the new prerequisite values
* @param prerequisiteId the id of the prerequisite to update
* @param courseId the id of the course the prerequisite is part of
* @return the updated prerequisite
*/
public Prerequisite updatePrerequisite(PrerequisiteRequestDTO prerequisiteValues, long prerequisiteId, long courseId) {
var existingPrerequisite = prerequisiteRepository.findByIdAndCourseIdElseThrow(prerequisiteId, courseId);

existingPrerequisite.setTitle(prerequisiteValues.title());
existingPrerequisite.setDescription(prerequisiteValues.description());
existingPrerequisite.setTaxonomy(prerequisiteValues.taxonomy());
existingPrerequisite.setSoftDueDate(prerequisiteValues.softDueDate());
existingPrerequisite.setMasteryThreshold(prerequisiteValues.masteryThreshold());
existingPrerequisite.setOptional(prerequisiteValues.optional());

return prerequisiteRepository.save(existingPrerequisite);
}

/**
* Deletes an existing prerequisite if it is part of the given course or throws an EntityNotFoundException
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import de.tum.in.www1.artemis.repository.CompetencyProgressRepository;
import de.tum.in.www1.artemis.repository.CompetencyRelationRepository;
import de.tum.in.www1.artemis.repository.CompetencyRepository;
import de.tum.in.www1.artemis.repository.CourseCompetencyRepository;
import de.tum.in.www1.artemis.repository.CourseRepository;
import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.security.Role;
Expand Down Expand Up @@ -105,13 +106,16 @@ public class CompetencyResource {

private final CompetencyRelationService competencyRelationService;

private final CourseCompetencyRepository courseCompetencyRepository;

private final CompetencyJolService competencyJolService;

public CompetencyResource(CourseRepository courseRepository, AuthorizationCheckService authorizationCheckService, UserRepository userRepository,
CompetencyRepository competencyRepository, CompetencyRelationRepository competencyRelationRepository, CompetencyService competencyService,
CompetencyProgressRepository competencyProgressRepository, CompetencyProgressService competencyProgressService, ExerciseService exerciseService,
LectureUnitService lectureUnitService, CompetencyRelationService competencyRelationService,
Optional<IrisCompetencyGenerationSessionService> irisCompetencyGenerationSessionService, CompetencyJolService competencyJolService) {
Optional<IrisCompetencyGenerationSessionService> irisCompetencyGenerationSessionService, CourseCompetencyRepository courseCompetencyRepository,
CompetencyJolService competencyJolService) {
this.courseRepository = courseRepository;
this.competencyRelationRepository = competencyRelationRepository;
this.authorizationCheckService = authorizationCheckService;
Expand All @@ -124,6 +128,7 @@ public CompetencyResource(CourseRepository courseRepository, AuthorizationCheckS
this.lectureUnitService = lectureUnitService;
this.competencyRelationService = competencyRelationService;
this.irisCompetencyGenerationSessionService = irisCompetencyGenerationSessionService;
this.courseCompetencyRepository = courseCompetencyRepository;
this.competencyJolService = competencyJolService;
}

Expand Down Expand Up @@ -536,6 +541,19 @@ public ResponseEntity<List<Competency>> generateCompetenciesFromCourseDescriptio
return ResponseEntity.ok().body(competencies);
}

/**
* GET courses/{courseId}/competencies/titles : Returns the titles of all course competencies. Used for a validator in the client
*
* @param courseId the id of the current course
* @return the titles of all course competencies
*/
@GetMapping("courses/{courseId}/competencies/titles")
@EnforceAtLeastEditorInCourse
public ResponseEntity<List<String>> getCourseCompetencyTitles(@PathVariable Long courseId) {
final var titles = courseCompetencyRepository.findAllTitlesByCourseId(courseId);
return ResponseEntity.ok(titles);
}

/**
* Checks if the user has the necessary permissions and the competency matches the course.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -24,8 +25,10 @@
import de.tum.in.www1.artemis.security.Role;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
import de.tum.in.www1.artemis.security.annotations.enforceRoleInCourse.EnforceAtLeastEditorInCourse;
import de.tum.in.www1.artemis.security.annotations.enforceRoleInCourse.EnforceAtLeastStudentInCourse;
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.service.competency.PrerequisiteService;
import de.tum.in.www1.artemis.web.rest.dto.competency.PrerequisiteRequestDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.PrerequisiteResponseDTO;

/**
Expand Down Expand Up @@ -72,11 +75,66 @@ public ResponseEntity<List<PrerequisiteResponseDTO>> getPrerequisites(@PathVaria
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, null);
}

var prerequisites = prerequisiteRepository.findByCourseIdOrderById(courseId);
var prerequisites = prerequisiteRepository.findAllByCourseIdOrderById(courseId);

return ResponseEntity.ok(prerequisites.stream().map(PrerequisiteResponseDTO::of).toList());
}

/**
* GET /courses/:courseId/competencies/prerequisites/:prerequisiteId : Gets the prerequisite competency with the given id for a course.
*
* @param prerequisiteId the id of the prerequisite to fetch
* @param courseId the id of the course in which the prerequisite should exist
* @return the ResponseEntity with status 200 (OK) and with body the found prerequisite or with status 404 (Not Found)
*/
@GetMapping("courses/{courseId}/competencies/prerequisites/{prerequisiteId}")
@EnforceAtLeastStudentInCourse
public ResponseEntity<PrerequisiteResponseDTO> getPrerequisite(@PathVariable long prerequisiteId, @PathVariable long courseId) {
log.debug("REST request to get prerequisite with id: {}", prerequisiteId);

var prerequisite = prerequisiteRepository.findByIdAndCourseIdElseThrow(prerequisiteId, courseId);

return ResponseEntity.ok(PrerequisiteResponseDTO.of(prerequisite));
}

/**
* POST /courses/:courseId/prerequisites : creates a new prerequisite competency.
*
* @param courseId the id of the course to which the competency should be added
* @param prerequisite the prerequisite that should be created
* @return the ResponseEntity with status 201 (Created) and with body the new prerequisite
* @throws URISyntaxException if the Location URI syntax is incorrect
*/
@PostMapping("courses/{courseId}/competencies/prerequisites")
@EnforceAtLeastEditorInCourse
public ResponseEntity<PrerequisiteResponseDTO> createPrerequisite(@PathVariable long courseId, @RequestBody PrerequisiteRequestDTO prerequisite) throws URISyntaxException {
log.debug("REST request to create Prerequisite : {}", prerequisite);

final var savedPrerequisite = prerequisiteService.createPrerequisite(prerequisite, courseId);
final var uri = new URI("/api/courses/" + courseId + "/prerequisites/" + savedPrerequisite.getId());

return ResponseEntity.created(uri).body(PrerequisiteResponseDTO.of(savedPrerequisite));
}

/**
* PUT /courses/:courseId/prerequisites/:prerequisiteId : updates an existing prerequisite
*
* @param courseId the id of the course to which the prerequisite belongs
* @param prerequisiteId the id of the prerequisite to update
* @param prerequisiteValues the new prerequisite values
* @return the ResponseEntity with status 200 (OK)
*/
@PutMapping("courses/{courseId}/competencies/prerequisites/{prerequisiteId}")
@EnforceAtLeastEditorInCourse
public ResponseEntity<PrerequisiteResponseDTO> updatePrerequisite(@PathVariable long courseId, @PathVariable long prerequisiteId,
@RequestBody PrerequisiteRequestDTO prerequisiteValues) {
log.info("REST request to update Prerequisite with id : {}", prerequisiteId);

final var savedPrerequisite = prerequisiteService.updatePrerequisite(prerequisiteValues, prerequisiteId, courseId);

return ResponseEntity.ok(PrerequisiteResponseDTO.of(savedPrerequisite));
}

/**
* DELETE /courses/:courseId/prerequisites/:prerequisiteId : deletes an existing prerequisite
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package de.tum.in.www1.artemis.web.rest.dto.competency;

import java.time.ZonedDateTime;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.in.www1.artemis.domain.competency.CompetencyTaxonomy;
import de.tum.in.www1.artemis.domain.competency.CourseCompetency;
import de.tum.in.www1.artemis.domain.competency.Prerequisite;

/**
* DTO used to send create/update requests regarding {@link Prerequisite} objects.
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record PrerequisiteRequestDTO(@NotBlank @Size(min = 1, max = CourseCompetency.MAX_TITLE_LENGTH) String title, String description, CompetencyTaxonomy taxonomy,
ZonedDateTime softDueDate, int masteryThreshold, boolean optional) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
import { TranslateService } from '@ngx-translate/core';
import { LectureUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/lectureUnit.service';
import { intersection } from 'lodash-es';
import { CompetencyTaxonomy, CompetencyValidators, DEFAULT_MASTERY_THRESHOLD } from 'app/entities/competency.model';
import { CompetencyTaxonomy, CourseCompetencyValidators, DEFAULT_MASTERY_THRESHOLD } from 'app/entities/competency.model';
import { faQuestionCircle, faTimes } from '@fortawesome/free-solid-svg-icons';
import dayjs from 'dayjs/esm';

Expand All @@ -23,12 +23,9 @@ export const titleUniqueValidator = (competencyService: CompetencyService, cours
if (initialTitle && title === initialTitle) {
return of(null);
}
return competencyService.getAllForCourse(courseId).pipe(
return competencyService.getCourseCompetencyTitles(courseId).pipe(
map((res) => {
let competencyTitles: string[] = [];
if (res.body) {
competencyTitles = res.body.map((competency) => competency.title!);
}
const competencyTitles = res.body!;
if (title && competencyTitles.includes(title)) {
return {
titleUnique: { valid: false },
Expand Down Expand Up @@ -90,9 +87,8 @@ export class CompetencyFormComponent implements OnInit, OnChanges {
@Output()
onCancel: EventEmitter<any> = new EventEmitter<any>();

titleUniqueValidator = titleUniqueValidator;
protected readonly competencyTaxonomy = CompetencyTaxonomy;
protected readonly competencyValidators = CompetencyValidators;
protected readonly competencyValidators = CourseCompetencyValidators;

@Output()
formSubmitted: EventEmitter<CompetencyFormData> = new EventEmitter<CompetencyFormData>();
Expand Down Expand Up @@ -168,11 +164,7 @@ export class CompetencyFormComponent implements OnInit, OnChanges {
initialTitle = this.formData.title;
}
this.form = this.fb.nonNullable.group({
title: [
undefined as string | undefined,
[Validators.required, Validators.maxLength(255)],
[this.titleUniqueValidator(this.competencyService, this.courseId, initialTitle)],
],
title: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)], [titleUniqueValidator(this.competencyService, this.courseId, initialTitle)]],
description: [undefined as string | undefined, [Validators.maxLength(10000)]],
softDueDate: [undefined],
taxonomy: [undefined as CompetencyTaxonomy | undefined],
Expand Down
Loading

0 comments on commit 62f4132

Please sign in to comment.