Skip to content

Commit

Permalink
Learning path: Add learning path explanation for students (#8815)
Browse files Browse the repository at this point in the history
  • Loading branch information
JohannesWt authored Jun 19, 2024
1 parent 915b691 commit 9460b12
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@if (isLoading()) {
<div class="row justify-content-center m-4">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
<span class="sr-only" jhiTranslate="loading"></span>
</div>
</div>
} @else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { CompetencyGraphDTO, NodeType } from 'app/entities/competency/learning-p
import { CompetencyNodeComponent, SizeUpdate } from 'app/course/learning-paths/components/competency-node/competency-node.component';
import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service';
import { AlertService } from 'app/core/util/alert.service';
import { ArtemisSharedModule } from 'app/shared/shared.module';

@Component({
selector: 'jhi-competency-graph',
standalone: true,
imports: [CompetencyNodeComponent, NgxGraphModule],
imports: [CompetencyNodeComponent, NgxGraphModule, ArtemisSharedModule],
templateUrl: './competency-graph.component.html',
styleUrl: './competency-graph.component.scss',
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@if (isLectureUnitLoading()) {
<div class="row justify-content-center m-4">
<div class="spinner-border text-primary" role="status">
<span class="sr-only"></span>
<span class="sr-only" jhiTranslate="loading"></span>
</div>
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import { LectureUnitCompletionEvent } from 'app/overview/course-lectures/course-
import { LearningPathNavigationService } from 'app/course/learning-paths/services/learning-path-navigation.service';
import { lastValueFrom, switchMap } from 'rxjs';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import { ArtemisSharedModule } from 'app/shared/shared.module';

@Component({
selector: 'jhi-learning-path-lecture-unit',
standalone: true,
imports: [ArtemisLectureUnitsModule],
imports: [ArtemisLectureUnitsModule, ArtemisSharedModule],
templateUrl: './learning-path-lecture-unit.component.html',
})
export class LearningPathLectureUnitComponent {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
<div class="col learning-path-student-page">
@if (learningPathId()) {
@if (isLoading()) {
<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()!" />
<div class="learning-path-student-content">
@if (currentLearningObject()?.type === LearningObjectType.LECTURE) {
<jhi-learning-path-lecture-unit [lectureUnitId]="currentLearningObject()!.id" />
} @else if (currentLearningObject()?.type === LearningObjectType.EXERCISE) {
<jhi-learning-path-exercise [courseId]="courseId()" [exerciseId]="currentLearningObject()!.id" />
}
</div>
} @else {
<div class="row align-items-center justify-content-center h-100">
<div class="learning-path-generation-container">
<h3 class="mb-3" jhiTranslate="artemisApp.learningPath.generation.title"></h3>
<div jhiTranslate="artemisApp.learningPath.generation.description"></div>
<button
(click)="generateLearningPath(this.courseId())"
type="button"
class="mt-4 btn btn-primary"
id="generate-learning-path-button"
jhiTranslate="artemisApp.learningPath.generation.generateButton"
></button>
</div>
</div>
}
<div class="learning-path-student-content">
@if (currentLearningObject()?.type === LearningObjectType.LECTURE) {
<jhi-learning-path-lecture-unit [lectureUnitId]="currentLearningObject()!.id" />
} @else if (currentLearningObject()?.type === LearningObjectType.EXERCISE) {
<jhi-learning-path-exercise [courseId]="courseId()" [exerciseId]="currentLearningObject()!.id" />
}
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@
height: calc(100vh - var(--sidebar-footer-height-dev) - 10.65rem);
overflow: auto;
}

.learning-path-generation-container {
max-width: 500px;
text-align: center;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,24 @@ import { CourseExerciseDetailsModule } from 'app/overview/exercise-details/cours
import { LearningPathLectureUnitComponent } from 'app/course/learning-paths/components/learning-path-lecture-unit/learning-path-lecture-unit.component';
import { LearningPathExerciseComponent } from 'app/course/learning-paths/components/learning-path-exercise/learning-path-exercise.component';
import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service';
import { EntityNotFoundError } from 'app/course/learning-paths/exceptions/entity-not-found.error';
import { LearningPathNavigationService } from 'app/course/learning-paths/services/learning-path-navigation.service';
import { EntityNotFoundError } from 'app/course/learning-paths/exceptions/entity-not-found.error';
import { ArtemisSharedModule } from 'app/shared/shared.module';

@Component({
selector: 'jhi-learning-path-student-page',
templateUrl: './learning-path-student-page.component.html',
styleUrl: './learning-path-student-page.component.scss',
standalone: true,
imports: [CommonModule, RouterModule, LearningPathStudentNavComponent, CourseExerciseDetailsModule, LearningPathLectureUnitComponent, LearningPathExerciseComponent],
imports: [
CommonModule,
RouterModule,
LearningPathStudentNavComponent,
CourseExerciseDetailsModule,
LearningPathLectureUnitComponent,
LearningPathExerciseComponent,
ArtemisSharedModule,
],
})
export class LearningPathStudentPageComponent implements OnInit {
protected readonly LearningObjectType = LearningObjectType;
Expand All @@ -38,25 +47,29 @@ export class LearningPathStudentPageComponent implements OnInit {
}

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

private async generateLearningPath(courseId: number): Promise<void> {
async generateLearningPath(courseId: number): Promise<void> {
try {
this.isLoading.set(true);
const learningPathId = await this.learningApiService.generateLearningPath(courseId);
this.learningPathId.set(learningPathId);
} catch (error) {
this.alertService.error(error);
} finally {
this.isLoading.set(false);
}
}
}
5 changes: 5 additions & 0 deletions src/main/webapp/i18n/de/learningPath.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
},
"competencyGraphModal": {
"title": "Abhängigkeiten der Lernpfadkompetenzen"
},
"generation": {
"title": "Willkommen bei deinem Lernpfad!",
"description": "Der Lernpfad ermöglicht es dir, in deinem eigenen Tempo zu lernen, mit einer personalisierten Lernreise, die auf deine Leistung zugeschnitten ist. Sie passt sich dynamisch an und stellt sicher, dass du immer die relevantesten Inhalte und Herausforderungen erhältst. Starte jetzt und erlebe deinen individuellen Weg zum Erfolg!",
"generateButton": "Meinen Lernfpad starten"
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/main/webapp/i18n/en/learningPath.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
},
"competencyGraphModal": {
"title": "Learning path competency relations"
},
"generation": {
"title": "Welcome to your learning path!",
"description": "This feature lets you learn at your own pace with a personalized journey tailored to your performance. It adapts dynamically, ensuring you always have the most relevant content and challenges. Start now and experience a customized path to success!",
"generateButton": "Start my learning path"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { MockTranslateService } from '../../../helpers/mocks/service/mock-transl
import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service';
import { AlertService } from 'app/core/util/alert.service';
import { MockAlertService } from '../../../helpers/mocks/service/mock-alert.service';
import { EntityNotFoundError } from 'app/course/learning-paths/exceptions/entity-not-found.error';

describe('LearningPathStudentPageComponent', () => {
let component: LearningPathStudentPageComponent;
Expand Down Expand Up @@ -101,14 +102,44 @@ describe('LearningPathStudentPageComponent', () => {
expect(alertServiceErrorSpy).toHaveBeenCalledOnce();
});

it('should set isLoading correctly during load', async () => {
it('should set isLoading correctly during learning path load', async () => {
jest.spyOn(learningPathApiService, 'getLearningPathId').mockResolvedValue(learningPathId);
const loadingSpy = jest.spyOn(component.isLoading, 'set');

fixture.detectChanges();
await fixture.whenStable();

expect(loadingSpy).toHaveBeenCalledWith(true);
expect(loadingSpy).toHaveBeenCalledWith(false);
expect(loadingSpy).toHaveBeenNthCalledWith(1, true);
expect(loadingSpy).toHaveBeenNthCalledWith(2, false);
});

it('should generate learning path on click', async () => {
jest.spyOn(learningPathApiService, 'getLearningPathId').mockRejectedValue(new EntityNotFoundError());
const generateLearningPathSpy = jest.spyOn(learningPathApiService, 'generateLearningPath').mockResolvedValue(learningPathId);

fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();

const generateLearningPathButton = fixture.debugElement.query(By.css('#generate-learning-path-button'));
generateLearningPathButton.nativeElement.click();

fixture.detectChanges();
await fixture.whenStable();

expect(component.learningPathId()).toEqual(learningPathId);
expect(generateLearningPathSpy).toHaveBeenCalledWith(courseId);
});

it('should set isLoading correctly during learning path generation', async () => {
jest.spyOn(learningPathApiService, 'getLearningPathId').mockRejectedValue(new EntityNotFoundError());
jest.spyOn(learningPathApiService, 'generateLearningPath').mockResolvedValue(learningPathId);
const loadingSpy = jest.spyOn(component.isLoading, 'set');

fixture.detectChanges();
await fixture.whenStable();

expect(loadingSpy).toHaveBeenNthCalledWith(1, true);
expect(loadingSpy).toHaveBeenNthCalledWith(2, false);
});
});

0 comments on commit 9460b12

Please sign in to comment.