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

Learning paths: Add explanation view for learning path users #9392

Merged
merged 6 commits into from
Oct 1, 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 @@ -31,6 +31,12 @@ public class LearningPath extends DomainObject {
@Column(name = "progress")
private int progress;

/**
* flag indicating if a student started the learning path
*/
@Column(name = "started_by_student")
private boolean startedByStudent = false;
JohannesWt marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we move this to the future LearnerProfile, that is stored once per student? Or should they get the explanation in each course?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense for the future. @N0W0RK can you add that point to your backlog?


@ManyToOne
@JoinColumn(name = "user_id")
private User user;
Expand Down Expand Up @@ -89,8 +95,16 @@ public void removeCompetency(CourseCompetency competency) {
this.competencies.remove(competency);
}

public boolean isStartedByStudent() {
return startedByStudent;
}

public void setStartedByStudent(boolean startedByStudent) {
this.startedByStudent = startedByStudent;
}

@Override
public String toString() {
return "LearningPath{" + "id=" + getId() + ", user=" + user + ", course=" + course + ", competencies=" + competencies + '}';
return "LearningPath{" + "id=" + getId() + ", user=" + user + ", course=" + course + ", competencies=" + competencies + ", startedByStudent=" + startedByStudent + "}";
JohannesWt marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package de.tum.cit.aet.artemis.atlas.dto;

import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.atlas.domain.competency.LearningPath;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record LearningPathDTO(long id, boolean startedByStudent, int progress) {

public static LearningPathDTO of(LearningPath learningPath) {
return new LearningPathDTO(learningPath.getId(), learningPath.isStartedByStudent(), learningPath.getProgress());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import de.tum.cit.aet.artemis.atlas.dto.CompetencyGraphEdgeDTO;
import de.tum.cit.aet.artemis.atlas.dto.CompetencyGraphNodeDTO;
import de.tum.cit.aet.artemis.atlas.dto.LearningPathCompetencyGraphDTO;
import de.tum.cit.aet.artemis.atlas.dto.LearningPathDTO;
import de.tum.cit.aet.artemis.atlas.dto.LearningPathHealthDTO;
import de.tum.cit.aet.artemis.atlas.dto.LearningPathInformationDTO;
import de.tum.cit.aet.artemis.atlas.dto.LearningPathNavigationOverviewDTO;
Expand All @@ -39,6 +40,7 @@
import de.tum.cit.aet.artemis.core.dto.SearchResultPageDTO;
import de.tum.cit.aet.artemis.core.dto.pageablesearch.SearchTermPageableSearchDTO;
import de.tum.cit.aet.artemis.core.exception.AccessForbiddenException;
import de.tum.cit.aet.artemis.core.exception.ConflictException;
import de.tum.cit.aet.artemis.core.repository.CourseRepository;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
import de.tum.cit.aet.artemis.core.util.PageUtil;
Expand Down Expand Up @@ -243,6 +245,52 @@ private void updateLearningPathProgress(@NotNull LearningPath learningPath) {
log.debug("Updated LearningPath (id={}) for user (id={})", learningPath.getId(), userId);
}

/**
* Get the learning path for the current user in the given course.
*
* @param courseId the id of the course
* @return the learning path of the current user
*/
public LearningPathDTO getLearningPathForCurrentUser(long courseId) {
final var currentUser = userRepository.getUser();
final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(courseId, currentUser.getId());
return LearningPathDTO.of(learningPath);
}
Comment on lines +254 to +258
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure User Enrollment Verification Before Accessing Learning Path

In the getLearningPathForCurrentUser method, it's important to verify that the current user is enrolled in the specified course before accessing the learning path. This prevents unauthorized access to learning paths of courses the user is not enrolled in.

Apply this diff to add the enrollment check:

 public LearningPathDTO getLearningPathForCurrentUser(long courseId) {
     final var currentUser = userRepository.getUser();
+    courseRepository.checkIfUserIsEnrolledInCourseElseThrow(currentUser, courseId);
     final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(courseId, currentUser.getId());
     return LearningPathDTO.of(learningPath);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public LearningPathDTO getLearningPathForCurrentUser(long courseId) {
final var currentUser = userRepository.getUser();
final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(courseId, currentUser.getId());
return LearningPathDTO.of(learningPath);
}
public LearningPathDTO getLearningPathForCurrentUser(long courseId) {
final var currentUser = userRepository.getUser();
courseRepository.checkIfUserIsEnrolledInCourseElseThrow(currentUser, courseId);
final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(courseId, currentUser.getId());
return LearningPathDTO.of(learningPath);
}


/**
* Generate a learning path for the current user in the given course.
*
* @param courseId the id of the course
* @return the generated learning path
*/
public LearningPathDTO generateLearningPathForCurrentUser(long courseId) {
final var currentUser = userRepository.getUser();
final var course = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(courseId);
if (learningPathRepository.findByCourseIdAndUserId(courseId, currentUser.getId()).isPresent()) {
throw new ConflictException("Learning path already exists.", "LearningPath", "learningPathAlreadyExists");
}
final var learningPath = generateLearningPathForUser(course, currentUser);
return LearningPathDTO.of(learningPath);
}
Comment on lines +266 to +274
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add Enrollment Check Before Generating Learning Path

In the generateLearningPathForCurrentUser method, consider verifying that the current user is enrolled in the specified course. This ensures that only authorized users can generate learning paths for courses they are part of.

Apply this diff to include the enrollment check:

 public LearningPathDTO generateLearningPathForCurrentUser(long courseId) {
     final var currentUser = userRepository.getUser();
+    courseRepository.checkIfUserIsEnrolledInCourseElseThrow(currentUser, courseId);
     final var course = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(courseId);
     if (learningPathRepository.findByCourseIdAndUserId(courseId, currentUser.getId()).isPresent()) {
         throw new ConflictException("Learning path already exists.", "LearningPath", "learningPathAlreadyExists");
     }
     final var learningPath = generateLearningPathForUser(course, currentUser);
     return LearningPathDTO.of(learningPath);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public LearningPathDTO generateLearningPathForCurrentUser(long courseId) {
final var currentUser = userRepository.getUser();
final var course = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(courseId);
if (learningPathRepository.findByCourseIdAndUserId(courseId, currentUser.getId()).isPresent()) {
throw new ConflictException("Learning path already exists.", "LearningPath", "learningPathAlreadyExists");
}
final var learningPath = generateLearningPathForUser(course, currentUser);
return LearningPathDTO.of(learningPath);
}
public LearningPathDTO generateLearningPathForCurrentUser(long courseId) {
final var currentUser = userRepository.getUser();
courseRepository.checkIfUserIsEnrolledInCourseElseThrow(currentUser, courseId);
final var course = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(courseId);
if (learningPathRepository.findByCourseIdAndUserId(courseId, currentUser.getId()).isPresent()) {
throw new ConflictException("Learning path already exists.", "LearningPath", "learningPathAlreadyExists");
}
final var learningPath = generateLearningPathForUser(course, currentUser);
return LearningPathDTO.of(learningPath);
}


/**
* Start the learning path for the current user
*
* @param learningPathId the id of the learning path
*/
public void startLearningPathForCurrentUser(long learningPathId) {
final var learningPath = learningPathRepository.findByIdElseThrow(learningPathId);
final var currentUser = userRepository.getUser();
if (!learningPath.getUser().equals(currentUser)) {
throw new AccessForbiddenException("You are not allowed to start this learning path.");
}
else if (learningPath.isStartedByStudent()) {
pzdr7 marked this conversation as resolved.
Show resolved Hide resolved
throw new ConflictException("Learning path already started.", "LearningPath", "learningPathAlreadyStarted");
}
learningPath.setStartedByStudent(true);
learningPathRepository.save(learningPath);
}
Comment on lines +281 to +292
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Verify User Enrollment Before Starting Learning Path

While you are checking if the learning path belongs to the current user in the startLearningPathForCurrentUser method, consider also verifying that the user is enrolled in the course associated with the learning path. This adds an extra layer of security to prevent unauthorized access.

Apply this diff to add the enrollment check:

 public void startLearningPathForCurrentUser(long learningPathId) {
     final var learningPath = learningPathRepository.findByIdElseThrow(learningPathId);
     final var currentUser = userRepository.getUser();
     if (!learningPath.getUser().equals(currentUser)) {
         throw new AccessForbiddenException("You are not allowed to start this learning path.");
     }
+    if (!courseRepository.isUserEnrolledInCourse(currentUser, learningPath.getCourse().getId())) {
+        throw new AccessForbiddenException("You are not enrolled in this course.");
+    }
     else if (learningPath.isStartedByStudent()) {
         throw new ConflictException("Learning path already started.", "LearningPath", "learningPathAlreadyStarted");
     }
     learningPath.setStartedByStudent(true);
     learningPathRepository.save(learningPath);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void startLearningPathForCurrentUser(long learningPathId) {
final var learningPath = learningPathRepository.findByIdElseThrow(learningPathId);
final var currentUser = userRepository.getUser();
if (!learningPath.getUser().equals(currentUser)) {
throw new AccessForbiddenException("You are not allowed to start this learning path.");
}
else if (learningPath.isStartedByStudent()) {
throw new ConflictException("Learning path already started.", "LearningPath", "learningPathAlreadyStarted");
}
learningPath.setStartedByStudent(true);
learningPathRepository.save(learningPath);
}
public void startLearningPathForCurrentUser(long learningPathId) {
final var learningPath = learningPathRepository.findByIdElseThrow(learningPathId);
final var currentUser = userRepository.getUser();
if (!learningPath.getUser().equals(currentUser)) {
throw new AccessForbiddenException("You are not allowed to start this learning path.");
}
if (!courseRepository.isUserEnrolledInCourse(currentUser, learningPath.getCourse().getId())) {
throw new AccessForbiddenException("You are not enrolled in this course.");
}
else if (learningPath.isStartedByStudent()) {
throw new ConflictException("Learning path already started.", "LearningPath", "learningPathAlreadyStarted");
}
learningPath.setStartedByStudent(true);
learningPathRepository.save(learningPath);
}


/**
* Gets the health status of learning paths for the given course.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.springframework.context.annotation.Profile;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
Expand All @@ -28,6 +29,7 @@
import de.tum.cit.aet.artemis.atlas.dto.CompetencyNameDTO;
import de.tum.cit.aet.artemis.atlas.dto.CompetencyProgressForLearningPathDTO;
import de.tum.cit.aet.artemis.atlas.dto.LearningPathCompetencyGraphDTO;
import de.tum.cit.aet.artemis.atlas.dto.LearningPathDTO;
import de.tum.cit.aet.artemis.atlas.dto.LearningPathHealthDTO;
import de.tum.cit.aet.artemis.atlas.dto.LearningPathInformationDTO;
import de.tum.cit.aet.artemis.atlas.dto.LearningPathNavigationDTO;
Expand Down Expand Up @@ -301,19 +303,31 @@ private ResponseEntity<NgxLearningPathDTO> getLearningPathNgx(@PathVariable long
}

/**
* GET courses/:courseId/learning-path-id : Gets the id of the learning path.
* GET courses/:courseId/learning-path/me : Gets the learning path of the current user in the course.
*
* @param courseId the id of the course from which the learning path id should be fetched
* @return the ResponseEntity with status 200 (OK) and with body the id of the learning path
* @param courseId the id of the course for which the learning path should be fetched
* @return the ResponseEntity with status 200 (OK) and with body the learning path
*/
@GetMapping("courses/{courseId}/learning-path-id")
@GetMapping("courses/{courseId}/learning-path/me")
@EnforceAtLeastStudentInCourse
public ResponseEntity<Long> getLearningPathId(@PathVariable long courseId) {
log.debug("REST request to get learning path id for course with id: {}", courseId);
public ResponseEntity<LearningPathDTO> getLearningPathForCurrentUser(@PathVariable long courseId) {
log.debug("REST request to get learning path of current user for course with id: {}", courseId);
courseService.checkLearningPathsEnabledElseThrow(courseId);
User user = userRepository.getUser();
final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(courseId, user.getId());
return ResponseEntity.ok(learningPath.getId());
return ResponseEntity.ok(learningPathService.getLearningPathForCurrentUser(courseId));
}
JohannesWt marked this conversation as resolved.
Show resolved Hide resolved

/**
* PATCH learning-path/:learningPathId/start : Starts the learning path for the current user.
*
* @param learningPathId the id of the learning path to start
* @return the ResponseEntity with status 204 (NO_CONTENT)
*/
@PatchMapping("learning-path/{learningPathId}/start")
@EnforceAtLeastStudent
public ResponseEntity<Void> startLearningPathForCurrentUser(@PathVariable long learningPathId) {
log.debug("REST request to start learning path with id: {}", learningPathId);
learningPathService.startLearningPathForCurrentUser(learningPathId);
return ResponseEntity.noContent().build();
JohannesWt marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -324,20 +338,11 @@ public ResponseEntity<Long> getLearningPathId(@PathVariable long courseId) {
*/
@PostMapping("courses/{courseId}/learning-path")
@EnforceAtLeastStudentInCourse
public ResponseEntity<Long> generateLearningPath(@PathVariable long courseId) throws URISyntaxException {
log.debug("REST request to generate learning path for user in course with id: {}", courseId);
public ResponseEntity<LearningPathDTO> generateLearningPathForCurrentUser(@PathVariable long courseId) throws URISyntaxException {
log.debug("REST request to generate learning path for current user in course with id: {}", courseId);
courseService.checkLearningPathsEnabledElseThrow(courseId);

User user = userRepository.getUser();
final var learningPathOptional = learningPathRepository.findByCourseIdAndUserId(courseId, user.getId());

if (learningPathOptional.isPresent()) {
throw new BadRequestException("Learning path already exists.");
}

final var course = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(courseId);
final var learningPath = learningPathService.generateLearningPathForUser(course, user);
return ResponseEntity.created(new URI("api/learning-path/" + learningPath.getId())).body(learningPath.getId());
final var learningPathDTO = learningPathService.generateLearningPathForCurrentUser(courseId);
return ResponseEntity.created(new URI("api/learning-path/" + learningPathDTO.id())).body(learningPathDTO);
JohannesWt marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="20240924125742" author="johannes_wt">
<addColumn tableName="learning_path">
<column name="started_by_student" defaultValueBoolean="false" type="boolean">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
2 changes: 1 addition & 1 deletion src/main/resources/config/liquibase/master.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<include file="classpath:config/liquibase/changelog/20240708144500_changelog.xml" relativeToChangelogFile="false"/>
<include file="classpath:config/liquibase/changelog/20240802091201_changelog.xml" relativeToChangelogFile="false"/>
<include file="classpath:config/liquibase/changelog/20240804144500_changelog.xml" relativeToChangelogFile="false"/>

<include file="classpath:config/liquibase/changelog/20240924125742_changelog.xml" relativeToChangelogFile="false"/>
JohannesWt marked this conversation as resolved.
Show resolved Hide resolved
<!-- NOTE: please use the format "YYYYMMDDhhmmss_changelog.xml", i.e. year month day hour minutes seconds and not something else! -->
<!-- we should also stay in a chronological order! -->
<!-- you can use the command 'date '+%Y%m%d%H%M%S'' to get the current date and time in the correct format -->
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/app/admin/metrics/metrics.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export enum HttpMethod {
Post = 'POST',
Get = 'GET',
Delete = 'DELETE',
Patch = 'PATCH',
JohannesWt marked this conversation as resolved.
Show resolved Hide resolved
}

export interface ProcessMetrics {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<div class="col learning-path-student-page">
@if (isLearningPathIdLoading()) {
@if (isLearningPathLoading()) {
<div class="row justify-content-center align-items-center h-100">
<div class="spinner-border text-primary" role="status">
<span class="sr-only" jhiTranslate="loading"></span>
</div>
</div>
} @else if (learningPathId()) {
<jhi-learning-path-student-nav [learningPathId]="learningPathId()!" />
} @else if (learningPath() && learningPath()!.startedByStudent) {
<jhi-learning-path-student-nav [learningPathId]="learningPath()!.id" />
<div class="learning-path-student-content">
@if (currentLearningObject()?.type === LearningObjectType.LECTURE) {
<jhi-learning-path-lecture-unit [lectureUnitId]="currentLearningObject()!.id" />
Expand All @@ -27,10 +27,10 @@ <h3 class="mb-3" jhiTranslate="artemisApp.learningPath.completion.title"></h3>
<h3 class="mb-3" jhiTranslate="artemisApp.learningPath.generation.title"></h3>
<div jhiTranslate="artemisApp.learningPath.generation.description"></div>
<button
(click)="generateLearningPath(this.courseId())"
(click)="startLearningPath()"
type="button"
class="mt-4 btn btn-primary"
id="generate-learning-path-button"
id="start-learning-path-button"
jhiTranslate="artemisApp.learningPath.generation.generateButton"
></button>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, effect, inject, signal } from '@angular/core';
import { LearningObjectType } from 'app/entities/competency/learning-path.model';
import { LearningObjectType, LearningPathDTO } from 'app/entities/competency/learning-path.model';
import { map } from 'rxjs';
import { toSignal } from '@angular/core/rxjs-interop';
import { CommonModule } from '@angular/common';
Expand Down Expand Up @@ -32,45 +32,49 @@ import { ArtemisSharedModule } from 'app/shared/shared.module';
export class LearningPathStudentPageComponent {
protected readonly LearningObjectType = LearningObjectType;

private readonly learningApiService: LearningPathApiService = inject(LearningPathApiService);
private readonly learningApiService = inject(LearningPathApiService);
private readonly learningPathNavigationService = inject(LearningPathNavigationService);
private readonly alertService: AlertService = inject(AlertService);
private readonly activatedRoute: ActivatedRoute = inject(ActivatedRoute);
private readonly alertService = inject(AlertService);
private readonly activatedRoute = inject(ActivatedRoute);

readonly isLearningPathIdLoading = signal(false);
readonly learningPathId = signal<number | undefined>(undefined);
readonly isLearningPathLoading = signal(false);
readonly learningPath = signal<LearningPathDTO | undefined>(undefined);
readonly courseId = toSignal(this.activatedRoute.parent!.parent!.params.pipe(map((params) => Number(params.courseId))), { requireSync: true });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid using non-null assertion operators (!).

The use of non-null assertions on this.activatedRoute.parent!.parent! can lead to runtime errors if parent is null or undefined. It's safer to use optional chaining or add null checks to ensure robustness.

Consider updating the code as follows:

readonly courseId = toSignal(
    this.activatedRoute.parent?.parent?.params.pipe(map((params) => Number(params.courseId))),
    { requireSync: true }
);
🧰 Tools
🪛 Biome

[error] 42-42: Forbidden non-null assertion.

Unsafe fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator

(lint/style/noNonNullAssertion)


[error] 42-42: Forbidden non-null assertion.

Unsafe fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator

(lint/style/noNonNullAssertion)

readonly currentLearningObject = this.learningPathNavigationService.currentLearningObject;
readonly isLearningPathNavigationLoading = this.learningPathNavigationService.isLoading;

constructor() {
effect(async () => await this.loadLearningPathId(this.courseId()), { allowSignalWrites: true });
effect(() => this.loadLearningPath(this.courseId()), { allowSignalWrites: true });
}

private async loadLearningPathId(courseId: number): Promise<void> {
private async loadLearningPath(courseId: number): Promise<void> {
try {
this.isLearningPathIdLoading.set(true);
const learningPathId = await this.learningApiService.getLearningPathId(courseId);
this.learningPathId.set(learningPathId);
this.isLearningPathLoading.set(true);
const learningPath = await this.learningApiService.getLearningPathForCurrentUser(courseId);
this.learningPath.set(learningPath);
} catch (error) {
// If learning path does not exist (404) ignore the error
if (!(error instanceof EntityNotFoundError)) {
this.alertService.error(error);
}
} finally {
this.isLearningPathIdLoading.set(false);
this.isLearningPathLoading.set(false);
}
}

async generateLearningPath(courseId: number): Promise<void> {
async startLearningPath(): Promise<void> {
try {
this.isLearningPathIdLoading.set(true);
const learningPathId = await this.learningApiService.generateLearningPath(courseId);
this.learningPathId.set(learningPathId);
this.isLearningPathLoading.set(true);
if (!this.learningPath()) {
const learningPath = await this.learningApiService.generateLearningPathForCurrentUser(this.courseId());
this.learningPath.set(learningPath);
}
await this.learningApiService.startLearningPathForCurrentUser(this.learningPath()!.id);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle potential undefined for this.learningPath().

Using this.learningPath()! assumes that learningPath is not undefined, which might not always be the case. This could result in runtime errors if learningPath is undefined.

Modify the code to include a null check:

const learningPath = this.learningPath();
if (learningPath) {
    await this.learningApiService.startLearningPathForCurrentUser(learningPath.id);
}
🧰 Tools
🪛 Biome

[error] 72-72: Forbidden non-null assertion.

Unsafe fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator

(lint/style/noNonNullAssertion)

this.learningPath.update((learningPath) => ({ ...learningPath!, startedByStudent: true }));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure safe usage of learningPath in the update function.

Inside the update function, using learningPath! assumes that learningPath is not undefined. To prevent potential runtime errors, handle the possibility of learningPath being undefined.

Update the code as follows:

this.learningPath.update((learningPath) =>
    learningPath ? { ...learningPath, startedByStudent: true } : learningPath
);
🧰 Tools
🪛 Biome

[error] 73-73: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

} catch (error) {
this.alertService.error(error);
} finally {
this.isLearningPathIdLoading.set(false);
this.isLearningPathLoading.set(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export abstract class BaseApiHttpService {
* @param url The endpoint URL excluding the base server url (/api).
* @param options The HTTP options to send with the request.
*
* @return An `Promise` of the response body of type `T`.
* @return A `Promise` of the response body of type `T`.
*/
private async request<T>(
method: HttpMethod,
Expand Down Expand Up @@ -73,7 +73,7 @@ export abstract class BaseApiHttpService {
* @param options The HTTP options to send with the request.
* @protected
*
* @return An `Promise` of type `Object` (T),
* @return A `Promise` of type `Object` (T),
*/
protected async get<T>(
url: string,
Expand Down Expand Up @@ -102,7 +102,7 @@ export abstract class BaseApiHttpService {
* @param options The HTTP options to send with the request.
* @protected
*
* @return An `Promise` of type `Object` (T),
* @return A `Promise` of type `Object` (T),
*/
protected async post<T>(
url: string,
Expand All @@ -122,4 +122,34 @@ export abstract class BaseApiHttpService {
): Promise<T> {
return await this.request<T>(HttpMethod.Post, url, { body: body, ...options });
}

/**
* Constructs a `PATCH` request that interprets the body as JSON and
* returns a Promise of an object of type `T`.
*
* @param url The endpoint URL excluding the base server url (/api).
* @param body The content to include in the body of the request.
* @param options The HTTP options to send with the request.
* @protected
*
* @return A `Promise` of type `Object` (T),
*/
protected async patch<T>(
url: string,
body?: any,
options?: {
headers?:
| HttpHeaders
| {
[header: string]: string | string[];
};
params?:
| HttpParams
| {
[param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>;
};
},
): Promise<T> {
return await this.request<T>(HttpMethod.Patch, url, { body: body, ...options });
}
}
Loading
Loading