Learning Pa
- {{ learningPath.masteredCompetencies }}
+
diff --git a/src/main/webapp/app/entities/learning-path.model.ts b/src/main/webapp/app/entities/learning-path.model.ts
index 3c8646260f0d..b36aac4a5f73 100644
--- a/src/main/webapp/app/entities/learning-path.model.ts
+++ b/src/main/webapp/app/entities/learning-path.model.ts
@@ -5,7 +5,7 @@ import { Competency } from 'app/entities/competency.model';
export class LearningPath implements BaseEntity {
public id?: number;
- public masteredCompetencies?: number;
+ public progress?: number;
public user?: User;
public course?: Course;
public competencies?: Competency[];
From f6f4a0dacf470d806739d6b77577c47cf72c628f Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 29 Jun 2023 15:33:13 +0200
Subject: [PATCH 032/215] learning path service and test
---
.../learning-paths/learning-path.service.ts | 3 +--
.../service/learning-path.service.spec.ts | 24 +++++++++++++++++++
2 files changed, 25 insertions(+), 2 deletions(-)
create mode 100644 src/test/javascript/spec/service/learning-path.service.spec.ts
diff --git a/src/main/webapp/app/course/learning-paths/learning-path.service.ts b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
index fa64567c62ee..5cf49c2c8115 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path.service.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
@@ -10,7 +10,6 @@ export class LearningPathService {
constructor(private httpClient: HttpClient) {}
enableLearningPaths(courseId: number): Observable> {
- // return this.httpClient.put(`${this.resourceURL}/courses/${courseId}/learning-paths/enable`, { observe: 'response' })
- return new Observable();
+ return this.httpClient.put(`${this.resourceURL}/courses/${courseId}/learning-paths/enable`, null, { observe: 'response' });
}
}
diff --git a/src/test/javascript/spec/service/learning-path.service.spec.ts b/src/test/javascript/spec/service/learning-path.service.spec.ts
new file mode 100644
index 000000000000..1d46f57ce2ae
--- /dev/null
+++ b/src/test/javascript/spec/service/learning-path.service.spec.ts
@@ -0,0 +1,24 @@
+import { MockHttpService } from '../helpers/mocks/service/mock-http.service';
+import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
+
+describe('LearningPathService', () => {
+ let learningPathService: LearningPathService;
+ let httpService: MockHttpService;
+ let putStub: jest.SpyInstance;
+
+ beforeEach(() => {
+ httpService = new MockHttpService();
+ learningPathService = new LearningPathService(httpService);
+ putStub = jest.spyOn(httpService, 'put');
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should send a request to the server to activate the user', () => {
+ learningPathService.enableLearningPaths(1).subscribe();
+ expect(putStub).toHaveBeenCalledOnce();
+ expect(putStub).toHaveBeenCalledWith('api/courses/1/learning-paths/enable', null, { observe: 'response' });
+ });
+});
From 3c6d997871aff777b008206607f459ffb4b790e9 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 29 Jun 2023 15:50:16 +0200
Subject: [PATCH 033/215] add test for paging service
---
.../learning-path-paging.service.spec.ts | 40 +++++++++++++++++++
1 file changed, 40 insertions(+)
create mode 100644 src/test/javascript/spec/service/learning-path-paging.service.spec.ts
diff --git a/src/test/javascript/spec/service/learning-path-paging.service.spec.ts b/src/test/javascript/spec/service/learning-path-paging.service.spec.ts
new file mode 100644
index 000000000000..15acf56fc044
--- /dev/null
+++ b/src/test/javascript/spec/service/learning-path-paging.service.spec.ts
@@ -0,0 +1,40 @@
+import { MockHttpService } from '../helpers/mocks/service/mock-http.service';
+import { LearningPathPagingService } from 'app/course/learning-paths/learning-path-paging.service';
+import { PageableSearch, SortingOrder } from 'app/shared/table/pageable-table';
+import { HttpParams } from '@angular/common/http';
+import { TableColumn } from 'app/course/learning-paths/learning-path-management/learning-path-management.component';
+
+describe('LearningPathPagingService', () => {
+ let learningPathPagingService: LearningPathPagingService;
+ let httpService: MockHttpService;
+ let getStub: jest.SpyInstance;
+
+ beforeEach(() => {
+ httpService = new MockHttpService();
+ learningPathPagingService = new LearningPathPagingService(httpService);
+ getStub = jest.spyOn(httpService, 'get');
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should send a request to the server to activate the user', () => {
+ const pageable = {
+ page: 1,
+ pageSize: 10,
+ searchTerm: 'initialSearchTerm',
+ sortingOrder: SortingOrder.DESCENDING,
+ sortedColumn: TableColumn.ID,
+ } as PageableSearch;
+ learningPathPagingService.searchForLearningPaths(pageable, 1).subscribe();
+ const params = new HttpParams()
+ .set('pageSize', String(pageable.pageSize))
+ .set('page', String(pageable.page))
+ .set('sortingOrder', pageable.sortingOrder)
+ .set('searchTerm', pageable.searchTerm)
+ .set('sortedColumn', pageable.sortedColumn);
+ expect(getStub).toHaveBeenCalledOnce();
+ expect(getStub).toHaveBeenCalledWith('api/courses/1/learning-paths', { params, observe: 'response' });
+ });
+});
From 2aef85c135bd0c9262ef59e824d8c7f96c6fb518 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 29 Jun 2023 15:51:49 +0200
Subject: [PATCH 034/215] allow loading of learning paths
---
.../learning-path-management.component.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index b89c29944c61..430a3639fe95 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -127,7 +127,10 @@ export class LearningPathManagementComponent implements OnInit, OnDestroy {
});
if (this.course?.learningPathEnabled) {
- // TODO: load learning paths of students
+ this.content = { resultsOnPage: [], numberOfPages: 0 };
+
+ this.performSearch(this.sort, 0);
+ this.performSearch(this.search, 300);
}
this.isLoading = false;
From 7ae59ff88b1e643a4b823efeef154d54aeb99b4b Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 29 Jun 2023 15:52:07 +0200
Subject: [PATCH 035/215] add tests for management component
---
...learning-path-management.component.spec.ts | 126 ++++++++++++++++++
1 file changed, 126 insertions(+)
create mode 100644 src/test/javascript/spec/component/learning-paths/learning-path-management.component.spec.ts
diff --git a/src/test/javascript/spec/component/learning-paths/learning-path-management.component.spec.ts b/src/test/javascript/spec/component/learning-paths/learning-path-management.component.spec.ts
new file mode 100644
index 000000000000..d661106eb0a2
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/learning-path-management.component.spec.ts
@@ -0,0 +1,126 @@
+import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { LearningPathManagementComponent, TableColumn } from 'app/course/learning-paths/learning-path-management/learning-path-management.component';
+import { LearningPathPagingService } from 'app/course/learning-paths/learning-path-paging.service';
+import { SortService } from 'app/shared/service/sort.service';
+import { PageableSearch, SearchResult, SortingOrder } from 'app/shared/table/pageable-table';
+import { LearningPath } from 'app/entities/learning-path.model';
+import { ArtemisTestModule } from '../../test.module';
+import { MockComponent, MockDirective } from 'ng-mocks';
+import { ButtonComponent } from 'app/shared/components/button.component';
+import { NgbPagination } from '@ng-bootstrap/ng-bootstrap';
+import { SortByDirective } from 'app/shared/sort/sort-by.directive';
+import { SortDirective } from 'app/shared/sort/sort.directive';
+import { of } from 'rxjs';
+import { CourseManagementService } from 'app/course/manage/course-management.service';
+import { Course } from 'app/entities/course.model';
+
+describe('LearningPathManagementComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LearningPathManagementComponent;
+ let courseManagementService: CourseManagementService;
+ let findCourseStub: jest.SpyInstance;
+ let pagingService: LearningPathPagingService;
+ let sortService: SortService;
+ let searchForLearningPathsStub: jest.SpyInstance;
+ let sortByPropertyStub: jest.SpyInstance;
+ let searchResult: SearchResult;
+ let state: PageableSearch;
+ let learningPath: LearningPath;
+ let course: Course;
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, MockComponent(NgbPagination)],
+ declarations: [LearningPathManagementComponent, MockComponent(ButtonComponent), MockDirective(SortByDirective), MockDirective(SortDirective)],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathManagementComponent);
+ comp = fixture.componentInstance;
+ courseManagementService = TestBed.inject(CourseManagementService);
+ findCourseStub = jest.spyOn(courseManagementService, 'find');
+ pagingService = TestBed.inject(LearningPathPagingService);
+ sortService = TestBed.inject(SortService);
+ searchForLearningPathsStub = jest.spyOn(pagingService, 'searchForLearningPaths');
+ sortByPropertyStub = jest.spyOn(sortService, 'sortByProperty');
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ beforeEach(() => {
+ fixture.detectChanges();
+ course = new Course();
+ course.learningPathEnabled = true;
+ findCourseStub.mockReturnValue(of(course));
+ learningPath = new LearningPath();
+ learningPath.id = 1;
+ searchResult = { numberOfPages: 3, resultsOnPage: [learningPath] };
+ state = {
+ page: 1,
+ pageSize: 10,
+ searchTerm: 'initialSearchTerm',
+ sortingOrder: SortingOrder.DESCENDING,
+ sortedColumn: TableColumn.ID,
+ ...searchResult,
+ };
+ searchForLearningPathsStub.mockReturnValue(of(searchResult));
+ });
+
+ const setStateAndCallOnInit = (middleExpectation: () => void) => {
+ comp.state = { ...state };
+ comp.ngOnInit();
+ middleExpectation();
+ expect(comp.content).toEqual(searchResult);
+ comp.sortRows();
+ expect(sortByPropertyStub).toHaveBeenCalledWith(searchResult.resultsOnPage, comp.sortedColumn, comp.listSorting);
+ };
+
+ it('should set content to paging result on sort', fakeAsync(() => {
+ expect(comp.listSorting).toBeFalse();
+ setStateAndCallOnInit(() => {
+ comp.listSorting = true;
+ tick(10);
+ expect(searchForLearningPathsStub).toHaveBeenCalledWith({ ...state, sortingOrder: SortingOrder.ASCENDING });
+ expect(comp.listSorting).toBeTrue();
+ });
+ }));
+
+ it('should set content to paging result on pageChange', fakeAsync(() => {
+ expect(comp.page).toBe(1);
+ setStateAndCallOnInit(() => {
+ comp.onPageChange(5);
+ tick(10);
+ expect(searchForLearningPathsStub).toHaveBeenCalledWith({ ...state, page: 5 });
+ expect(comp.page).toBe(5);
+ });
+ }));
+
+ it('should set content to paging result on search', fakeAsync(() => {
+ expect(comp.searchTerm).toBe('');
+ setStateAndCallOnInit(() => {
+ const givenSearchTerm = 'givenSearchTerm';
+ comp.searchTerm = givenSearchTerm;
+ tick(10);
+ expect(searchForLearningPathsStub).not.toHaveBeenCalled();
+ tick(290);
+ expect(searchForLearningPathsStub).toHaveBeenCalledWith({ ...state, searchTerm: givenSearchTerm });
+ expect(comp.searchTerm).toEqual(givenSearchTerm);
+ });
+ }));
+
+ it('should set content to paging result on sortedColumn change', fakeAsync(() => {
+ expect(comp.sortedColumn).toEqual(TableColumn.ID);
+ setStateAndCallOnInit(() => {
+ comp.sortedColumn = TableColumn.USER_LOGIN;
+ tick(10);
+ expect(searchForLearningPathsStub).toHaveBeenCalledWith({ ...state, sortedColumn: TableColumn.USER_LOGIN });
+ expect(comp.sortedColumn).toEqual(TableColumn.USER_LOGIN);
+ });
+ }));
+
+ it('should return competency id', () => {
+ expect(comp.trackId(0, learningPath)).toEqual(learningPath.id);
+ });
+});
From 883d3c72545ba24b6339bd2e4fd941738231daaa Mon Sep 17 00:00:00 2001
From: MaximilianAnzinger
Date: Thu, 29 Jun 2023 18:28:44 +0200
Subject: [PATCH 036/215] test learning path update
---
.../CompetencyProgressRepository.java | 2 +-
.../artemis/service/LearningPathService.java | 2 +-
.../lecture/LearningPathIntegrationTest.java | 57 ++++++++-----------
3 files changed, 27 insertions(+), 34 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/CompetencyProgressRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/CompetencyProgressRepository.java
index 17227016cdb4..691a488d3f8c 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/CompetencyProgressRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/CompetencyProgressRepository.java
@@ -56,7 +56,7 @@ public interface CompetencyProgressRepository extends JpaRepository findAllByCompetencyIdsAndUserId(@Param("competencyId") Set competencyIds, @Param("userId") Long userId);
+ List findAllByCompetencyIdsAndUserId(@Param("competencyIds") Set competencyIds, @Param("userId") Long userId);
@Query("""
SELECT AVG(cp.confidence)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index ab26eac1eabf..e144c9dea3e1 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -126,7 +126,7 @@ private void updateLearningPathProgress(final LearningPath learningPath) {
// TODO: consider optional competencies
final var completed = (float) competencyProgresses.stream().filter(CompetencyProgressService::isMastered).count();
- learningPath.setProgress(Math.round(completed / (float) learningPath.getCompetencies().size()));
+ learningPath.setProgress(Math.round(completed * 100 / (float) learningPath.getCompetencies().size()));
learningPathRepository.save(learningPath);
log.debug("Updated LearningPath (id={}) for user (id={})", learningPath.getId(), userId);
}
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index a3ee3f1a45c2..de8b35ff1679 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -21,11 +21,14 @@
import de.tum.in.www1.artemis.domain.*;
import de.tum.in.www1.artemis.domain.competency.Competency;
import de.tum.in.www1.artemis.domain.competency.LearningPath;
+import de.tum.in.www1.artemis.domain.lecture.TextUnit;
import de.tum.in.www1.artemis.exercise.ExerciseUtilService;
import de.tum.in.www1.artemis.exercise.textexercise.TextExerciseUtilService;
import de.tum.in.www1.artemis.participation.ParticipationUtilService;
import de.tum.in.www1.artemis.repository.*;
+import de.tum.in.www1.artemis.service.CompetencyProgressService;
import de.tum.in.www1.artemis.service.LearningPathService;
+import de.tum.in.www1.artemis.service.LectureUnitService;
import de.tum.in.www1.artemis.user.UserUtilService;
import de.tum.in.www1.artemis.util.PageableSearchUtilService;
@@ -75,12 +78,20 @@ class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbuck
@Autowired
GradingCriterionRepository gradingCriterionRepository;
+ @Autowired
+ LectureUnitService lectureUnitService;
+
+ @Autowired
+ CompetencyProgressService competencyProgressService;
+
private Course course;
private Competency[] competencies;
private TextExercise textExercise;
+ private TextUnit textUnit;
+
private final int NUMBER_OF_STUDENTS = 5;
private final String STUDENT_OF_COURSE = TEST_PREFIX + "student1";
@@ -95,8 +106,6 @@ class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbuck
@BeforeEach
void setupTestScenario() throws Exception {
- participantScoreScheduleService.activate();
-
userUtilService.addUsers(TEST_PREFIX, NUMBER_OF_STUDENTS, 1, 1, 1);
// Add users that are not in the course
@@ -116,9 +125,11 @@ void setupTestScenario() throws Exception {
lecture.setCourse(course);
lectureRepository.save(lecture);
- final var textUnit = lectureUtilService.createTextUnit();
+ textUnit = lectureUtilService.createTextUnit();
lectureUtilService.addLectureUnitsToLecture(lecture, List.of(textUnit));
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ lectureUnitService.setLectureUnitCompletion(textUnit, student, true);
}
private void enableLearningPathsForTestingCourse() {
@@ -150,6 +161,7 @@ private Competency createCompetencyRESTCall() throws Exception {
final var competencyToCreate = new Competency();
competencyToCreate.setTitle("CompetencyToCreateTitle");
competencyToCreate.setCourse(course);
+ competencyToCreate.setLectureUnits(Set.of(textUnit));
return request.postWithResponseBody("/api/courses/" + course.getId() + "/competencies", competencyToCreate, Competency.class, HttpStatus.CREATED);
}
@@ -308,39 +320,20 @@ void testRemoveCompetencyFromLearningPathsOnDeleteCompetency() throws Exception
@Test
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
- void testUpdateLearningPathProgressOnCreateCompetency() throws Exception {
+ void testUpdateLearningPathProgress() throws Exception {
enableLearningPathsForTestingCourse();
- final var createdCompetency = createCompetencyRESTCall();
- // TODO
- }
- @Test
- @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
- void testUpdateLearningPathProgressOnUpdateCompetency() throws Exception {
- enableLearningPathsForTestingCourse();
- // TODO
- }
+ // add competency with completed learning unit
+ final var createdCompetency = createCompetencyRESTCall();
- @Test
- @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
- void testUpdateLearningPathProgressOnImportCompetency() throws Exception {
- enableLearningPathsForTestingCourse();
- final var importedCompetency = importCompetencyRESTCall();
- // TODO
- }
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ var learningPath = learningPathRepository.findWithEagerCompetenciesByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ assertThat(learningPath.getProgress()).as("contains no completed competency").isEqualTo(0);
- @Test
- @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
- void testUpdateLearningPathProgressOnDeleteCompetency() throws Exception {
- enableLearningPathsForTestingCourse();
- deleteCompetencyRESTCall(competencies[0]);
- // TODO
- }
+ // force update to avoid waiting for scheduler
+ competencyProgressService.updateCompetencyProgress(createdCompetency.getId(), student);
- @Test
- @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
- void testUpdateLearningPathProgress() {
- enableLearningPathsForTestingCourse();
- // TODO
+ learningPath = learningPathRepository.findWithEagerCompetenciesByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ assertThat(learningPath.getProgress()).as("contains completed competency").isNotEqualTo(0);
}
}
From 80f41643a84bccdb18212513f0da9d94209dfb61 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 30 Jun 2023 12:27:24 +0200
Subject: [PATCH 037/215] Update learning-path-management.component.ts
---
.../learning-path-management.component.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index 430a3639fe95..205ec27ba25c 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -155,6 +155,9 @@ export class LearningPathManagementComponent implements OnInit, OnDestroy {
}),
)
.subscribe({
+ next: (res) => {
+ this.course = res.body!;
+ },
error: (res: HttpErrorResponse) => onError(this.alertService, res),
});
}
From 50d5cc23f9615a9c25ab13eeb56d91b4b09da46a Mon Sep 17 00:00:00 2001
From: MaximilianAnzinger
Date: Mon, 3 Jul 2023 09:22:27 +0200
Subject: [PATCH 038/215] fix endpoint
---
.../www1/artemis/repository/CourseRepository.java | 8 ++++++++
.../in/www1/artemis/web/rest/CourseResource.java | 15 +++++++++++++++
.../learning-path-management.component.ts | 2 +-
.../course/manage/course-management.service.ts | 10 ++++++++++
4 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
index 3e3fe087d540..7464acbe3777 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
@@ -154,6 +154,9 @@ SELECT CASE WHEN (count(c) > 0) THEN true ELSE false END
@EntityGraph(type = LOAD, attributePaths = { "competencies", "prerequisites" })
Optional findWithEagerCompetenciesById(long courseId);
+ @EntityGraph(type = LOAD, attributePaths = { "learningPaths" })
+ Optional findWithEagerLearningPathsById(long courseId);
+
@EntityGraph(type = LOAD, attributePaths = { "competencies", "learningPaths", "learningPaths.competencies" })
Optional findWithEagerLearningPathsAndCompetenciesById(long courseId);
@@ -434,6 +437,11 @@ default Course findWithEagerCompetenciesByIdElseThrow(long courseId) {
return findWithEagerCompetenciesById(courseId).orElseThrow(() -> new EntityNotFoundException("Course", courseId));
}
+ @NotNull
+ default Course findWithEagerLearningPathsByIdElseThrow(long courseId) {
+ return findWithEagerLearningPathsById(courseId).orElseThrow(() -> new EntityNotFoundException("Course", courseId));
+ }
+
@NotNull
default Course findWithEagerLearningPathsAndCompetenciesByIdElseThrow(long courseId) {
return findWithEagerLearningPathsAndCompetenciesById(courseId).orElseThrow(() -> new EntityNotFoundException("Course", courseId));
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
index 6a87454ca3b7..9d7e0048246d 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
@@ -629,6 +629,21 @@ public ResponseEntity getCourseWithOrganizations(@PathVariable Long cour
return ResponseEntity.ok(course);
}
+ /**
+ * GET /courses/:courseId/with-learning-paths Get a course by id with eagerly loaded learning paths
+ *
+ * @param courseId the id of the course
+ * @return the course with eagerly loaded learning paths
+ */
+ @GetMapping("courses/{courseId}/with-learning-paths")
+ @EnforceAtLeastInstructor
+ public ResponseEntity getCourseWithLearningPaths(@PathVariable Long courseId) {
+ log.debug("REST request to get a course with its organizations : {}", courseId);
+ Course course = courseRepository.findWithEagerLearningPathsByIdElseThrow(courseId);
+ authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
+ return ResponseEntity.ok(course);
+ }
+
/**
* GET /courses/:courseId/lockedSubmissions Get locked submissions for course for user
*
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index 205ec27ba25c..4cb66827b72c 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -122,7 +122,7 @@ export class LearningPathManagementComponent implements OnInit, OnDestroy {
private loadData() {
this.isLoading = true;
- this.courseSub = this.courseManagementService.find(this.courseId).subscribe((courseResponse) => {
+ this.courseSub = this.courseManagementService.findWithLearningPaths(this.courseId).subscribe((courseResponse) => {
this.course = courseResponse.body!;
});
diff --git a/src/main/webapp/app/course/manage/course-management.service.ts b/src/main/webapp/app/course/manage/course-management.service.ts
index 68cd769eaca9..15c01e3788ca 100644
--- a/src/main/webapp/app/course/manage/course-management.service.ts
+++ b/src/main/webapp/app/course/manage/course-management.service.ts
@@ -134,6 +134,16 @@ export class CourseManagementService {
.pipe(map((res: EntityResponseType) => this.processCourseEntityResponseType(res)));
}
+ /**
+ * finds a course with the given id and eagerly loaded learning pahts
+ * @param courseId the id of the course to be found
+ */
+ findWithLearningPaths(courseId: number): Observable {
+ return this.http
+ .get(`${this.resourceUrl}/${courseId}/with-learning-paths`, { observe: 'response' })
+ .pipe(map((res: EntityResponseType) => this.processCourseEntityResponseType(res)));
+ }
+
// TODO: separate course overview and course management REST API calls in a better way
/**
* finds all courses using a GET request
From a1b99437cb90b3037112f4fabf4eea1f136b1d1e Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 3 Jul 2023 10:19:39 +0200
Subject: [PATCH 039/215] add LearningPathUtilService
---
.../competency/LearningPathUtilService.java | 26 +++++++++++++++++++
.../lecture/LearningPathIntegrationTest.java | 25 ++++++++----------
2 files changed, 37 insertions(+), 14 deletions(-)
create mode 100644 src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
diff --git a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
new file mode 100644
index 000000000000..7d5d3e7ebbdd
--- /dev/null
+++ b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
@@ -0,0 +1,26 @@
+package de.tum.in.www1.artemis.competency;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.tum.in.www1.artemis.domain.Course;
+import de.tum.in.www1.artemis.repository.CourseRepository;
+import de.tum.in.www1.artemis.service.LearningPathService;
+
+@Service
+public class LearningPathUtilService {
+
+ @Autowired
+ CourseRepository courseRepository;
+
+ @Autowired
+ LearningPathService learningPathService;
+
+ public Course enableAndGenerateLearningPathsForCourse(Course course) {
+ course = courseRepository.findWithEagerLearningPathsAndCompetenciesByIdElseThrow(course.getId());
+ learningPathService.generateLearningPaths(course);
+ course.setLeanringPathsEnabled(true);
+ return courseRepository.save(course);
+ }
+
+}
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index de8b35ff1679..d9325a99e640 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -17,6 +17,7 @@
import de.tum.in.www1.artemis.AbstractSpringIntegrationBambooBitbucketJiraTest;
import de.tum.in.www1.artemis.competency.CompetencyUtilService;
+import de.tum.in.www1.artemis.competency.LearningPathUtilService;
import de.tum.in.www1.artemis.course.CourseUtilService;
import de.tum.in.www1.artemis.domain.*;
import de.tum.in.www1.artemis.domain.competency.Competency;
@@ -84,6 +85,9 @@ class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbuck
@Autowired
CompetencyProgressService competencyProgressService;
+ @Autowired
+ LearningPathUtilService learningPathUtilService;
+
private Course course;
private Competency[] competencies;
@@ -132,13 +136,6 @@ void setupTestScenario() throws Exception {
lectureUnitService.setLectureUnitCompletion(textUnit, student, true);
}
- private void enableLearningPathsForTestingCourse() {
- course = courseRepository.findWithEagerLearningPathsAndCompetenciesByIdElseThrow(course.getId());
- learningPathService.generateLearningPaths(course);
- course.setLeanringPathsEnabled(true);
- course = courseRepository.save(course);
- }
-
private ZonedDateTime now() {
return ZonedDateTime.now();
}
@@ -225,7 +222,7 @@ void testGenerateLearningPathOnEnrollment() throws Exception {
course.setEnrollmentStartDate(past(1));
course.setEnrollmentEndDate(future(1));
course = courseRepository.save(course);
- enableLearningPathsForTestingCourse();
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
this.setupEnrollmentRequestMocks();
@@ -253,7 +250,7 @@ void testGetLearningPathsOnPageForCourseLearningPathsDisabled() throws Exception
@Test
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
void testGetLearningPathsOnPageForCourseEmpty() throws Exception {
- enableLearningPathsForTestingCourse();
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
final var search = pageableSearchUtilService.configureSearch(STUDENT_OF_COURSE + "SuffixThatAllowsTheResultToBeEmpty");
final var result = request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.OK, LearningPath.class,
pageableSearchUtilService.searchMapping(search));
@@ -263,7 +260,7 @@ void testGetLearningPathsOnPageForCourseEmpty() throws Exception {
@Test
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
void testGetLearningPathsOnPageForCourseExactlyStudent() throws Exception {
- enableLearningPathsForTestingCourse();
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
final var search = pageableSearchUtilService.configureSearch(STUDENT_OF_COURSE);
final var result = request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.OK, LearningPath.class,
pageableSearchUtilService.searchMapping(search));
@@ -273,7 +270,7 @@ void testGetLearningPathsOnPageForCourseExactlyStudent() throws Exception {
@Test
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
void testAddCompetencyToLearningPathsOnCreateCompetency() throws Exception {
- enableLearningPathsForTestingCourse();
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
final var createdCompetency = createCompetencyRESTCall();
@@ -289,7 +286,7 @@ void testAddCompetencyToLearningPathsOnCreateCompetency() throws Exception {
@Test
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
void testAddCompetencyToLearningPathsOnImportCompetency() throws Exception {
- enableLearningPathsForTestingCourse();
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
final var importedCompetency = importCompetencyRESTCall();
@@ -305,7 +302,7 @@ void testAddCompetencyToLearningPathsOnImportCompetency() throws Exception {
@Test
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
void testRemoveCompetencyFromLearningPathsOnDeleteCompetency() throws Exception {
- enableLearningPathsForTestingCourse();
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
deleteCompetencyRESTCall(competencies[0]);
@@ -321,7 +318,7 @@ void testRemoveCompetencyFromLearningPathsOnDeleteCompetency() throws Exception
@Test
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
void testUpdateLearningPathProgress() throws Exception {
- enableLearningPathsForTestingCourse();
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
// add competency with completed learning unit
final var createdCompetency = createCompetencyRESTCall();
From 757ad9c9db122aa40651291654443c587e5457a0 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 3 Jul 2023 10:53:36 +0200
Subject: [PATCH 040/215] improve documentation
---
.../competency/CompetencyUtilService.java | 20 +++++++++++++++++++
.../competency/LearningPathUtilService.java | 9 +++++++++
2 files changed, 29 insertions(+)
diff --git a/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java b/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java
index c78015566a40..fbe30bd68071 100644
--- a/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java
+++ b/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java
@@ -16,6 +16,13 @@ public class CompetencyUtilService {
@Autowired
private CompetencyRepository competencyRepo;
+ /**
+ * Creates competency and links it to the course. The title of the competency will hold the specified suffix.
+ *
+ * @param course course the competency will be linked to
+ * @param suffix the suffix that will be included in the title
+ * @return the persisted competency
+ */
private Competency createCompetency(Course course, String suffix) {
Competency competency = new Competency();
competency.setTitle("Example Competency" + suffix);
@@ -24,10 +31,23 @@ private Competency createCompetency(Course course, String suffix) {
return competencyRepo.save(competency);
}
+ /**
+ * Creates competency and links it to the course.
+ *
+ * @param course course the competency will be linked to
+ * @return the persisted competency
+ */
public Competency createCompetency(Course course) {
return createCompetency(course, "");
}
+ /**
+ * Creates multiple competencies and links them to the course
+ *
+ * @param course course the competencies will be linked to
+ * @param numberOfCompetencies number of competencies to create
+ * @return array of the persisted competencies
+ */
public Competency[] createCompetencies(Course course, int numberOfCompetencies) {
Competency[] competencies = new Competency[numberOfCompetencies];
for (int i = 0; i < competencies.length; i++) {
diff --git a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
index 7d5d3e7ebbdd..e05ba4c31dfb 100644
--- a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
+++ b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
@@ -7,6 +7,9 @@
import de.tum.in.www1.artemis.repository.CourseRepository;
import de.tum.in.www1.artemis.service.LearningPathService;
+/**
+ * Service responsible for initializing the database with specific testdata related to learning paths for use in integration tests.
+ */
@Service
public class LearningPathUtilService {
@@ -16,6 +19,12 @@ public class LearningPathUtilService {
@Autowired
LearningPathService learningPathService;
+ /**
+ * Enable and generate learning paths for course.
+ *
+ * @param course the course for which the learning paths are generated
+ * @return the updated course
+ */
public Course enableAndGenerateLearningPathsForCourse(Course course) {
course = courseRepository.findWithEagerLearningPathsAndCompetenciesByIdElseThrow(course.getId());
learningPathService.generateLearningPaths(course);
From 6dc60d54e47798bce1f330949c2a66d868dd9321 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 3 Jul 2023 11:06:09 +0200
Subject: [PATCH 041/215] Fix compiler error
---
.../learning-path-paging.service.spec.ts | 19 ++++++++++++++-----
.../service/learning-path.service.spec.ts | 18 ++++++++++++++----
2 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/src/test/javascript/spec/service/learning-path-paging.service.spec.ts b/src/test/javascript/spec/service/learning-path-paging.service.spec.ts
index 15acf56fc044..d8fdbe59a6e0 100644
--- a/src/test/javascript/spec/service/learning-path-paging.service.spec.ts
+++ b/src/test/javascript/spec/service/learning-path-paging.service.spec.ts
@@ -1,18 +1,27 @@
import { MockHttpService } from '../helpers/mocks/service/mock-http.service';
import { LearningPathPagingService } from 'app/course/learning-paths/learning-path-paging.service';
import { PageableSearch, SortingOrder } from 'app/shared/table/pageable-table';
-import { HttpParams } from '@angular/common/http';
+import { HttpClient, HttpParams } from '@angular/common/http';
import { TableColumn } from 'app/course/learning-paths/learning-path-management/learning-path-management.component';
+import { ArtemisTestModule } from '../test.module';
+import { TestBed } from '@angular/core/testing';
describe('LearningPathPagingService', () => {
let learningPathPagingService: LearningPathPagingService;
- let httpService: MockHttpService;
+ let httpService: HttpClient;
let getStub: jest.SpyInstance;
beforeEach(() => {
- httpService = new MockHttpService();
- learningPathPagingService = new LearningPathPagingService(httpService);
- getStub = jest.spyOn(httpService, 'get');
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule],
+ providers: [{ provide: HttpClient, useClass: MockHttpService }],
+ })
+ .compileComponents()
+ .then(() => {
+ httpService = TestBed.inject(HttpClient);
+ learningPathPagingService = new LearningPathPagingService(httpService);
+ getStub = jest.spyOn(httpService, 'get');
+ });
});
afterEach(() => {
diff --git a/src/test/javascript/spec/service/learning-path.service.spec.ts b/src/test/javascript/spec/service/learning-path.service.spec.ts
index 1d46f57ce2ae..0f7241ac4099 100644
--- a/src/test/javascript/spec/service/learning-path.service.spec.ts
+++ b/src/test/javascript/spec/service/learning-path.service.spec.ts
@@ -1,15 +1,25 @@
import { MockHttpService } from '../helpers/mocks/service/mock-http.service';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
+import { ArtemisTestModule } from '../test.module';
+import { HttpClient } from '@angular/common/http';
+import { TestBed } from '@angular/core/testing';
describe('LearningPathService', () => {
let learningPathService: LearningPathService;
- let httpService: MockHttpService;
+ let httpService: HttpClient;
let putStub: jest.SpyInstance;
beforeEach(() => {
- httpService = new MockHttpService();
- learningPathService = new LearningPathService(httpService);
- putStub = jest.spyOn(httpService, 'put');
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule],
+ providers: [{ provide: HttpClient, useClass: MockHttpService }],
+ })
+ .compileComponents()
+ .then(() => {
+ httpService = TestBed.inject(HttpClient);
+ learningPathService = new LearningPathService(httpService);
+ putStub = jest.spyOn(httpService, 'put');
+ });
});
afterEach(() => {
From 59e37850eedc099c111c2941f36608fcfeb0717a Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 3 Jul 2023 11:53:43 +0200
Subject: [PATCH 042/215] Fix update for LPs without competencies
---
.../artemis/service/LearningPathService.java | 18 ++++++++++++++++--
.../lecture/LearningPathIntegrationTest.java | 18 +++++++++++++++++-
2 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index e144c9dea3e1..b6f9e800d25d 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -65,13 +65,15 @@ public void generateLearningPathForUser(@NotNull Course course, @NotNull User us
lpToCreate.getCompetencies().addAll(course.getCompetencies());
var persistedLearningPath = learningPathRepository.save(lpToCreate);
log.debug("Created LearningPath (id={}) for user (id={}) in course (id={})", persistedLearningPath.getId(), user.getId(), course.getId());
+ updateLearningPathProgress(persistedLearningPath);
}
}
/**
* Search for all learning paths fitting a {@link PageableSearchDTO search query}. The result is paged.
*
- * @param search The search query defining the search term and the size of the returned page
+ * @param search the search query defining the search term and the size of the returned page
+ * @param course the course the learning paths are linked to
* @return A wrapper object containing a list of all found learning paths and the total number of pages
*/
public SearchResultPageDTO getAllOfCourseOnPageWithSize(final PageableSearchDTO search, final Course course) {
@@ -114,11 +116,22 @@ public void updateLearningPathProgress(final long learningPathId) {
this.updateLearningPathProgress(learningPath);
}
+ /**
+ * Updates progress of the learning path specified by course and user id.
+ *
+ * @param courseId id of the course the learning path is linked to
+ * @param userId id of the user the learning path is linked to
+ */
public void updateLearningPathProgress(final long courseId, final long userId) {
final var learningPath = learningPathRepository.findWithEagerCompetenciesByCourseIdAndUserId(courseId, userId);
learningPath.ifPresent(this::updateLearningPathProgress);
}
+ /**
+ * Updates progress of the given learning path. Competencies of the learning path must be loaded eagerly.
+ *
+ * @param learningPath learning path that is updated
+ */
private void updateLearningPathProgress(final LearningPath learningPath) {
final var userId = learningPath.getUser().getId();
final var competencyIds = learningPath.getCompetencies().stream().map(Competency::getId).collect(Collectors.toSet());
@@ -126,7 +139,8 @@ private void updateLearningPathProgress(final LearningPath learningPath) {
// TODO: consider optional competencies
final var completed = (float) competencyProgresses.stream().filter(CompetencyProgressService::isMastered).count();
- learningPath.setProgress(Math.round(completed * 100 / (float) learningPath.getCompetencies().size()));
+ final var numberOfCompetencies = learningPath.getCompetencies().size();
+ learningPath.setProgress(numberOfCompetencies == 0 ? 0 : Math.round(completed * 100 / (float) numberOfCompetencies));
learningPathRepository.save(learningPath);
log.debug("Updated LearningPath (id={}) for user (id={})", learningPath.getId(), userId);
}
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index d9325a99e640..675f5261d494 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -154,6 +154,10 @@ private void testAllPreAuthorize() throws Exception {
request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.FORBIDDEN, LearningPath.class, pageableSearchUtilService.searchMapping(search));
}
+ private Course enableLearningPathsRESTCall(Course course) throws Exception {
+ return request.putWithResponseBody("/api/courses/" + course.getId() + "/learning-paths/enable", course, Course.class, HttpStatus.OK);
+ }
+
private Competency createCompetencyRESTCall() throws Exception {
final var competencyToCreate = new Competency();
competencyToCreate.setTitle("CompetencyToCreateTitle");
@@ -198,7 +202,7 @@ void testAll_asEditor() throws Exception {
@Test
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
void testEnableLearningPaths() throws Exception {
- request.putWithResponseBody("/api/courses/" + course.getId() + "/learning-paths/enable", course, Course.class, HttpStatus.OK);
+ enableLearningPathsRESTCall(course);
final var updatedCourse = courseRepository.findWithEagerLearningPathsAndCompetenciesByIdElseThrow(course.getId());
assertThat(updatedCourse.getLearningPathsEnabled()).as("should enable LearningPaths").isTrue();
assertThat(updatedCourse.getLearningPaths()).isNotNull();
@@ -207,6 +211,18 @@ void testEnableLearningPaths() throws Exception {
lp -> assertThat(lp.getCompetencies().size()).as("LearningPath (id={}) should have be linked to all Competencies", lp.getId()).isEqualTo(competencies.length));
}
+ @Test
+ @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
+ void testEnableLearningPathsWithNoCompetencies() throws Exception {
+ var courseWithoutCompetencies = courseUtilService.createCoursesWithExercisesAndLectures(TEST_PREFIX, false, false, 0).get(0);
+ enableLearningPathsRESTCall(courseWithoutCompetencies);
+ final var updatedCourse = courseRepository.findWithEagerLearningPathsAndCompetenciesByIdElseThrow(courseWithoutCompetencies.getId());
+ assertThat(updatedCourse.getLearningPathsEnabled()).as("should enable LearningPaths").isTrue();
+ assertThat(updatedCourse.getLearningPaths()).isNotNull();
+ assertThat(updatedCourse.getLearningPaths().size()).as("should create LearningPath for each student").isEqualTo(NUMBER_OF_STUDENTS);
+ updatedCourse.getLearningPaths().forEach(lp -> assertThat(lp.getProgress()).as("LearningPath (id={}) should have no progress", lp.getId()).isEqualTo(0));
+ }
+
@Test
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
void testEnableLearningPathsAlreadyEnabled() throws Exception {
From 618892f787427e68f822c34d89885ac538255854 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Tue, 4 Jul 2023 15:44:52 +0200
Subject: [PATCH 043/215] Improve search and test coverage
---
.../domain/competency/LearningPath.java | 4 +--
.../artemis/repository/CourseRepository.java | 10 +++++--
.../repository/LearningPathRepository.java | 4 +--
.../artemis/service/LearningPathService.java | 2 +-
.../learning-path-management.component.html | 4 +--
.../learning-path-management.component.ts | 19 +++++++-------
src/main/webapp/app/entities/course.model.ts | 4 ++-
.../artemis/course/CourseTestService.java | 26 +++++++++++++++++++
...rseBitbucketBambooJiraIntegrationTest.java | 6 +++++
.../CourseGitlabJenkinsIntegrationTest.java | 6 +++++
10 files changed, 65 insertions(+), 20 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/competency/LearningPath.java b/src/main/java/de/tum/in/www1/artemis/domain/competency/LearningPath.java
index f6a0bb270386..c3897b5e7f46 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/competency/LearningPath.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/competency/LearningPath.java
@@ -8,7 +8,6 @@
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
-import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import de.tum.in.www1.artemis.domain.Course;
@@ -25,7 +24,6 @@ public class LearningPath extends DomainObject {
@ManyToOne
@JoinColumn(name = "user_id")
- @JsonIgnore
private User user;
@ManyToOne
@@ -85,7 +83,7 @@ public String toString() {
public enum LearningPathSearchColumn {
- ID("id"), STUDENT_LOGIN("student.login");
+ ID("id"), USER_LOGIN("user.login"), USER_NAME("user.lastName"), PROGRESS("progress");
private final String mappedColumnName;
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
index baed6adf8030..ec762da4231f 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
@@ -141,8 +141,14 @@ SELECT CASE WHEN (count(c) > 0) THEN true ELSE false END
@EntityGraph(type = LOAD, attributePaths = { "competencies", "prerequisites" })
Optional findWithEagerCompetenciesById(long courseId);
- @EntityGraph(type = LOAD, attributePaths = { "learningPaths" })
- Optional findWithEagerLearningPathsById(long courseId);
+ @Query("""
+ SELECT c
+ FROM Course c
+ LEFT JOIN FETCH c.learningPaths lp
+ LEFT JOIN FETCH lp.user
+ WHERE c.id = :courseId
+ """)
+ Optional findWithEagerLearningPathsById(@Param("courseId") long courseId);
@EntityGraph(type = LOAD, attributePaths = { "competencies", "learningPaths", "learningPaths.competencies" })
Optional findWithEagerLearningPathsAndCompetenciesById(long courseId);
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
index 262a40536eff..f029baec0790 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
@@ -40,9 +40,9 @@ default LearningPath findWithEagerCompetenciesByCourseIdAndUserIdElseThrow(long
@Query("""
SELECT lp
FROM LearningPath lp
- WHERE (lp.course.id = :courseId) AND (lp.user.login LIKE %:partialLogin%)
+ WHERE (lp.course.id = :courseId) AND (lp.user.login LIKE %:searchTerm% OR lp.user.firstName LIKE %:searchTerm% OR lp.user.lastName LIKE %:searchTerm%)
""")
- Page findByLoginInCourse(@Param("partialLogin") String partialLogin, @Param("courseId") long courseId, Pageable pageable);
+ Page findByLoginOrNameInCourse(@Param("searchTerm") String searchTerm, @Param("courseId") long courseId, Pageable pageable);
@EntityGraph(type = LOAD, attributePaths = { "competencies" })
Optional findWithEagerCompetenciesById(long learningPathId);
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index b6f9e800d25d..fe1b81e6815c 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -79,7 +79,7 @@ public void generateLearningPathForUser(@NotNull Course course, @NotNull User us
public SearchResultPageDTO getAllOfCourseOnPageWithSize(final PageableSearchDTO search, final Course course) {
final var pageable = PageUtil.createLearningPathPageRequest(search);
final var searchTerm = search.getSearchTerm();
- final Page learningPathPage = learningPathRepository.findByLoginInCourse(searchTerm, course.getId(), pageable);
+ final Page learningPathPage = learningPathRepository.findByLoginOrNameInCourse(searchTerm, course.getId(), pageable);
return new SearchResultPageDTO<>(learningPathPage.getContent(), learningPathPage.getTotalPages());
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
index b53219e7bc09..f4e59a47562c 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
@@ -6,7 +6,7 @@
Learning Path Management
-
+
-
+
- content more content far more content
-
-
-
+
+
+
+
+
+ No task selected
+
+
+
+
+
+
-
discussions discussions discussions
- Refresh
+ Previous
- Refresh
+ Next
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
index faa0184d4efd..ee3f4b6d5e5d 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
@@ -1,37 +1,79 @@
-import { Component, Input } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
import { Exercise } from 'app/entities/exercise.model';
-import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
+import { LectureUnit, LectureUnitType } from 'app/entities/lecture-unit/lectureUnit.model';
import { Lecture } from 'app/entities/lecture.model';
+import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
+import { RecommendationType } from 'app/entities/learning-path.model';
+import { LectureService } from 'app/lecture/lecture.service';
+import { onError } from 'app/shared/util/global.utils';
+import { HttpErrorResponse } from '@angular/common/http';
+import { AlertService } from 'app/core/util/alert.service';
+import { isCommunicationEnabled, isMessagingEnabled } from 'app/entities/course.model';
+import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
@Component({
selector: 'jhi-learning-path-container',
templateUrl: './learning-path-container.component.html',
})
-export class LearningPathContainerComponent {
+export class LearningPathContainerComponent implements OnInit {
@Input()
courseId: number;
+ learningPathId: number;
+ learningObjectId: number;
+ lectureId?: number;
lecture: Lecture | undefined;
lectureUnit: LectureUnit | undefined;
exercise: Exercise | undefined;
- history: any[] = [];
+ history: [number, number][] = [];
// icons
faChevronLeft = faChevronLeft;
faChevronRight = faChevronRight;
- constructor() {}
+ discussionComponent?: DiscussionSectionComponent;
+
+ constructor(
+ private router: Router,
+ private activatedRoute: ActivatedRoute,
+ private learningPathService: LearningPathService,
+ private lectureService: LectureService,
+ private alertService: AlertService,
+ ) {}
+
+ ngOnInit() {
+ console.log('ON INIT CONTAINER');
+ if (!this.courseId) {
+ this.activatedRoute.parent!.parent!.params.subscribe((params) => {
+ this.courseId = +params['courseId'];
+ });
+ }
+ this.learningPathService.getLearningPathId(this.courseId).subscribe((learningPathIdResponse) => {
+ this.learningPathId = learningPathIdResponse.body!;
+ });
+ }
onNextTask() {
if (this.lectureUnit?.id) {
- this.history.push(this.lectureUnit.id);
- this.lectureUnit = undefined;
+ this.history.push([this.lectureUnit.id, this.lectureId!]);
} else if (this.exercise?.id) {
- this.history.push(this.exercise.id);
- this.lectureUnit = undefined;
+ this.history.push([this.exercise.id, -1]);
}
+ this.undefineAll();
console.log('request next task');
+ console.log(this.courseId);
+ this.learningPathService.getRecommendation(this.learningPathId).subscribe((recommendationResponse) => {
+ const recommendation = recommendationResponse.body!;
+ this.learningObjectId = recommendation.learningObjectId;
+ this.lectureId = recommendation.lectureId;
+ if (recommendation.type == RecommendationType.LECTURE_UNIT) {
+ this.loadLectureUnit();
+ } else if (recommendation.type === RecommendationType.EXERCISE) {
+ this.loadExercise();
+ }
+ });
}
undefineAll() {
@@ -45,21 +87,51 @@ export class LearningPathContainerComponent {
const task = this.history.pop();
if (!task) {
return;
- } else if (task instanceof LectureUnit) {
- this.lectureUnit = task;
- this.loadLectureUnit();
} else {
- this.exercise = task;
- this.loadExercise();
+ this.learningObjectId = task[0];
+ this.lectureId = task[1];
+ if (task[1] == -1) {
+ this.loadExercise();
+ } else {
+ this.loadLectureUnit();
+ }
}
console.log('request previous task');
}
loadLectureUnit() {
- console.log('load lecture unit');
+ console.log('loading lecture unit');
+ this.lectureService.findWithDetails(this.lectureId!).subscribe({
+ next: (findLectureResult) => {
+ this.lecture = findLectureResult.body!;
+ if (this.lecture?.lectureUnits) {
+ this.lectureUnit = this.lecture.lectureUnits.find((lectureUnit) => lectureUnit.id === this.learningObjectId);
+ }
+ },
+ error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
+ });
}
loadExercise() {
console.log('load exercise');
}
+
+ /**
+ * This function gets called if the router outlet gets activated. This is
+ * used only for the DiscussionComponent
+ * @param instance The component instance
+ */
+ onChildActivate(instance: DiscussionSectionComponent) {
+ console.log(instance);
+ this.discussionComponent = instance; // save the reference to the component instance
+ if (this.lecture) {
+ instance.lecture = this.lecture;
+ instance.isCommunicationPage = false;
+ }
+ // todo exercise
+ }
+
+ protected readonly isMessagingEnabled = isMessagingEnabled;
+ protected readonly isCommunicationEnabled = isCommunicationEnabled;
+ protected readonly LectureUnitType = LectureUnitType;
}
diff --git a/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component.ts b/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component.ts
index 2bb1981d3dcf..af97c18bd9f7 100644
--- a/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component.ts
@@ -16,11 +16,8 @@ export interface LectureUnitCompletionEvent {
templateUrl: './learning-path-lecture-unit-view.component.html',
})
export class LearningPathLectureUnitViewComponent {
- @Input()
- lecture: Lecture;
- @Input()
- lectureUnit: LectureUnit;
-
+ @Input() lecture: Lecture;
+ @Input() lectureUnit: LectureUnit;
readonly LectureUnitType = LectureUnitType;
constructor(private lectureUnitService: LectureUnitService, private alertService: AlertService) {}
diff --git a/src/main/webapp/app/entities/learning-path.model.ts b/src/main/webapp/app/entities/learning-path.model.ts
index 025a723c292d..dc2390022442 100644
--- a/src/main/webapp/app/entities/learning-path.model.ts
+++ b/src/main/webapp/app/entities/learning-path.model.ts
@@ -46,3 +46,15 @@ export enum NodeType {
EXERCISE,
LECTURE_UNIT,
}
+
+export class LearningPathRecommendation {
+ public learningObjectId: number;
+ public lectureId?: number;
+ public type: RecommendationType;
+}
+
+export enum RecommendationType {
+ EMPTY = 'EMPTY',
+ LECTURE_UNIT = 'LECTURE_UNIT',
+ EXERCISE = 'EXERCISE',
+}
diff --git a/src/main/webapp/app/overview/course-learning-path/course-learning-path.component.ts b/src/main/webapp/app/overview/course-learning-path/course-learning-path.component.ts
index b6b3e0cb779d..c9d342f129c1 100644
--- a/src/main/webapp/app/overview/course-learning-path/course-learning-path.component.ts
+++ b/src/main/webapp/app/overview/course-learning-path/course-learning-path.component.ts
@@ -13,8 +13,8 @@ export class CourseLearningPathComponent implements OnInit {
constructor(private activatedRoute: ActivatedRoute) {}
ngOnInit(): void {
- this.activatedRoute.parent?.parent?.params.subscribe((params) => {
- this.courseId = parseInt(params['courseId'], 10);
+ this.activatedRoute.parent!.parent!.params.subscribe((params) => {
+ this.courseId = +params['courseId'];
});
}
}
diff --git a/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts b/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
index c4cc4e431a3f..9c544455b565 100644
--- a/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
+++ b/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
@@ -66,11 +66,16 @@ export class DiscussionSectionComponent extends CourseDiscussionDirective implem
params: this.activatedRoute.params,
queryParams: this.activatedRoute.queryParams,
}).subscribe((routeParams: { params: Params; queryParams: Params }) => {
+ console.log(routeParams.params);
this.currentPostId = +routeParams.queryParams.postId;
this.course = this.exercise?.course ?? this.lecture?.course;
this.metisService.setCourse(this.course);
this.metisService.setPageType(this.pageType);
- this.setChannel(routeParams.params.courseId);
+ if (routeParams.params.courseId) {
+ this.setChannel(routeParams.params.courseId);
+ } else if (this.course?.id) {
+ this.setChannel(this.course.id);
+ }
this.createEmptyPost();
this.resetFormGroup();
});
diff --git a/src/main/webapp/i18n/de/competency.json b/src/main/webapp/i18n/de/competency.json
index bd9fa4e9c696..74db1b570c43 100644
--- a/src/main/webapp/i18n/de/competency.json
+++ b/src/main/webapp/i18n/de/competency.json
@@ -168,6 +168,11 @@
"hide": "Lernpfad ausbleden",
"show": "Lernpfad einblenden",
"header": "Lernpfad"
+ },
+ "participate": {
+ "noTaskSelected": "Aktuell hast du keine Vorlesungseinheit oder Aufgabe ausgewählt.",
+ "prev": "Zurück",
+ "next": "Nächste"
}
}
}
diff --git a/src/main/webapp/i18n/en/competency.json b/src/main/webapp/i18n/en/competency.json
index bd778500d71f..3b7cb1d04568 100644
--- a/src/main/webapp/i18n/en/competency.json
+++ b/src/main/webapp/i18n/en/competency.json
@@ -167,6 +167,11 @@
"hide": "Hide Learning Path",
"show": "Show Learning Path",
"header": "Learning Path"
+ },
+ "participate": {
+ "noTaskSelected": "Currently you have no lecture unit or exercise selected.",
+ "prev": "Previous",
+ "next": "Next"
}
}
}
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 41388e7844b6..09bb6d32cccf 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -270,4 +270,31 @@ private static NgxLearningPathDTO.Node getNodeForExercise(Competency competency,
return new NgxLearningPathDTO.Node(LearningPathService.getExerciseNodeId(competency.getId(), exercise.getId()), NgxLearningPathDTO.NodeType.EXERCISE, exercise.getId(),
exercise.getTitle());
}
+
+ @Nested
+ class RecommendationTest {
+
+ @BeforeEach
+ void setAuthorizationForRepositoryRequests() {
+ SecurityUtils.setAuthorizationObject();
+ }
+
+ @Test
+ void testGetRecommendationEmpty() {
+ competencyUtilService.createCompetency(course);
+ LearningPath learningPath = learningPathUtilService.createLearningPathInCourse(course);
+ learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningUnitsByIdElseThrow(learningPath.getId());
+ assertThat(learningPathService.getRecommendation(learningPath)).isNull();
+ }
+
+ @Test
+ void testGetRecommendationNotEmpty() {
+ var competency = competencyUtilService.createCompetency(course);
+ final var lectureUnit = lectureUtilService.createTextUnit();
+ competencyUtilService.linkLectureUnitToCompetency(competency, lectureUnit);
+ LearningPath learningPath = learningPathUtilService.createLearningPathInCourse(course);
+ learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningUnitsByIdElseThrow(learningPath.getId());
+ assertThat(learningPathService.getRecommendation(learningPath)).isNotNull();
+ }
+ }
}
From a8d3fad104f55295fbfbd0fa88f649ceba7af3b3 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 17 Jul 2023 10:39:50 +0200
Subject: [PATCH 053/215] clean up
---
.../learning-path-graph/learning-path-graph.component.html | 4 ++--
.../participate/learning-path-container.component.html | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
index 7da8df136dd7..7ea68e33cc8f 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
@@ -26,14 +26,14 @@
-
{{ node.label }} asdff
+
{{ node.label }}
- asdf
+
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
index 1e146fa566c3..7f89d3c701aa 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
@@ -3,7 +3,7 @@
-
+
From 454f7881b34b23cb15399f08f0279745dcc2c4c1 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 17 Jul 2023 16:27:41 +0200
Subject: [PATCH 054/215] update routing
---
.../learning-paths/learning-paths.module.ts | 36 +++++++++------
.../learning-path-container.component.html | 19 ++------
.../learning-path-container.component.ts | 46 +++++++++++++------
...ning-path-lecture-unit-view.component.html | 17 +++++++
...arning-path-lecture-unit-view.component.ts | 21 +++++++++
.../learning-path-lecture-unit-view.module.ts | 33 +++++++++++++
...ning-path-lecture-unit-view.component.html | 6 ---
.../learning-path-lecture-unit-view.module.ts | 11 -----
.../course-exercise-details.component.ts | 10 ++--
9 files changed, 135 insertions(+), 64 deletions(-)
create mode 100644 src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.html
rename src/main/webapp/app/course/learning-paths/participate/{lectureunit => lecture-unit}/learning-path-lecture-unit-view.component.ts (63%)
create mode 100644 src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.module.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component.html
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.module.ts
diff --git a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
index 5d0740f39807..103e810f6a2a 100644
--- a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
@@ -11,7 +11,6 @@ import { LearningPathGraphSidebarComponent } from 'app/course/learning-paths/par
import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph-node.component';
import { NgxGraphModule } from '@swimlane/ngx-graph';
-import { ArtemisLearningPathLectureUnitViewModule } from 'app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.module';
import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
const routes: Routes = [
@@ -25,25 +24,36 @@ const routes: Routes = [
canActivate: [UserRouteAccessService],
children: [
{
- path: '',
+ path: 'lecture-unit',
pathMatch: 'full',
- loadChildren: () => import('app/overview/discussion-section/discussion-section.module').then((m) => m.DiscussionSectionModule),
+ children: [
+ {
+ path: '',
+ pathMatch: 'full',
+ loadChildren: () =>
+ import('app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.module').then(
+ (m) => m.ArtemisLearningPathLectureUnitViewModule,
+ ),
+ },
+ ],
+ },
+ {
+ path: 'exercise',
+ pathMatch: 'full',
+ children: [
+ {
+ path: '',
+ pathMatch: 'full',
+ loadChildren: () => import('app/overview/exercise-details/course-exercise-details.module').then((m) => m.CourseExerciseDetailsModule),
+ },
+ ],
},
],
},
];
@NgModule({
- imports: [
- ArtemisSharedModule,
- FormsModule,
- ReactiveFormsModule,
- ArtemisSharedComponentModule,
- NgxGraphModule,
- ArtemisLearningPathLectureUnitViewModule,
- RouterModule.forChild(routes),
- ArtemisLectureUnitsModule,
- ],
+ imports: [ArtemisSharedModule, FormsModule, ReactiveFormsModule, ArtemisSharedComponentModule, NgxGraphModule, RouterModule.forChild(routes), ArtemisLectureUnitsModule],
declarations: [LearningPathContainerComponent, LearningPathManagementComponent, LearningPathGraphSidebarComponent, LearningPathGraphComponent, LearningPathGraphNodeComponent],
exports: [LearningPathContainerComponent],
})
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
index 7f89d3c701aa..33169ba7befa 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
@@ -4,23 +4,10 @@
-
-
-
-
- No task selected
-
-
-
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
index ee3f4b6d5e5d..0cad01cf985a 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
@@ -2,7 +2,7 @@ import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
import { Exercise } from 'app/entities/exercise.model';
-import { LectureUnit, LectureUnitType } from 'app/entities/lecture-unit/lectureUnit.model';
+import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
import { Lecture } from 'app/entities/lecture.model';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
import { RecommendationType } from 'app/entities/learning-path.model';
@@ -10,8 +10,9 @@ import { LectureService } from 'app/lecture/lecture.service';
import { onError } from 'app/shared/util/global.utils';
import { HttpErrorResponse } from '@angular/common/http';
import { AlertService } from 'app/core/util/alert.service';
-import { isCommunicationEnabled, isMessagingEnabled } from 'app/entities/course.model';
-import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
+import { LearningPathLectureUnitViewComponent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
+import { CourseExerciseDetailsComponent } from 'app/overview/exercise-details/course-exercise-details.component';
+import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
@Component({
selector: 'jhi-learning-path-container',
@@ -33,14 +34,13 @@ export class LearningPathContainerComponent implements OnInit {
faChevronLeft = faChevronLeft;
faChevronRight = faChevronRight;
- discussionComponent?: DiscussionSectionComponent;
-
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
+ private alertService: AlertService,
private learningPathService: LearningPathService,
private lectureService: LectureService,
- private alertService: AlertService,
+ private exerciseService: ExerciseService,
) {}
ngOnInit() {
@@ -96,7 +96,6 @@ export class LearningPathContainerComponent implements OnInit {
this.loadLectureUnit();
}
}
- console.log('request previous task');
}
loadLectureUnit() {
@@ -110,28 +109,45 @@ export class LearningPathContainerComponent implements OnInit {
},
error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
});
+ this.router.navigate(['lecture-unit'], { relativeTo: this.activatedRoute });
}
loadExercise() {
console.log('load exercise');
+ this.exerciseService.getExerciseDetails(this.learningObjectId).subscribe({
+ next: (exerciseResponse) => {
+ this.exercise = exerciseResponse.body!;
+ },
+ error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
+ });
+ this.router.navigate(['exercise'], { relativeTo: this.activatedRoute });
}
/**
* This function gets called if the router outlet gets activated. This is
- * used only for the DiscussionComponent
+ * used only for the LearningPathLectureUnitViewComponent
* @param instance The component instance
*/
- onChildActivate(instance: DiscussionSectionComponent) {
+ onChildActivate(instance: LearningPathLectureUnitViewComponent | CourseExerciseDetailsComponent) {
console.log(instance);
- this.discussionComponent = instance; // save the reference to the component instance
+ if (instance instanceof LearningPathLectureUnitViewComponent) {
+ this.setupLectureUnitView(instance);
+ } else {
+ this.setupExerciseView(instance);
+ }
+ }
+
+ setupLectureUnitView(instance: LearningPathLectureUnitViewComponent) {
if (this.lecture) {
instance.lecture = this.lecture;
- instance.isCommunicationPage = false;
+ instance.lectureUnit = this.lectureUnit!;
}
- // todo exercise
}
- protected readonly isMessagingEnabled = isMessagingEnabled;
- protected readonly isCommunicationEnabled = isCommunicationEnabled;
- protected readonly LectureUnitType = LectureUnitType;
+ setupExerciseView(instance: CourseExerciseDetailsComponent) {
+ if (this.exercise) {
+ instance.courseId = this.courseId;
+ instance.exerciseId = this.learningObjectId;
+ }
+ }
}
diff --git a/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.html b/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.html
new file mode 100644
index 000000000000..8002222110b2
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.html
@@ -0,0 +1,17 @@
+
diff --git a/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component.ts b/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.ts
similarity index 63%
rename from src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component.ts
rename to src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.ts
index af97c18bd9f7..97dee6226034 100644
--- a/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.ts
@@ -5,6 +5,8 @@ import { onError } from 'app/shared/util/global.utils';
import { Lecture } from 'app/entities/lecture.model';
import { LectureUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/lectureUnit.service';
import { AlertService } from 'app/core/util/alert.service';
+import { isCommunicationEnabled, isMessagingEnabled } from 'app/entities/course.model';
+import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
export interface LectureUnitCompletionEvent {
lectureUnit: LectureUnit;
@@ -20,6 +22,8 @@ export class LearningPathLectureUnitViewComponent {
@Input() lectureUnit: LectureUnit;
readonly LectureUnitType = LectureUnitType;
+ discussionComponent?: DiscussionSectionComponent;
+
constructor(private lectureUnitService: LectureUnitService, private alertService: AlertService) {}
completeLectureUnit(event: LectureUnitCompletionEvent): void {
@@ -32,4 +36,21 @@ export class LearningPathLectureUnitViewComponent {
});
}
}
+
+ protected readonly isMessagingEnabled = isMessagingEnabled;
+ protected readonly isCommunicationEnabled = isCommunicationEnabled;
+
+ /**
+ * This function gets called if the router outlet gets activated. This is
+ * used only for the DiscussionComponent
+ * @param instance The component instance
+ */
+ onChildActivate(instance: DiscussionSectionComponent) {
+ console.log(instance);
+ this.discussionComponent = instance; // save the reference to the component instance
+ if (this.lecture) {
+ instance.lecture = this.lecture;
+ instance.isCommunicationPage = false;
+ }
+ }
}
diff --git a/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.module.ts b/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.module.ts
new file mode 100644
index 000000000000..33dc55941bda
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.module.ts
@@ -0,0 +1,33 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { LearningPathLectureUnitViewComponent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
+import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
+import { ArtemisSharedModule } from 'app/shared/shared.module';
+import { Authority } from 'app/shared/constants/authority.constants';
+import { UserRouteAccessService } from 'app/core/auth/user-route-access-service';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: LearningPathLectureUnitViewComponent,
+ data: {
+ authorities: [Authority.USER],
+ pageTitle: 'overview.learningPath',
+ },
+ canActivate: [UserRouteAccessService],
+ children: [
+ {
+ path: '',
+ pathMatch: 'full',
+ loadChildren: () => import('app/overview/discussion-section/discussion-section.module').then((m) => m.DiscussionSectionModule),
+ },
+ ],
+ },
+];
+
+@NgModule({
+ imports: [ArtemisSharedModule, RouterModule.forChild(routes), ArtemisLectureUnitsModule],
+ declarations: [LearningPathLectureUnitViewComponent],
+ exports: [LearningPathLectureUnitViewComponent],
+})
+export class ArtemisLearningPathLectureUnitViewModule {}
diff --git a/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component.html b/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component.html
deleted file mode 100644
index c6f4bcd378d1..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.module.ts b/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.module.ts
deleted file mode 100644
index 2e1f5bfc6c7d..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.module.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { NgModule } from '@angular/core';
-import { LearningPathLectureUnitViewComponent } from 'app/course/learning-paths/participate/lectureunit/learning-path-lecture-unit-view.component';
-import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
-import { ArtemisSharedModule } from 'app/shared/shared.module';
-
-@NgModule({
- imports: [ArtemisSharedModule, ArtemisLectureUnitsModule],
- declarations: [LearningPathLectureUnitViewComponent],
- exports: [LearningPathLectureUnitViewComponent],
-})
-export class ArtemisLearningPathLectureUnitViewModule {}
diff --git a/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts b/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts
index dfd01daa1ae6..d2f364805128 100644
--- a/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts
+++ b/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts
@@ -69,7 +69,7 @@ export class CourseExerciseDetailsComponent implements OnInit, OnDestroy {
readonly isMessagingEnabled = isMessagingEnabled;
private currentUser: User;
- private exerciseId: number;
+ public exerciseId: number;
public courseId: number;
public course: Course;
public exercise?: Exercise;
@@ -142,8 +142,12 @@ export class CourseExerciseDetailsComponent implements OnInit, OnDestroy {
this.route.params.subscribe((params) => {
const didExerciseChange = this.exerciseId !== parseInt(params['exerciseId'], 10);
const didCourseChange = this.courseId !== parseInt(params['courseId'], 10);
- this.exerciseId = parseInt(params['exerciseId'], 10);
- this.courseId = parseInt(params['courseId'], 10);
+ if (params['exerciseId']) {
+ this.exerciseId = parseInt(params['exerciseId'], 10);
+ }
+ if (params['courseId']) {
+ this.courseId = parseInt(params['courseId'], 10);
+ }
this.courseService.find(this.courseId).subscribe((courseResponse) => (this.course = courseResponse.body!));
this.accountService.identity().then((user: User) => {
this.currentUser = user;
From 606e1360bc7d91afd2df70c0f37d26bd3efd5218 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 17 Jul 2023 16:59:18 +0200
Subject: [PATCH 055/215] move client test
---
.../{ => management}/learning-path-management.component.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename src/test/javascript/spec/component/learning-paths/{ => management}/learning-path-management.component.spec.ts (99%)
diff --git a/src/test/javascript/spec/component/learning-paths/learning-path-management.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
similarity index 99%
rename from src/test/javascript/spec/component/learning-paths/learning-path-management.component.spec.ts
rename to src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
index 27d290f581c8..b2784bccf534 100644
--- a/src/test/javascript/spec/component/learning-paths/learning-path-management.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
@@ -4,7 +4,7 @@ import { LearningPathPagingService } from 'app/course/learning-paths/learning-pa
import { SortService } from 'app/shared/service/sort.service';
import { PageableSearch, SearchResult, SortingOrder } from 'app/shared/table/pageable-table';
import { LearningPath } from 'app/entities/learning-path.model';
-import { ArtemisTestModule } from '../../test.module';
+import { ArtemisTestModule } from '../../../test.module';
import { MockComponent, MockDirective, MockProvider } from 'ng-mocks';
import { ButtonComponent } from 'app/shared/components/button.component';
import { NgbPagination } from '@ng-bootstrap/ng-bootstrap';
From 6e650d7a339e01d858838da847b99b3e4235a2fc Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Tue, 18 Jul 2023 11:23:35 +0200
Subject: [PATCH 056/215] fix html tag
---
.../participate/learning-path-container.component.html | 2 +-
.../participate/learning-path-graph-sidebar.component.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
index 33169ba7befa..1aaca5150412 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
@@ -1,6 +1,6 @@
-
+
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
index ab4e0de7ef6b..4005f86f7695 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
@@ -5,7 +5,7 @@ import { Course } from 'app/entities/course.model';
import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
@Component({
- selector: 'jhi-learning-path-sidebar',
+ selector: 'jhi-learning-path-graph-sidebar',
styleUrls: ['./learning-path-graph-sidebar.component.scss'],
templateUrl: './learning-path-graph-sidebar.component.html',
})
From d72d5be28f515fdb0587e0d0ad3c22490ff5b065 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 19 Jul 2023 09:57:39 +0200
Subject: [PATCH 057/215] add tests
---
.../learning-paths/learning-path.service.ts | 2 +-
.../learning-path-container.component.spec.ts | 159 ++++++++++++++++++
...rning-path-graph-sidebar.component.spec.ts | 50 ++++++
...g-path-lecture-unit-view.component.spec.ts | 111 ++++++++++++
.../service/learning-path.service.spec.ts | 22 ++-
5 files changed, 342 insertions(+), 2 deletions(-)
create mode 100644 src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/participate/learning-path-graph-sidebar.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
diff --git a/src/main/webapp/app/course/learning-paths/learning-path.service.ts b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
index ea5c3a3762c8..47727a8295bc 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path.service.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { Course } from 'app/entities/course.model';
-import { LearningPathRecommendation, NgxLearningPathDTO, RecommendationType } from 'app/entities/learning-path.model';
+import { LearningPathRecommendation, NgxLearningPathDTO } from 'app/entities/learning-path.model';
import { map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
new file mode 100644
index 000000000000..860931670aa3
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
@@ -0,0 +1,159 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MockComponent, MockModule } from 'ng-mocks';
+import { By } from '@angular/platform-browser';
+import { ArtemisTestModule } from '../../../test.module';
+import { of } from 'rxjs';
+import { ActivatedRoute, RouterModule } from '@angular/router';
+import { HttpResponse } from '@angular/common/http';
+import { LearningPathContainerComponent } from 'app/course/learning-paths/participate/learning-path-container.component';
+import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
+import { LearningPathRecommendation, RecommendationType } from 'app/entities/learning-path.model';
+import { LectureService } from 'app/lecture/lecture.service';
+import { Lecture } from 'app/entities/lecture.model';
+import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
+import { Exercise } from 'app/entities/exercise.model';
+import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
+import { LearningPathGraphSidebarComponent } from 'app/course/learning-paths/participate/learning-path-graph-sidebar.component';
+import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model';
+import { TextExercise } from 'app/entities/text-exercise.model';
+
+describe('LearningPathContainerComponent', () => {
+ let fixture: ComponentFixture
;
+ let comp: LearningPathContainerComponent;
+ let learningPathService: LearningPathService;
+ let getLearningPathIdStub: jest.SpyInstance;
+ const learningPathId = 1337;
+ let getRecommendationStub: jest.SpyInstance;
+ let lectureService: LectureService;
+ let lecture: Lecture;
+ let lectureUnit: LectureUnit;
+ let findWithDetailsStub: jest.SpyInstance;
+ let exerciseService: ExerciseService;
+ let exercise: Exercise;
+ let getExerciseDetailsStub: jest.SpyInstance;
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, MockComponent(LearningPathGraphSidebarComponent), MockModule(RouterModule)],
+ declarations: [LearningPathContainerComponent],
+ providers: [
+ {
+ provide: ActivatedRoute,
+ useValue: {
+ parent: {
+ parent: {
+ params: of({
+ courseId: 1,
+ }),
+ },
+ },
+ },
+ },
+ ],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathContainerComponent);
+ comp = fixture.componentInstance;
+ learningPathService = TestBed.inject(LearningPathService);
+ getLearningPathIdStub = jest.spyOn(learningPathService, 'getLearningPathId').mockReturnValue(of(new HttpResponse({ body: learningPathId })));
+ getRecommendationStub = jest.spyOn(learningPathService, 'getRecommendation');
+
+ lectureUnit = new AttachmentUnit();
+ lectureUnit.id = 3;
+ lecture = new Lecture();
+ lecture.id = 2;
+ lecture.lectureUnits = [lectureUnit];
+ lectureService = TestBed.inject(LectureService);
+ findWithDetailsStub = jest.spyOn(lectureService, 'findWithDetails').mockReturnValue(of(new HttpResponse({ body: lecture })));
+
+ exercise = new TextExercise(undefined, undefined);
+ exercise.id = 4;
+ exerciseService = TestBed.inject(ExerciseService);
+ getExerciseDetailsStub = jest.spyOn(exerciseService, 'getExerciseDetails').mockReturnValue(of(new HttpResponse({ body: exercise })));
+
+ fixture.detectChanges();
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should initialize', () => {
+ expect(comp.courseId).toBe(1);
+ expect(getLearningPathIdStub).toHaveBeenCalled();
+ expect(getLearningPathIdStub).toHaveBeenCalledWith(1);
+ });
+
+ it('should request recommendation on next button click', () => {
+ const button = fixture.debugElement.query(By.css('.next-button'));
+ expect(button).not.toBeNull();
+ button.nativeElement.click();
+ expect(getRecommendationStub).toHaveBeenCalledWith(learningPathId);
+ });
+
+ it('should load lecture unit on recommendation', () => {
+ const recommendation = new LearningPathRecommendation();
+ recommendation.learningObjectId = lectureUnit.id!;
+ recommendation.lectureId = lecture.id;
+ recommendation.type = RecommendationType.LECTURE_UNIT;
+ getRecommendationStub.mockReturnValue(of(new HttpResponse({ body: recommendation })));
+ comp.onNextTask();
+ expect(findWithDetailsStub).toHaveBeenCalled();
+ expect(findWithDetailsStub).toHaveBeenCalledWith(lecture.id);
+ expect(getExerciseDetailsStub).not.toHaveBeenCalled();
+ });
+
+ it('should load exercise on recommendation', () => {
+ const recommendation = new LearningPathRecommendation();
+ recommendation.learningObjectId = exercise.id!;
+ recommendation.type = RecommendationType.EXERCISE;
+ getRecommendationStub.mockReturnValue(of(new HttpResponse({ body: recommendation })));
+ comp.onNextTask();
+ expect(findWithDetailsStub).not.toHaveBeenCalled();
+ expect(getExerciseDetailsStub).toHaveBeenCalled();
+ expect(getExerciseDetailsStub).toHaveBeenCalledWith(exercise.id);
+ });
+
+ it('should store current lecture unit in history', () => {
+ comp.learningObjectId = lectureUnit.id!;
+ comp.lectureUnit = lectureUnit;
+ comp.lectureId = lecture.id;
+ comp.lecture = lecture;
+ fixture.detectChanges();
+ comp.onNextTask();
+ expect(comp.history).toEqual([[lectureUnit.id!, lecture.id!]]);
+ });
+
+ it('should store current exercise in history', () => {
+ comp.learningObjectId = exercise.id!;
+ comp.exercise = exercise;
+ fixture.detectChanges();
+ comp.onNextTask();
+ expect(comp.history).toEqual([[exercise.id!, -1]]);
+ });
+
+ it('should load no previous task if history is empty', () => {
+ comp.onPrevTask();
+ expect(findWithDetailsStub).not.toHaveBeenCalled();
+ expect(getExerciseDetailsStub).not.toHaveBeenCalled();
+ });
+
+ it('should load previous lecture unit', () => {
+ comp.history = [[lectureUnit.id!, lecture.id!]];
+ fixture.detectChanges();
+ comp.onPrevTask();
+ expect(findWithDetailsStub).toHaveBeenCalled();
+ expect(findWithDetailsStub).toHaveBeenCalledWith(lecture.id);
+ expect(getExerciseDetailsStub).not.toHaveBeenCalled();
+ });
+
+ it('should load previous exercise', () => {
+ comp.history = [[exercise.id!, -1]];
+ fixture.detectChanges();
+ comp.onPrevTask();
+ expect(findWithDetailsStub).not.toHaveBeenCalled();
+ expect(getExerciseDetailsStub).toHaveBeenCalled();
+ expect(getExerciseDetailsStub).toHaveBeenCalledWith(exercise.id);
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-graph-sidebar.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-graph-sidebar.component.spec.ts
new file mode 100644
index 000000000000..f3be63c89f51
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-graph-sidebar.component.spec.ts
@@ -0,0 +1,50 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MockComponent, MockPipe } from 'ng-mocks';
+import { By } from '@angular/platform-browser';
+import { ArtemisTestModule } from '../../../test.module';
+import { LearningPathGraphSidebarComponent } from 'app/course/learning-paths/participate/learning-path-graph-sidebar.component';
+import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
+import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
+import { NgbTooltipMocksModule } from '../../../helpers/mocks/directive/ngbTooltipMocks.module';
+
+describe('LearningPathGraphSidebarComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LearningPathGraphSidebarComponent;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, MockComponent(LearningPathGraphComponent), MockPipe(ArtemisTranslatePipe), NgbTooltipMocksModule],
+ declarations: [LearningPathGraphSidebarComponent],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathGraphSidebarComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+ });
+
+ it('should create', () => {
+ expect(fixture).toBeTruthy();
+ expect(comp).toBeTruthy();
+ });
+
+ it('should show graph when not collapsed', () => {
+ comp.collapsed = false;
+ fixture.detectChanges();
+ const expanded = fixture.debugElement.query(By.css('.expanded-graph'));
+ const collapsed = fixture.debugElement.query(By.css('.collapsed-graph'));
+ expect(expanded.nativeElement.hasAttribute('hidden')).toBeFalsy();
+ expect(collapsed.nativeElement.hasAttribute('hidden')).toBeTruthy();
+ });
+
+ it('should not show graph when collapsed', () => {
+ comp.collapsed = true;
+ fixture.detectChanges();
+ const expanded = fixture.debugElement.query(By.css('.expanded-graph'));
+ const collapsed = fixture.debugElement.query(By.css('.collapsed-graph'));
+ expect(expanded.nativeElement.hasAttribute('hidden')).toBeTruthy();
+ expect(collapsed.nativeElement.hasAttribute('hidden')).toBeFalsy();
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
new file mode 100644
index 000000000000..edf58948cd6b
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
@@ -0,0 +1,111 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MockModule } from 'ng-mocks';
+import { ArtemisTestModule } from '../../../test.module';
+import { RouterModule } from '@angular/router';
+import { Lecture } from 'app/entities/lecture.model';
+import { LearningPathLectureUnitViewComponent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
+import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model';
+import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
+import { LectureUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/lectureUnit.service';
+import { VideoUnit } from 'app/entities/lecture-unit/videoUnit.model';
+import { TextUnit } from 'app/entities/lecture-unit/textUnit.model';
+import { OnlineUnit } from 'app/entities/lecture-unit/onlineUnit.model';
+import { Course, CourseInformationSharingConfiguration } from 'app/entities/course.model';
+
+describe('LearningPathLectureUnitViewComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LearningPathLectureUnitViewComponent;
+ let lecture: Lecture;
+ let lectureUnitService: LectureUnitService;
+ let setCompletionStub: jest.SpyInstance;
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, MockModule(RouterModule), MockModule(ArtemisLectureUnitsModule)],
+ declarations: [LearningPathLectureUnitViewComponent],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathLectureUnitViewComponent);
+ comp = fixture.componentInstance;
+ lecture = new Lecture();
+ lecture.id = 1;
+ lectureUnitService = TestBed.inject(LectureUnitService);
+ setCompletionStub = jest.spyOn(lectureUnitService, 'setCompletion');
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should display attachment unit correctly', () => {
+ const attachment = new AttachmentUnit();
+ attachment.id = 3;
+ lecture.lectureUnits = [attachment];
+ comp.lecture = lecture;
+ comp.lectureUnit = attachment;
+ fixture.detectChanges();
+ const view = fixture.debugElement.nativeElement.querySelector('jhi-attachment-unit');
+ expect(view).toBeTruthy();
+ });
+
+ it('should display video unit correctly', () => {
+ const video = new VideoUnit();
+ video.id = 3;
+ lecture.lectureUnits = [video];
+ comp.lecture = lecture;
+ comp.lectureUnit = video;
+ fixture.detectChanges();
+ const view = fixture.debugElement.nativeElement.querySelector('jhi-video-unit');
+ expect(view).toBeTruthy();
+ });
+
+ it('should display text unit correctly', () => {
+ const text = new TextUnit();
+ text.id = 3;
+ lecture.lectureUnits = [text];
+ comp.lecture = lecture;
+ comp.lectureUnit = text;
+ fixture.detectChanges();
+ const view = fixture.debugElement.nativeElement.querySelector('jhi-text-unit');
+ expect(view).toBeTruthy();
+ });
+
+ it('should display online unit correctly', () => {
+ const online = new OnlineUnit();
+ online.id = 3;
+ lecture.lectureUnits = [online];
+ comp.lecture = lecture;
+ comp.lectureUnit = online;
+ fixture.detectChanges();
+ const view = fixture.debugElement.nativeElement.querySelector('jhi-online-unit');
+ expect(view).toBeTruthy();
+ });
+
+ it('should display no discussions when disabled', () => {
+ const attachment = new AttachmentUnit();
+ attachment.id = 3;
+ lecture.lectureUnits = [attachment];
+ comp.lecture = lecture;
+ comp.lectureUnit = attachment;
+ lecture.course = new Course();
+ lecture.course.courseInformationSharingConfiguration = CourseInformationSharingConfiguration.DISABLED;
+ fixture.detectChanges();
+ const outlet = fixture.debugElement.nativeElement.querySelector('router-outlet');
+ expect(outlet).toBeFalsy();
+ });
+
+ it('should display discussions when enabled', () => {
+ const attachment = new AttachmentUnit();
+ attachment.id = 3;
+ lecture.lectureUnits = [attachment];
+ comp.lecture = lecture;
+ comp.lectureUnit = attachment;
+ lecture.course = new Course();
+ lecture.course.courseInformationSharingConfiguration = CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING;
+ fixture.detectChanges();
+ const outlet = fixture.debugElement.nativeElement.querySelector('router-outlet');
+ expect(outlet).toBeTruthy();
+ });
+});
diff --git a/src/test/javascript/spec/service/learning-path.service.spec.ts b/src/test/javascript/spec/service/learning-path.service.spec.ts
index 0f7241ac4099..e6cae7839aa7 100644
--- a/src/test/javascript/spec/service/learning-path.service.spec.ts
+++ b/src/test/javascript/spec/service/learning-path.service.spec.ts
@@ -8,6 +8,7 @@ describe('LearningPathService', () => {
let learningPathService: LearningPathService;
let httpService: HttpClient;
let putStub: jest.SpyInstance;
+ let getStub: jest.SpyInstance;
beforeEach(() => {
TestBed.configureTestingModule({
@@ -19,6 +20,7 @@ describe('LearningPathService', () => {
httpService = TestBed.inject(HttpClient);
learningPathService = new LearningPathService(httpService);
putStub = jest.spyOn(httpService, 'put');
+ getStub = jest.spyOn(httpService, 'get');
});
});
@@ -26,9 +28,27 @@ describe('LearningPathService', () => {
jest.restoreAllMocks();
});
- it('should send a request to the server to activate the user', () => {
+ it('should send a request to the server to enable learning paths for course', () => {
learningPathService.enableLearningPaths(1).subscribe();
expect(putStub).toHaveBeenCalledOnce();
expect(putStub).toHaveBeenCalledWith('api/courses/1/learning-paths/enable', null, { observe: 'response' });
});
+
+ it('should send a request to the server to get ngx representation of learning path', () => {
+ learningPathService.getNgxLearningPath(1).subscribe();
+ expect(getStub).toHaveBeenCalledOnce();
+ expect(getStub).toHaveBeenCalledWith('api/courses/1/learning-path-graph', { observe: 'response' });
+ });
+
+ it('should send a request to the server to get learning path id of the current user in the course', () => {
+ learningPathService.getLearningPathId(1).subscribe();
+ expect(getStub).toHaveBeenCalledOnce();
+ expect(getStub).toHaveBeenCalledWith('api/courses/1/learning-path-id', { observe: 'response' });
+ });
+
+ it('should send a request to the server to get recommendation for learning path', () => {
+ learningPathService.getRecommendation(1).subscribe();
+ expect(getStub).toHaveBeenCalledOnce();
+ expect(getStub).toHaveBeenCalledWith('api/learning-path/1/recommendation', { observe: 'response' });
+ });
});
From 7e995f666b7b4a65861dd989795e8d7f95505ce4 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 19 Jul 2023 13:47:37 +0200
Subject: [PATCH 058/215] add graph min-size
---
.../participate/learning-path-graph-sidebar.component.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss
index 9b4ce9f778d7..38d1584c12c5 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss
@@ -7,6 +7,7 @@ $graph-min-width: 215px;
.expanded-graph {
display: flex;
width: calc(#{$draggable-width} + #{$graph-min-width});
+ min-height: 500px;
margin-left: auto;
.scrollbar {
From f01d566071cc953e7da251336e547ff860dc05cc Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 20 Jul 2023 12:15:19 +0200
Subject: [PATCH 059/215] improve endpoints and rendering
---
package-lock.json | 10 ----
package.json | 3 +-
.../artemis/service/LearningPathService.java | 24 +++++-----
.../web/rest/LearningPathResource.java | 48 +++++++++++--------
.../learning-path-graph.component.html | 12 ++---
.../learning-path-graph.component.ts | 15 ++----
.../learning-paths/learning-path.service.ts | 12 ++---
.../learning-path-container.component.html | 2 +-
.../learning-path-container.component.ts | 9 +---
...learning-path-graph-sidebar.component.html | 3 +-
.../learning-path-graph-sidebar.component.ts | 8 +---
.../lecture/LearningPathIntegrationTest.java | 41 +++++++++++++---
.../service/learning-path.service.spec.ts | 12 ++---
13 files changed, 104 insertions(+), 95 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 74613708b522..6f16c5ae6515 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5662,11 +5662,6 @@
"d3-time-format": "2 - 3"
}
},
- "node_modules/@swimlane/ngx-graph/node_modules/d3-selection": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz",
- "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg=="
- },
"node_modules/@swimlane/ngx-graph/node_modules/d3-shape": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
@@ -19848,11 +19843,6 @@
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
},
- "node_modules/webcola/node_modules/d3-selection": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz",
- "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg=="
- },
"node_modules/webcola/node_modules/d3-shape": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
diff --git a/package.json b/package.json
index 02d5cff55b8f..0830fa3fe281 100644
--- a/package.json
+++ b/package.json
@@ -90,7 +90,8 @@
"d3-color": "^3.1.0",
"d3-interpolate": "^3.0.1",
"d3-transition": "^3.0.1",
- "d3-brush": "^3.0.0"
+ "d3-brush": "^3.0.0",
+ "d3-selection": "^3.0.0"
},
"semver": "7.5.3",
"word-wrap": "1.2.3"
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index fdeb726167c7..a57354c217fc 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -327,19 +327,19 @@ private void generateNgxRepresentationForRelation(CompetencyRelation relation, M
}
public static String getCompetencyStartNodeId(long competencyId) {
- return competencyId + "-start";
+ return "node-" + competencyId + "-start";
}
public static String getCompetencyEndNodeId(long competencyId) {
- return competencyId + "-end";
+ return "node-" + competencyId + "-end";
}
public static String getLectureUnitNodeId(long competencyId, long lectureUnitId) {
- return competencyId + "-lu-" + lectureUnitId;
+ return "node-" + competencyId + "-lu-" + lectureUnitId;
}
public static String getExerciseNodeId(long competencyId, long exerciseId) {
- return competencyId + "-ex-" + exerciseId;
+ return "node-" + competencyId + "-ex-" + exerciseId;
}
public static String getMatchingClusterStartNodeId(long matchingClusterId) {
@@ -351,35 +351,35 @@ public static String getMatchingClusterEndNodeId(long matchingClusterId) {
}
public static String getLectureUnitInEdgeId(long competencyId, long lectureUnitId) {
- return competencyId + "-lu-" + getInEdgeId(lectureUnitId);
+ return "edge-" + competencyId + "-lu-" + getInEdgeId(lectureUnitId);
}
public static String getLectureUnitOutEdgeId(long competencyId, long lectureUnitId) {
- return competencyId + "-lu-" + getOutEdgeId(lectureUnitId);
+ return "edge-" + competencyId + "-lu-" + getOutEdgeId(lectureUnitId);
}
public static String getExerciseInEdgeId(long competencyId, long exercise) {
- return competencyId + "-ex-" + getInEdgeId(exercise);
+ return "edge-" + competencyId + "-ex-" + getInEdgeId(exercise);
}
public static String getExerciseOutEdgeId(long competencyId, long exercise) {
- return competencyId + "-ex-" + getOutEdgeId(exercise);
+ return "edge-" + competencyId + "-ex-" + getOutEdgeId(exercise);
}
public static String getInEdgeId(long id) {
- return id + "-in";
+ return "edge-" + id + "-in";
}
public static String getOutEdgeId(long id) {
- return id + "-out";
+ return "edge-" + id + "-out";
}
public static String getRelationEdgeId(String sourceNodeId, String targetNodeId) {
- return "relation-" + sourceNodeId + "-" + targetNodeId;
+ return "edge-relation-" + sourceNodeId + "-" + targetNodeId;
}
public static String getDirectEdgeId(long competencyId) {
- return competencyId + "-direct";
+ return "edge-" + competencyId + "-direct";
}
public LearningObject getRecommendation(LearningPath learningPath) {
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 2f9b21d39290..761aaf308b12 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -22,6 +22,7 @@
import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO;
import de.tum.in.www1.artemis.web.rest.dto.learningpath.LearningPathRecommendation;
import de.tum.in.www1.artemis.web.rest.dto.learningpath.NgxLearningPathDTO;
+import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;
@RestController
@RequestMapping("/api")
@@ -95,44 +96,51 @@ public ResponseEntity> getLearningPathsOnPage(
}
/**
- * GET /courses/:courseId/learning-path-graph : Gets the ngx representation of the learning path.
+ * GET /courses/:courseId/learning-path-id : Gets the id of the learning path.
*
- * @param courseId the id of the course from which the learning path should be fetched
- * @return the ResponseEntity with status 200 (OK) and with body the ngx representation of the learning path
+ * @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
*/
- @GetMapping("/courses/{courseId}/learning-path-graph")
+ @GetMapping("/courses/{courseId}/learning-path-id")
@EnforceAtLeastStudent
- public ResponseEntity getNgxLearningPath(@PathVariable Long courseId) {
- log.debug("REST request to get ngx representation of learning path for course with id: {}", courseId);
+ public ResponseEntity getLearningPathId(@PathVariable Long courseId) {
+ log.debug("REST request to get learning path id for course with id: {}", courseId);
Course course = courseRepository.findByIdElseThrow(courseId);
User user = userRepository.getUserWithGroupsAndAuthorities();
authorizationCheckService.isStudentInCourse(course, user);
if (!course.getLearningPathsEnabled()) {
throw new BadRequestException("Learning paths are not enabled for this course.");
}
- LearningPath learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningUnitsByCourseIdAndUserIdElseThrow(course.getId(), user.getId());
- NgxLearningPathDTO graph = learningPathService.generateNgxRepresentation(learningPath);
- return ResponseEntity.ok(graph);
+ LearningPath learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), user.getId());
+ return ResponseEntity.ok(learningPath.getId());
}
/**
- * GET /courses/:courseId/learning-path-id : Gets the id of the learning path.
+ * GET /learning-path/:learningPathId : Gets the ngx representation of the learning path.
*
- * @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 learningPathId the id of the learning path that should be fetched
+ * @return the ResponseEntity with status 200 (OK) and with body the ngx representation of the learning path
*/
- @GetMapping("/courses/{courseId}/learning-path-id")
+ @GetMapping("/learning-path/{learningPathId}")
@EnforceAtLeastStudent
- public ResponseEntity getLearningPathId(@PathVariable Long courseId) {
- log.debug("REST request to get learning path id for course with id: {}", courseId);
- Course course = courseRepository.findByIdElseThrow(courseId);
- User user = userRepository.getUserWithGroupsAndAuthorities();
- authorizationCheckService.isStudentInCourse(course, user);
+ public ResponseEntity getNgxLearningPath(@PathVariable Long learningPathId) {
+ log.debug("REST request to get ngx representation of learning path with id: {}", learningPathId);
+ LearningPath learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningUnitsByIdElseThrow(learningPathId);
+ Course course = courseRepository.findByIdElseThrow(learningPath.getCourse().getId());
if (!course.getLearningPathsEnabled()) {
throw new BadRequestException("Learning paths are not enabled for this course.");
}
- LearningPath learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), user.getId());
- return ResponseEntity.ok(learningPath.getId());
+ User user = userRepository.getUserWithGroupsAndAuthorities();
+ if (authorizationCheckService.isStudentInCourse(course, user)) {
+ if (!user.getId().equals(learningPath.getUser().getId())) {
+ throw new AccessForbiddenException("You are not allowed to access another users learning path.");
+ }
+ }
+ else if (!authorizationCheckService.isAtLeastInstructorInCourse(course, user) && !authorizationCheckService.isAdmin()) {
+ throw new AccessForbiddenException("You are not allowed to access another users learning path.");
+ }
+ NgxLearningPathDTO graph = learningPathService.generateNgxRepresentation(learningPath);
+ return ResponseEntity.ok(graph);
}
/**
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
index 7ea68e33cc8f..9a8f5baff820 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
@@ -22,18 +22,16 @@
-
-
-
-
- {{ node.label }}
+
+
+
-
+
-
+ text
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
index 9731392cff7b..cc323aced122 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, Input, OnChanges, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Layout } from '@swimlane/ngx-graph';
import * as shape from 'd3-shape';
@@ -13,6 +13,7 @@ import { NgxLearningPathDTO } from 'app/entities/learning-path.model';
})
export class LearningPathGraphComponent implements OnInit {
isLoading = false;
+ @Input() learningPathId: number;
@Input() courseId: number;
ngxLearningPath: NgxLearningPathDTO;
@@ -31,23 +32,17 @@ export class LearningPathGraphComponent implements OnInit {
constructor(private activatedRoute: ActivatedRoute, private learningPathService: LearningPathService) {}
ngOnInit() {
- if (!this.courseId) {
- this.activatedRoute.parent!.parent!.params.subscribe((params) => {
- this.courseId = +params['courseId'];
- if (this.courseId) {
- this.loadData();
- }
- });
- } else {
+ if (this.learningPathId) {
this.loadData();
}
}
loadData() {
this.isLoading = true;
- this.learningPathService.getNgxLearningPath(this.courseId).subscribe((ngxLearningPathResponse) => {
+ this.learningPathService.getNgxLearningPath(this.learningPathId).subscribe((ngxLearningPathResponse) => {
this.ngxLearningPath = ngxLearningPathResponse.body!;
this.isLoading = false;
+ console.log(this.ngxLearningPath);
});
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path.service.ts b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
index 47727a8295bc..75f4f7ed6bc4 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path.service.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
@@ -15,8 +15,12 @@ export class LearningPathService {
return this.httpClient.put(`${this.resourceURL}/courses/${courseId}/learning-paths/enable`, null, { observe: 'response' });
}
- getNgxLearningPath(courseId: number): Observable> {
- return this.httpClient.get(`${this.resourceURL}/courses/${courseId}/learning-path-graph`, { observe: 'response' }).pipe(
+ getLearningPathId(courseId: number) {
+ return this.httpClient.get(`${this.resourceURL}/courses/${courseId}/learning-path-id`, { observe: 'response' });
+ }
+
+ getNgxLearningPath(learningPathId: number): Observable> {
+ return this.httpClient.get(`${this.resourceURL}/learning-path/${learningPathId}`, { observe: 'response' }).pipe(
map((ngxLearningPathResponse) => {
if (!ngxLearningPathResponse.body!.nodes) {
ngxLearningPathResponse.body!.nodes = [];
@@ -32,10 +36,6 @@ export class LearningPathService {
);
}
- getLearningPathId(courseId: number) {
- return this.httpClient.get(`${this.resourceURL}/courses/${courseId}/learning-path-id`, { observe: 'response' });
- }
-
getRecommendation(learningPathId: number) {
return this.httpClient.get(`${this.resourceURL}/learning-path/${learningPathId}/recommendation`, { observe: 'response' });
}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
index 1aaca5150412..8c24c96375eb 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
@@ -1,6 +1,6 @@
-
+
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
index 0cad01cf985a..11285c292c05 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
@@ -19,8 +19,7 @@ import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service'
templateUrl: './learning-path-container.component.html',
})
export class LearningPathContainerComponent implements OnInit {
- @Input()
- courseId: number;
+ @Input() courseId: number;
learningPathId: number;
learningObjectId: number;
@@ -44,7 +43,6 @@ export class LearningPathContainerComponent implements OnInit {
) {}
ngOnInit() {
- console.log('ON INIT CONTAINER');
if (!this.courseId) {
this.activatedRoute.parent!.parent!.params.subscribe((params) => {
this.courseId = +params['courseId'];
@@ -52,6 +50,7 @@ export class LearningPathContainerComponent implements OnInit {
}
this.learningPathService.getLearningPathId(this.courseId).subscribe((learningPathIdResponse) => {
this.learningPathId = learningPathIdResponse.body!;
+ console.log('container' + this.learningPathId);
});
}
@@ -62,8 +61,6 @@ export class LearningPathContainerComponent implements OnInit {
this.history.push([this.exercise.id, -1]);
}
this.undefineAll();
- console.log('request next task');
- console.log(this.courseId);
this.learningPathService.getRecommendation(this.learningPathId).subscribe((recommendationResponse) => {
const recommendation = recommendationResponse.body!;
this.learningObjectId = recommendation.learningObjectId;
@@ -99,7 +96,6 @@ export class LearningPathContainerComponent implements OnInit {
}
loadLectureUnit() {
- console.log('loading lecture unit');
this.lectureService.findWithDetails(this.lectureId!).subscribe({
next: (findLectureResult) => {
this.lecture = findLectureResult.body!;
@@ -113,7 +109,6 @@ export class LearningPathContainerComponent implements OnInit {
}
loadExercise() {
- console.log('load exercise');
this.exerciseService.getExerciseDetails(this.learningObjectId).subscribe({
next: (exerciseResponse) => {
this.exercise = exerciseResponse.body!;
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html
index 1cadb8f0904d..08a8a91621f6 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html
@@ -13,8 +13,7 @@
-
-
+
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
index 4005f86f7695..cf6ff5060559 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
@@ -1,7 +1,6 @@
-import { AfterViewInit, Component, ViewChild } from '@angular/core';
+import { AfterViewInit, Component, Input, ViewChild } from '@angular/core';
import interact from 'interactjs';
import { faChevronLeft, faChevronRight, faGripLinesVertical, faNetworkWired } from '@fortawesome/free-solid-svg-icons';
-import { Course } from 'app/entities/course.model';
import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
@Component({
@@ -10,8 +9,7 @@ import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-p
templateUrl: './learning-path-graph-sidebar.component.html',
})
export class LearningPathGraphSidebarComponent implements AfterViewInit {
- course?: Course;
-
+ @Input() learningPathId: number;
collapsed: boolean;
// Icons
faChevronLeft = faChevronLeft;
@@ -22,8 +20,6 @@ export class LearningPathGraphSidebarComponent implements AfterViewInit {
@ViewChild(`learningPathGraphComponent`, { static: false })
learningPathGraphComponent: LearningPathGraphComponent;
- constructor() {}
-
ngAfterViewInit(): void {
// allows the conversation sidebar to be resized towards the right-hand side
interact('.expanded-graph')
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index b0c0c95cadee..49e9590407cd 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -28,7 +28,6 @@
import de.tum.in.www1.artemis.participation.ParticipationUtilService;
import de.tum.in.www1.artemis.repository.*;
import de.tum.in.www1.artemis.service.CompetencyProgressService;
-import de.tum.in.www1.artemis.service.LearningPathService;
import de.tum.in.www1.artemis.service.LectureUnitService;
import de.tum.in.www1.artemis.user.UserUtilService;
import de.tum.in.www1.artemis.util.PageableSearchUtilService;
@@ -44,9 +43,6 @@ class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbuck
@Autowired
private UserRepository userRepository;
- @Autowired
- private LearningPathService learningPathService;
-
@Autowired
private UserUtilService userUtilService;
@@ -354,7 +350,21 @@ void testUpdateLearningPathProgress() throws Exception {
@Test
@WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
void testGetNgxLearningPathForLearningPathsDisabled() throws Exception {
- request.get("/api/courses/" + course.getId() + "/learning-path-graph", HttpStatus.BAD_REQUEST, NgxLearningPathDTO.class);
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ course.setLeanringPathsEnabled(false);
+ courseRepository.save(course);
+ request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.BAD_REQUEST, NgxLearningPathDTO.class);
+ }
+
+ @Test
+ @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER")
+ void testGetNgxLearningPathForOtherStudent() throws Exception {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.FORBIDDEN, NgxLearningPathDTO.class);
}
/**
@@ -365,8 +375,25 @@ void testGetNgxLearningPathForLearningPathsDisabled() throws Exception {
*/
@Test
@WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
- void testGetNgxLearningPath() throws Exception {
+ void testGetNgxLearningPathAsStudent() throws Exception {
course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
- request.get("/api/courses/" + course.getId() + "/learning-path-graph", HttpStatus.OK, NgxLearningPathDTO.class);
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.OK, NgxLearningPathDTO.class);
+ }
+
+ /**
+ * This only tests if the end point successfully retrieves the graph representation. The correctness of the response is tested in LearningPathServiceTest.
+ *
+ * @throws Exception the request failed
+ * @see de.tum.in.www1.artemis.service.LearningPathServiceTest
+ */
+ @Test
+ @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
+ void testGetNgxLearningPathAsInstructor() throws Exception {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.OK, NgxLearningPathDTO.class);
}
}
diff --git a/src/test/javascript/spec/service/learning-path.service.spec.ts b/src/test/javascript/spec/service/learning-path.service.spec.ts
index e6cae7839aa7..6748f72a1fad 100644
--- a/src/test/javascript/spec/service/learning-path.service.spec.ts
+++ b/src/test/javascript/spec/service/learning-path.service.spec.ts
@@ -34,18 +34,18 @@ describe('LearningPathService', () => {
expect(putStub).toHaveBeenCalledWith('api/courses/1/learning-paths/enable', null, { observe: 'response' });
});
- it('should send a request to the server to get ngx representation of learning path', () => {
- learningPathService.getNgxLearningPath(1).subscribe();
- expect(getStub).toHaveBeenCalledOnce();
- expect(getStub).toHaveBeenCalledWith('api/courses/1/learning-path-graph', { observe: 'response' });
- });
-
it('should send a request to the server to get learning path id of the current user in the course', () => {
learningPathService.getLearningPathId(1).subscribe();
expect(getStub).toHaveBeenCalledOnce();
expect(getStub).toHaveBeenCalledWith('api/courses/1/learning-path-id', { observe: 'response' });
});
+ it('should send a request to the server to get ngx representation of learning path', () => {
+ learningPathService.getNgxLearningPath(1).subscribe();
+ expect(getStub).toHaveBeenCalledOnce();
+ expect(getStub).toHaveBeenCalledWith('api/learning-path/1', { observe: 'response' });
+ });
+
it('should send a request to the server to get recommendation for learning path', () => {
learningPathService.getRecommendation(1).subscribe();
expect(getStub).toHaveBeenCalledOnce();
From c1d3aaf01365aaa2927b941d9d65ca3d1e20f9b4 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 20 Jul 2023 12:49:34 +0200
Subject: [PATCH 060/215] extend Ngx model
---
.../artemis/service/LearningPathService.java | 9 ++++-----
.../dto/learningpath/NgxLearningPathDTO.java | 20 +++++++++++++++++--
.../app/entities/learning-path.model.ts | 1 +
3 files changed, 23 insertions(+), 7 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index a57354c217fc..571d45258c84 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -203,10 +203,9 @@ private void generateNgxRepresentationForCompetency(Competency competency, Set
{
currentCluster.add(new NgxLearningPathDTO.Node(getLectureUnitNodeId(competency.getId(), lectureUnit.getId()), NgxLearningPathDTO.NodeType.LECTURE_UNIT,
@@ -284,8 +283,8 @@ private void generateNgxRepresentationForRelations(LearningPath learningPath, Se
// generate match cluster start and end nodes
for (int i = 0; i < matchClusters.numberOfSets(); i++) {
- nodes.add(new NgxLearningPathDTO.Node(getMatchingClusterStartNodeId(i), NgxLearningPathDTO.NodeType.COMPETENCY_START, -1, ""));
- nodes.add(new NgxLearningPathDTO.Node(getMatchingClusterEndNodeId(i), NgxLearningPathDTO.NodeType.COMPETENCY_END, -1, ""));
+ nodes.add(new NgxLearningPathDTO.Node(getMatchingClusterStartNodeId(i), NgxLearningPathDTO.NodeType.COMPETENCY_START));
+ nodes.add(new NgxLearningPathDTO.Node(getMatchingClusterEndNodeId(i), NgxLearningPathDTO.NodeType.COMPETENCY_END));
}
// generate edges between match cluster nodes and corresponding competencies
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java
index 08606e88c07f..90ca65a77b43 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java
@@ -23,7 +23,23 @@ public String toString() {
return "NgxLearningPathDTO{nodes=" + nodes + ", edges=" + edges + ", clusters=" + clusters + "}";
}
- public record Node(String id, NodeType type, long linkedResource, String label) {
+ public record Node(String id, NodeType type, long linkedResource, boolean completed, String label) {
+
+ public Node(String id, NodeType type, long linkedResource, String label) {
+ this(id, type, linkedResource, false, label);
+ }
+
+ public Node(String id, NodeType type, String label) {
+ this(id, type, -1, label);
+ }
+
+ public Node(String id, NodeType type, long linkedResource) {
+ this(id, type, linkedResource, "");
+ }
+
+ public Node(String id, NodeType type) {
+ this(id, type, -1);
+ }
@Override
public boolean equals(Object obj) {
@@ -35,7 +51,7 @@ public boolean equals(Object obj) {
@Override
public String toString() {
- return "Node{id=" + id + ", type=" + type.name() + ", linkedResource=" + linkedResource + ", label=" + label + "}";
+ return "Node{id=" + id + ", type=" + type.name() + ", linkedResource=" + linkedResource + ", completed=" + completed + ", label=" + label + "}";
}
}
diff --git a/src/main/webapp/app/entities/learning-path.model.ts b/src/main/webapp/app/entities/learning-path.model.ts
index dc2390022442..b6573c8d0fb2 100644
--- a/src/main/webapp/app/entities/learning-path.model.ts
+++ b/src/main/webapp/app/entities/learning-path.model.ts
@@ -24,6 +24,7 @@ export class NgxLearningPathNode implements Node {
public id: string;
public type?: NodeType;
public linkedResource?: number;
+ public completed?: boolean;
public label?: string;
}
From 197efb8f72dce36988ad85260302596e055cede6 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 20 Jul 2023 15:53:53 +0200
Subject: [PATCH 061/215] fix rendering of completed learning objects
---
.../repository/LearningPathRepository.java | 23 ++++++++++---------
.../artemis/service/LearningPathService.java | 17 +++++++-------
.../web/rest/LearningPathResource.java | 4 ++--
.../learning-path-graph-node.component.html | 9 +++++---
.../learning-path-graph-node.component.ts | 17 +++++---------
.../learning-path-graph.component.html | 7 ++----
.../learning-path-graph.component.scss | 6 +++++
.../app/entities/learning-path.model.ts | 12 +++++-----
.../service/LearningPathServiceTest.java | 6 ++---
9 files changed, 52 insertions(+), 49 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
index 295d2b5ba610..4a08dfcc341a 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
@@ -34,14 +34,6 @@ default LearningPath findWithEagerCompetenciesByCourseIdAndUserIdElseThrow(long
return findWithEagerCompetenciesByCourseIdAndUserId(courseId, userId).orElseThrow(() -> new EntityNotFoundException("LearningPath"));
}
- @EntityGraph(type = LOAD, attributePaths = { "competencies", "competencies.lectureUnits", "competencies.exercises" })
- Optional findWithEagerCompetenciesAndLearningUnitsByCourseIdAndUserId(long courseId, long userId);
-
- @NotNull
- default LearningPath findWithEagerCompetenciesAndLearningUnitsByCourseIdAndUserIdElseThrow(long courseId, long userId) {
- return findWithEagerCompetenciesAndLearningUnitsByCourseIdAndUserId(courseId, userId).orElseThrow(() -> new EntityNotFoundException("LearningPath"));
- }
-
@Query("""
SELECT lp
FROM LearningPath lp
@@ -65,10 +57,19 @@ default LearningPath findWithEagerCompetenciesByIdElseThrow(long learningPathId)
}
@EntityGraph(type = LOAD, attributePaths = { "competencies", "competencies.lectureUnits", "competencies.exercises" })
- Optional findWithEagerCompetenciesAndLearningUnitsById(long learningPathId);
+ Optional findWithEagerCompetenciesAndLearningObjectsById(long learningPathId);
+
+ @NotNull
+ default LearningPath findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(long learningPathId) {
+ return findWithEagerCompetenciesAndLearningObjectsById(learningPathId).orElseThrow(() -> new EntityNotFoundException("LearningPath", learningPathId));
+ }
+
+ @EntityGraph(type = LOAD, attributePaths = { "competencies", "competencies.lectureUnits", "competencies.lectureUnits.completedUsers", "competencies.exercises",
+ "competencies.exercises.studentParticipations" })
+ Optional findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersById(long learningPathId);
@NotNull
- default LearningPath findWithEagerCompetenciesAndLearningUnitsByIdElseThrow(long learningPathId) {
- return findWithEagerCompetenciesAndLearningUnitsById(learningPathId).orElseThrow(() -> new EntityNotFoundException("LearningPath", learningPathId));
+ default LearningPath findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersByIdElseThrow(long learningPathId) {
+ return findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersById(learningPathId).orElseThrow(() -> new EntityNotFoundException("LearningPath", learningPathId));
}
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index 571d45258c84..9b1c5509f479 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -174,7 +174,7 @@ public NgxLearningPathDTO generateNgxRepresentation(LearningPath learningPath) {
Set nodes = new HashSet<>();
Set edges = new HashSet<>();
Set clusters = new HashSet<>();
- learningPath.getCompetencies().forEach(competency -> generateNgxRepresentationForCompetency(competency, nodes, edges, clusters));
+ learningPath.getCompetencies().forEach(competency -> generateNgxRepresentationForCompetency(learningPath, competency, nodes, edges, clusters));
generateNgxRepresentationForRelations(learningPath, nodes, edges);
return new NgxLearningPathDTO(nodes, edges, clusters);
}
@@ -192,12 +192,13 @@ public NgxLearningPathDTO generateNgxRepresentation(LearningPath learningPath) {
* a cluster consisting of all created nodes
*
*
- * @param competency the competency for which the representation will be created
- * @param nodes set of nodes to store the new nodes
- * @param edges set of edges to store the new edges
- * @param clusters set of clusters to store the new clusters
+ * @param learningPath the learning path for which the representation should be created
+ * @param competency the competency for which the representation will be created
+ * @param nodes set of nodes to store the new nodes
+ * @param edges set of edges to store the new edges
+ * @param clusters set of clusters to store the new clusters
*/
- private void generateNgxRepresentationForCompetency(Competency competency, Set nodes, Set edges,
+ private void generateNgxRepresentationForCompetency(LearningPath learningPath, Competency competency, Set nodes, Set edges,
Set clusters) {
Set currentCluster = new HashSet<>();
// generates start and end node
@@ -209,7 +210,7 @@ private void generateNgxRepresentationForCompetency(Competency competency, Set {
currentCluster.add(new NgxLearningPathDTO.Node(getLectureUnitNodeId(competency.getId(), lectureUnit.getId()), NgxLearningPathDTO.NodeType.LECTURE_UNIT,
- lectureUnit.getId(), lectureUnit.getName()));
+ lectureUnit.getId(), lectureUnit.isCompletedFor(learningPath.getUser()), lectureUnit.getName()));
edges.add(new NgxLearningPathDTO.Edge(getLectureUnitInEdgeId(competency.getId(), lectureUnit.getId()), startNodeId,
getLectureUnitNodeId(competency.getId(), lectureUnit.getId())));
edges.add(new NgxLearningPathDTO.Edge(getLectureUnitOutEdgeId(competency.getId(), lectureUnit.getId()), getLectureUnitNodeId(competency.getId(), lectureUnit.getId()),
@@ -218,7 +219,7 @@ private void generateNgxRepresentationForCompetency(Competency competency, Set {
currentCluster.add(new NgxLearningPathDTO.Node(getExerciseNodeId(competency.getId(), exercise.getId()), NgxLearningPathDTO.NodeType.EXERCISE, exercise.getId(),
- exercise.getTitle()));
+ exercise.isCompletedFor(learningPath.getUser()), exercise.getTitle()));
edges.add(new NgxLearningPathDTO.Edge(getExerciseInEdgeId(competency.getId(), exercise.getId()), startNodeId, getExerciseNodeId(competency.getId(), exercise.getId())));
edges.add(new NgxLearningPathDTO.Edge(getExerciseOutEdgeId(competency.getId(), exercise.getId()), getExerciseNodeId(competency.getId(), exercise.getId()), endNodeId));
});
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 761aaf308b12..99bed802d99f 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -125,7 +125,7 @@ public ResponseEntity getLearningPathId(@PathVariable Long courseId) {
@EnforceAtLeastStudent
public ResponseEntity getNgxLearningPath(@PathVariable Long learningPathId) {
log.debug("REST request to get ngx representation of learning path with id: {}", learningPathId);
- LearningPath learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningUnitsByIdElseThrow(learningPathId);
+ LearningPath learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersByIdElseThrow(learningPathId);
Course course = courseRepository.findByIdElseThrow(learningPath.getCourse().getId());
if (!course.getLearningPathsEnabled()) {
throw new BadRequestException("Learning paths are not enabled for this course.");
@@ -153,7 +153,7 @@ else if (!authorizationCheckService.isAtLeastInstructorInCourse(course, user) &&
@EnforceAtLeastStudent
public ResponseEntity getRecommendation(@PathVariable Long learningPathId) {
log.debug("REST request to get recommendation for learning path with id: {}", learningPathId);
- LearningPath learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningUnitsByIdElseThrow(learningPathId);
+ LearningPath learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(learningPathId);
LearningObject recommendation = learningPathService.getRecommendation(learningPath);
if (recommendation == null) {
return ResponseEntity.ok(new LearningPathRecommendation(-1, -1, LearningPathRecommendation.RecommendationType.EMPTY));
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
index ca0843db9214..db38a8dd8070 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
@@ -1,4 +1,7 @@
-
-
-
+
+
+
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
index 23d8ca4149b5..39d2baf34dbe 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
@@ -1,24 +1,19 @@
import { Component, Input } from '@angular/core';
-import { faCheckCircle, faCircle, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
+import { faCheckCircle, faCircle, faPlayCircle, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
+import { NgxLearningPathNode, NodeType } from 'app/entities/learning-path.model';
-export enum NodeType {
- COMPETENCY_START,
- COMPETENCY_END,
- MATCH_START,
- MATCH_END,
- COMPLETED,
-}
@Component({
selector: 'jhi-learning-path-graph-node',
templateUrl: './learning-path-graph-node.component.html',
})
export class LearningPathGraphNodeComponent {
- @Input()
- type: NodeType;
+ @Input() node: NgxLearningPathNode;
//icons
faCheckCircle = faCheckCircle;
- faInfoCircle = faInfoCircle;
+ faPlayCircle = faPlayCircle;
+ faQuestionCircle = faQuestionCircle;
+
faCircle = faCircle;
protected readonly NodeType = NodeType;
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
index 9a8f5baff820..8da15aa6ade3 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
@@ -21,18 +21,15 @@
-
+
-
+
-
- text
-
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
index a995d3a4614a..1b2978297de2 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
@@ -7,4 +7,10 @@
.ngx-graph {
width: auto;
}
+
+ .node {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ }
}
diff --git a/src/main/webapp/app/entities/learning-path.model.ts b/src/main/webapp/app/entities/learning-path.model.ts
index b6573c8d0fb2..1208eeca9eba 100644
--- a/src/main/webapp/app/entities/learning-path.model.ts
+++ b/src/main/webapp/app/entities/learning-path.model.ts
@@ -40,12 +40,12 @@ export class NgxLearningPathCluster implements ClusterNode {
public childNodeIds?: string[];
}
export enum NodeType {
- COMPETENCY_START,
- COMPETENCY_END,
- MATCH_START,
- MATCH_END,
- EXERCISE,
- LECTURE_UNIT,
+ COMPETENCY_START = 'COMPETENCY_START',
+ COMPETENCY_END = 'COMPETENCY_END',
+ MATCH_START = 'MATCH_START',
+ MATCH_END = 'MATCH_END',
+ EXERCISE = 'EXERCISE',
+ LECTURE_UNIT = 'LECTURE_UNIT',
}
export class LearningPathRecommendation {
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 09bb6d32cccf..dddedc1d99b3 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -60,7 +60,7 @@ class LearningPathServiceTest extends AbstractSpringIntegrationBambooBitbucketJi
private void generateAndAssert(NgxLearningPathDTO expected) {
LearningPath learningPath = learningPathUtilService.createLearningPathInCourse(course);
- learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningUnitsByIdElseThrow(learningPath.getId());
+ learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersByIdElseThrow(learningPath.getId());
NgxLearningPathDTO actual = learningPathService.generateNgxRepresentation(learningPath);
assertThat(actual).isNotNull();
assertNgxRepEquals(actual, expected);
@@ -283,7 +283,7 @@ void setAuthorizationForRepositoryRequests() {
void testGetRecommendationEmpty() {
competencyUtilService.createCompetency(course);
LearningPath learningPath = learningPathUtilService.createLearningPathInCourse(course);
- learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningUnitsByIdElseThrow(learningPath.getId());
+ learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(learningPath.getId());
assertThat(learningPathService.getRecommendation(learningPath)).isNull();
}
@@ -293,7 +293,7 @@ void testGetRecommendationNotEmpty() {
final var lectureUnit = lectureUtilService.createTextUnit();
competencyUtilService.linkLectureUnitToCompetency(competency, lectureUnit);
LearningPath learningPath = learningPathUtilService.createLearningPathInCourse(course);
- learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningUnitsByIdElseThrow(learningPath.getId());
+ learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(learningPath.getId());
assertThat(learningPathService.getRecommendation(learningPath)).isNotNull();
}
}
From 8cea81fa05c84476a15dc218385ef93bbc6c637e Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 21 Jul 2023 02:07:47 +0200
Subject: [PATCH 062/215] added tests and cleanup
---
.../learning-path-graph-node.component.html | 6 +--
.../learning-path-graph.component.scss | 1 -
.../learning-path-graph.component.ts | 3 +-
.../learning-path-management.component.ts | 11 +++-
...earning-path-progress-modal.component.html | 3 ++
...earning-path-progress-modal.component.scss | 12 +++++
.../learning-path-progress-modal.component.ts | 15 ++++++
.../learning-paths/learning-paths.module.ts | 10 +++-
.../learning-path-container.component.ts | 2 -
...arning-path-lecture-unit-view.component.ts | 1 -
.../discussion-section.component.ts | 1 -
...learning-path-graph-node.component.spec.ts | 44 ++++++++++++++++
.../learning-path-graph.component.spec.ts | 51 +++++++++++++++++++
...ning-path-progress-modal.component.spec.ts | 45 ++++++++++++++++
.../learning-path-container.component.spec.ts | 24 +++++++++
...g-path-lecture-unit-view.component.spec.ts | 33 +++++++++++-
16 files changed, 249 insertions(+), 13 deletions(-)
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
index db38a8dd8070..dc85250d2733 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
@@ -1,7 +1,7 @@
-
-
+
+
-
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
index 1b2978297de2..0a66e3296ee5 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
@@ -2,7 +2,6 @@
display: block;
width: 100%;
height: 100%;
- overflow: hidden;
.ngx-graph {
width: auto;
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
index cc323aced122..860236e0481b 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnChanges, OnInit } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Layout } from '@swimlane/ngx-graph';
import * as shape from 'd3-shape';
@@ -42,7 +42,6 @@ export class LearningPathGraphComponent implements OnInit {
this.learningPathService.getNgxLearningPath(this.learningPathId).subscribe((ngxLearningPathResponse) => {
this.ngxLearningPath = ngxLearningPathResponse.body!;
this.isLoading = false;
- console.log(this.ngxLearningPath);
});
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index b940cabed71b..ce90a3daac33 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -13,6 +13,9 @@ import { LearningPathPagingService } from 'app/course/learning-paths/learning-pa
import { SortService } from 'app/shared/service/sort.service';
import { LearningPath } from 'app/entities/learning-path.model';
import { faSort } from '@fortawesome/free-solid-svg-icons';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { ExamImportComponent } from 'app/exam/manage/exams/exam-import/exam-import.component';
+import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
export enum TableColumn {
ID = 'ID',
@@ -58,6 +61,7 @@ export class LearningPathManagementComponent implements OnInit, OnDestroy {
private alertService: AlertService,
private pagingService: LearningPathPagingService,
private sortService: SortService,
+ private modalService: NgbModal,
) {}
get page(): number {
@@ -202,6 +206,11 @@ export class LearningPathManagementComponent implements OnInit, OnDestroy {
}
}
viewLearningPath(learningPath: LearningPath) {
- // TODO
+ const modalRef = this.modalService.open(LearningPathProgressModalComponent, {
+ size: 'xl',
+ backdrop: 'static',
+ windowClass: 'learning-path-modal',
+ });
+ modalRef.componentInstance.learningPathId = learningPath.id;
}
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
new file mode 100644
index 000000000000..5d15f51178bf
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
new file mode 100644
index 000000000000..813a13004d4a
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
@@ -0,0 +1,12 @@
+.modal-container {
+ display: flex;
+ height: 90vh;
+
+ .graph {
+ width: 100%;
+ }
+}
+
+.learning-path-modal .modal-dialog .modal-content {
+ min-height: 500px;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
new file mode 100644
index 000000000000..478979f37922
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
@@ -0,0 +1,15 @@
+import { Component, Input } from '@angular/core';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+@Component({
+ selector: 'jhi-learning-path-progress-modal',
+ styleUrls: ['./learning-path-progress-modal.component.scss'],
+ templateUrl: './learning-path-progress-modal.component.html',
+})
+export class LearningPathProgressModalComponent {
+ @Input() learningPathId: number;
+ constructor(private activeModal: NgbActiveModal) {}
+
+ close() {
+ this.activeModal.close();
+ }
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
index 103e810f6a2a..7f03e4c6e2f0 100644
--- a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
@@ -12,6 +12,7 @@ import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-p
import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph-node.component';
import { NgxGraphModule } from '@swimlane/ngx-graph';
import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
+import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
const routes: Routes = [
{
@@ -54,7 +55,14 @@ const routes: Routes = [
@NgModule({
imports: [ArtemisSharedModule, FormsModule, ReactiveFormsModule, ArtemisSharedComponentModule, NgxGraphModule, RouterModule.forChild(routes), ArtemisLectureUnitsModule],
- declarations: [LearningPathContainerComponent, LearningPathManagementComponent, LearningPathGraphSidebarComponent, LearningPathGraphComponent, LearningPathGraphNodeComponent],
+ declarations: [
+ LearningPathContainerComponent,
+ LearningPathManagementComponent,
+ LearningPathGraphSidebarComponent,
+ LearningPathGraphComponent,
+ LearningPathGraphNodeComponent,
+ LearningPathProgressModalComponent,
+ ],
exports: [LearningPathContainerComponent],
})
export class ArtemisLearningPathsModule {}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
index 11285c292c05..53793b40c7d5 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
@@ -50,7 +50,6 @@ export class LearningPathContainerComponent implements OnInit {
}
this.learningPathService.getLearningPathId(this.courseId).subscribe((learningPathIdResponse) => {
this.learningPathId = learningPathIdResponse.body!;
- console.log('container' + this.learningPathId);
});
}
@@ -124,7 +123,6 @@ export class LearningPathContainerComponent implements OnInit {
* @param instance The component instance
*/
onChildActivate(instance: LearningPathLectureUnitViewComponent | CourseExerciseDetailsComponent) {
- console.log(instance);
if (instance instanceof LearningPathLectureUnitViewComponent) {
this.setupLectureUnitView(instance);
} else {
diff --git a/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.ts b/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.ts
index 97dee6226034..5ad133a7b589 100644
--- a/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.ts
@@ -46,7 +46,6 @@ export class LearningPathLectureUnitViewComponent {
* @param instance The component instance
*/
onChildActivate(instance: DiscussionSectionComponent) {
- console.log(instance);
this.discussionComponent = instance; // save the reference to the component instance
if (this.lecture) {
instance.lecture = this.lecture;
diff --git a/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts b/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
index 9c544455b565..edc9ed17430f 100644
--- a/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
+++ b/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
@@ -66,7 +66,6 @@ export class DiscussionSectionComponent extends CourseDiscussionDirective implem
params: this.activatedRoute.params,
queryParams: this.activatedRoute.queryParams,
}).subscribe((routeParams: { params: Params; queryParams: Params }) => {
- console.log(routeParams.params);
this.currentPostId = +routeParams.queryParams.postId;
this.course = this.exercise?.course ?? this.lecture?.course;
this.metisService.setCourse(this.course);
diff --git a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
new file mode 100644
index 000000000000..844a9462e972
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
@@ -0,0 +1,44 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../test.module';
+import { By } from '@angular/platform-browser';
+import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph-node.component';
+import { NgxLearningPathNode, NodeType } from 'app/entities/learning-path.model';
+
+describe('LearningPathGraphNodeComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LearningPathGraphNodeComponent;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule],
+ declarations: [LearningPathGraphNodeComponent],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathGraphNodeComponent);
+ comp = fixture.componentInstance;
+ });
+ });
+
+ it.each([NodeType.EXERCISE, NodeType.LECTURE_UNIT])('should display correct icon for completed learning object', (type: NodeType) => {
+ comp.node = { id: '1', type: type, completed: true } as NgxLearningPathNode;
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('#completed')).nativeElement).toBeTruthy();
+ });
+
+ it.each([NodeType.EXERCISE, NodeType.LECTURE_UNIT])('should display correct icon for not completed learning object', (type: NodeType) => {
+ comp.node = { id: '1', type: type, completed: false } as NgxLearningPathNode;
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('#not-completed')).nativeElement).toBeTruthy();
+ });
+
+ it.each([NodeType.COMPETENCY_START, NodeType.COMPETENCY_END, NodeType.COMPETENCY_START, NodeType.COMPETENCY_END])(
+ 'should display correct icon for generic node',
+ (type: NodeType) => {
+ comp.node = { id: '1', type: type } as NgxLearningPathNode;
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('#generic')).nativeElement).toBeTruthy();
+ },
+ );
+});
diff --git a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
new file mode 100644
index 000000000000..aac3ebb8ad1d
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
@@ -0,0 +1,51 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../test.module';
+import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
+import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
+
+describe('LearningPathGraphComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LearningPathGraphComponent;
+ let learningPathService: LearningPathService;
+ let getNgxLearningPathStub: jest.SpyInstance;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule],
+ declarations: [LearningPathGraphComponent],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathGraphComponent);
+ comp = fixture.componentInstance;
+ learningPathService = TestBed.inject(LearningPathService);
+ getNgxLearningPathStub = jest.spyOn(learningPathService, 'getNgxLearningPath');
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should load learning path from service', () => {
+ comp.learningPathId = 1;
+ fixture.detectChanges();
+ expect(getNgxLearningPathStub).toHaveBeenCalledOnce();
+ expect(getNgxLearningPathStub).toHaveBeenCalledWith(1);
+ });
+
+ it('should update, center, and zoom to fit on resize', () => {
+ const updateStub = jest.spyOn(comp.update$, 'next');
+ const centerStub = jest.spyOn(comp.center$, 'next');
+ const zoomToFitStub = jest.spyOn(comp.zoomToFit$, 'next');
+ fixture.detectChanges();
+ comp.onResize();
+ expect(updateStub).toHaveBeenCalledOnce();
+ expect(updateStub).toHaveBeenCalledWith(true);
+ expect(centerStub).toHaveBeenCalledOnce();
+ expect(centerStub).toHaveBeenCalledWith(true);
+ expect(zoomToFitStub).toHaveBeenCalledOnce();
+ expect(zoomToFitStub).toHaveBeenCalledWith(true);
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
new file mode 100644
index 000000000000..69044986dbba
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
@@ -0,0 +1,45 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../test.module';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { MockComponent } from 'ng-mocks';
+import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
+import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
+import { By } from '@angular/platform-browser';
+
+describe('LearningPathProgressModalComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LearningPathProgressModalComponent;
+ let activeModal: NgbActiveModal;
+ let closeStub: jest.SpyInstance;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, MockComponent(LearningPathGraphComponent)],
+ declarations: [LearningPathProgressModalComponent],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathProgressModalComponent);
+ comp = fixture.componentInstance;
+ activeModal = TestBed.inject(NgbActiveModal);
+ closeStub = jest.spyOn(activeModal, 'close');
+ fixture.detectChanges();
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should display learning path graph if id is present', () => {
+ comp.learningPathId = 1;
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('.graph')).nativeElement).toBeTruthy();
+ });
+
+ it('should correctly close modal', () => {
+ comp.close();
+ expect(closeStub).toHaveBeenCalledOnce();
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
index 860931670aa3..a63cd02a0a3f 100644
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
@@ -16,6 +16,8 @@ import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service'
import { LearningPathGraphSidebarComponent } from 'app/course/learning-paths/participate/learning-path-graph-sidebar.component';
import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model';
import { TextExercise } from 'app/entities/text-exercise.model';
+import { LearningPathLectureUnitViewComponent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
+import { CourseExerciseDetailsComponent } from 'app/overview/exercise-details/course-exercise-details.component';
describe('LearningPathContainerComponent', () => {
let fixture: ComponentFixture;
@@ -156,4 +158,26 @@ describe('LearningPathContainerComponent', () => {
expect(getExerciseDetailsStub).toHaveBeenCalled();
expect(getExerciseDetailsStub).toHaveBeenCalledWith(exercise.id);
});
+
+ it('should set properties of lecture unit view on activate', () => {
+ comp.learningObjectId = lectureUnit.id!;
+ comp.lectureUnit = lectureUnit;
+ comp.lectureId = lecture.id;
+ comp.lecture = lecture;
+ fixture.detectChanges();
+ const instance = { lecture: undefined, lectureUnit: undefined } as unknown as LearningPathLectureUnitViewComponent;
+ comp.setupLectureUnitView(instance);
+ expect(instance.lecture).toEqual(lecture);
+ expect(instance.lectureUnit).toEqual(lectureUnit);
+ });
+
+ it('should set properties of exercise view on activate', () => {
+ comp.exercise = exercise;
+ comp.learningObjectId = exercise.id!;
+ fixture.detectChanges();
+ const instance = { courseId: undefined, exerciseId: undefined } as unknown as CourseExerciseDetailsComponent;
+ comp.setupExerciseView(instance);
+ expect(instance.courseId).toBe(1);
+ expect(instance.exerciseId).toEqual(exercise.id);
+ });
});
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
index edf58948cd6b..137578892fef 100644
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
@@ -3,7 +3,7 @@ import { MockModule } from 'ng-mocks';
import { ArtemisTestModule } from '../../../test.module';
import { RouterModule } from '@angular/router';
import { Lecture } from 'app/entities/lecture.model';
-import { LearningPathLectureUnitViewComponent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
+import { LearningPathLectureUnitViewComponent, LectureUnitCompletionEvent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model';
import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
import { LectureUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/lectureUnit.service';
@@ -11,6 +11,7 @@ import { VideoUnit } from 'app/entities/lecture-unit/videoUnit.model';
import { TextUnit } from 'app/entities/lecture-unit/textUnit.model';
import { OnlineUnit } from 'app/entities/lecture-unit/onlineUnit.model';
import { Course, CourseInformationSharingConfiguration } from 'app/entities/course.model';
+import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
describe('LearningPathLectureUnitViewComponent', () => {
let fixture: ComponentFixture;
@@ -108,4 +109,34 @@ describe('LearningPathLectureUnitViewComponent', () => {
const outlet = fixture.debugElement.nativeElement.querySelector('router-outlet');
expect(outlet).toBeTruthy();
});
+
+ it('should set lecture unit completion', () => {
+ const attachment = new AttachmentUnit();
+ attachment.id = 3;
+ attachment.visibleToStudents = true;
+ attachment.completed = false;
+ lecture.lectureUnits = [attachment];
+ comp.lecture = lecture;
+ comp.lectureUnit = attachment;
+ lecture.course = new Course();
+ fixture.detectChanges();
+ const event = { lectureUnit: attachment, completed: true } as LectureUnitCompletionEvent;
+ comp.completeLectureUnit(event);
+ expect(setCompletionStub).toHaveBeenCalledOnce();
+ expect(setCompletionStub).toHaveBeenCalledWith(attachment.id, lecture.id, event.completed);
+ });
+
+ it('should set properties of child on activate', () => {
+ const attachment = new AttachmentUnit();
+ attachment.id = 3;
+ lecture.lectureUnits = [attachment];
+ comp.lecture = lecture;
+ comp.lectureUnit = attachment;
+ lecture.course = new Course();
+ fixture.detectChanges();
+ const instance = { lecture: undefined, isCommunicationPage: undefined } as DiscussionSectionComponent;
+ comp.onChildActivate(instance);
+ expect(instance.lecture).toEqual(lecture);
+ expect(instance.isCommunicationPage).toBeFalsy();
+ });
});
From 21ce9a9fd3767858de28e6e3f20cb1ccf3f5fb15 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 21 Jul 2023 10:25:19 +0200
Subject: [PATCH 063/215] fix codacy warnings
---
.../learning-path-management.component.ts | 1 -
.../artemis/competency/CompetencyUtilService.java | 9 ++++++++-
.../competency/LearningPathUtilService.java | 14 +++++++-------
.../www1/artemis/exercise/ExerciseUtilService.java | 7 -------
.../lecture/LearningPathIntegrationTest.java | 13 ++++---------
.../artemis/service/LearningPathServiceTest.java | 4 ++--
6 files changed, 21 insertions(+), 27 deletions(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index ce90a3daac33..46526a35925b 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -14,7 +14,6 @@ import { SortService } from 'app/shared/service/sort.service';
import { LearningPath } from 'app/entities/learning-path.model';
import { faSort } from '@fortawesome/free-solid-svg-icons';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
-import { ExamImportComponent } from 'app/exam/manage/exams/exam-import/exam-import.component';
import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
export enum TableColumn {
diff --git a/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java b/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java
index 6451d226004a..ab93c366b286 100644
--- a/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java
+++ b/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java
@@ -63,7 +63,7 @@ public Competency createCompetency(Course course) {
public Competency[] createCompetencies(Course course, int numberOfCompetencies) {
Competency[] competencies = new Competency[numberOfCompetencies];
for (int i = 0; i < competencies.length; i++) {
- competencies[i] = createCompetency(course, "" + i);
+ competencies[i] = createCompetency(course, String.valueOf(i));
}
return competencies;
}
@@ -90,6 +90,13 @@ public void linkExerciseToCompetency(Competency competency, Exercise exercise) {
exerciseRepository.save(exercise);
}
+ /**
+ * Adds a relation between competencies.
+ *
+ * @param tail the competency that relates to another competency
+ * @param type the type of the relation
+ * @param head the competency that the tail competency relates to
+ */
public void addRelation(Competency tail, CompetencyRelation.RelationType type, Competency head) {
CompetencyRelation relation = new CompetencyRelation();
relation.setTailCompetency(tail);
diff --git a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
index 658848ba26c2..7e49d00c7bb0 100644
--- a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
+++ b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
@@ -38,10 +38,10 @@ public class LearningPathUtilService {
* @return the updated course
*/
public Course enableAndGenerateLearningPathsForCourse(Course course) {
- course = courseRepository.findWithEagerLearningPathsAndCompetenciesByIdElseThrow(course.getId());
- learningPathService.generateLearningPaths(course);
- course.setLeanringPathsEnabled(true);
- return courseRepository.save(course);
+ var eagerlyLoadedCourse = courseRepository.findWithEagerLearningPathsAndCompetenciesByIdElseThrow(course.getId());
+ learningPathService.generateLearningPaths(eagerlyLoadedCourse);
+ eagerlyLoadedCourse.setLeanringPathsEnabled(true);
+ return courseRepository.save(eagerlyLoadedCourse);
}
public LearningPath createLearningPathInCourse(Course course) {
@@ -52,8 +52,8 @@ public LearningPath createLearningPathInCourse(Course course) {
}
public LearningPath createLearningPath(Set competencies) {
- LearningPath lp = new LearningPath();
- lp.setCompetencies(competencies);
- return learningPathRepository.save(lp);
+ LearningPath learningPath = new LearningPath();
+ learningPath.setCompetencies(competencies);
+ return learningPathRepository.save(learningPath);
}
}
diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/ExerciseUtilService.java b/src/test/java/de/tum/in/www1/artemis/exercise/ExerciseUtilService.java
index 56bd065e38d4..7f986ecb7241 100644
--- a/src/test/java/de/tum/in/www1/artemis/exercise/ExerciseUtilService.java
+++ b/src/test/java/de/tum/in/www1/artemis/exercise/ExerciseUtilService.java
@@ -12,7 +12,6 @@
import org.springframework.stereotype.Service;
import de.tum.in.www1.artemis.domain.*;
-import de.tum.in.www1.artemis.domain.competency.Competency;
import de.tum.in.www1.artemis.domain.enumeration.AssessmentType;
import de.tum.in.www1.artemis.domain.enumeration.IncludedInOverallScore;
import de.tum.in.www1.artemis.domain.exam.Exam;
@@ -314,12 +313,6 @@ public ProgrammingExercise findProgrammingExerciseWithTitle(Collection
return new ProgrammingExercise();
}
- public Exercise addCompetenciesToExercise(Exercise exercise, Set competencies) {
- exercise = exerciseRepo.findByIdElseThrow(exercise.getId());
- exercise.setCompetencies(competencies);
- return exerciseRepo.save(exercise);
- }
-
public Channel addChannelToExercise(Exercise exercise) {
Channel channel = ConversationFactory.generateChannel(exercise.getCourseViaExerciseGroupOrCourseMember());
channel.setExercise(exercise);
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 49e9590407cd..646ed5301c62 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -95,13 +95,13 @@ class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbuck
private final int NUMBER_OF_STUDENTS = 5;
- private final String STUDENT_OF_COURSE = TEST_PREFIX + "student1";
+ private static final String STUDENT_OF_COURSE = TEST_PREFIX + "student1";
- private final String TUTOR_OF_COURSE = TEST_PREFIX + "tutor1";
+ private static final String TUTOR_OF_COURSE = TEST_PREFIX + "tutor1";
- private final String EDITOR_OF_COURSE = TEST_PREFIX + "editor1";
+ private static final String EDITOR_OF_COURSE = TEST_PREFIX + "editor1";
- private final String INSTRUCTOR_OF_COURSE = TEST_PREFIX + "instructor1";
+ private static final String INSTRUCTOR_OF_COURSE = TEST_PREFIX + "instructor1";
private User studentNotInCourse;
@@ -169,11 +169,6 @@ private Competency importCompetencyRESTCall() throws Exception {
return request.postWithResponseBody("/api/courses/" + course.getId() + "/competencies/import", competencyToImport, Competency.class, HttpStatus.CREATED);
}
- private Competency updateCompetencyRESTCall() throws Exception {
- competencies[0].setTitle("Updated Title");
- return request.putWithResponseBody("/api/courses/" + course.getId() + "/competencies", competencies[0], Competency.class, HttpStatus.OK);
- }
-
private void deleteCompetencyRESTCall(Competency competency) throws Exception {
request.delete("/api/courses/" + course.getId() + "/competencies/" + competency.getId(), HttpStatus.OK);
}
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index dddedc1d99b3..1ccb42a0df64 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -113,7 +113,7 @@ void testCompetencyWithLectureUnitAndExercise() {
competencyUtilService.linkExerciseToCompetency(competency, exercise);
final var startNodeId = LearningPathService.getCompetencyStartNodeId(competency.getId());
final var endNodeId = LearningPathService.getCompetencyEndNodeId(competency.getId());
- HashSet expectedNodes = getExpectedNodesOfEmptyCompetency(competency);
+ Set expectedNodes = getExpectedNodesOfEmptyCompetency(competency);
expectedNodes.add(getNodeForLectureUnit(competency, lectureUnit));
expectedNodes.add(getNodeForExercise(competency, exercise));
Set expectedEdges = Set.of(
@@ -255,7 +255,7 @@ private void addExpectedComponentsForEmptyCompetencies(Competency... competencie
}
}
- private static HashSet getExpectedNodesOfEmptyCompetency(Competency competency) {
+ private static Set getExpectedNodesOfEmptyCompetency(Competency competency) {
return new HashSet<>(Set.of(
new NgxLearningPathDTO.Node(LearningPathService.getCompetencyStartNodeId(competency.getId()), NgxLearningPathDTO.NodeType.COMPETENCY_START, competency.getId(), ""),
new NgxLearningPathDTO.Node(LearningPathService.getCompetencyEndNodeId(competency.getId()), NgxLearningPathDTO.NodeType.COMPETENCY_END, competency.getId(), "")));
From f8daead37d1e477d13432f377eb275558c9291fd Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 21 Jul 2023 13:18:20 +0200
Subject: [PATCH 064/215] Fix more codestyle issues
---
.../artemis/service/LearningPathService.java | 31 +++++++++---
.../artemis/web/rest/CompetencyResource.java | 5 +-
.../web/rest/LearningPathResource.java | 4 +-
.../dto/learningpath/NgxLearningPathDTO.java | 21 ++++++++
...learning-path-graph-sidebar.component.scss | 10 ----
.../course-learning-path.component.html | 1 -
.../course-learning-path.component.ts | 20 --------
.../course-learning-path.module.ts | 28 -----------
.../app/overview/courses-routing.module.ts | 2 +-
.../competency/CompetencyUtilService.java | 5 +-
.../competency/LearningPathUtilService.java | 12 +++++
.../lecture/LearningPathIntegrationTest.java | 50 +++++++++++--------
12 files changed, 99 insertions(+), 90 deletions(-)
delete mode 100644 src/main/webapp/app/overview/course-learning-path/course-learning-path.component.html
delete mode 100644 src/main/webapp/app/overview/course-learning-path/course-learning-path.component.ts
delete mode 100644 src/main/webapp/app/overview/course-learning-path/course-learning-path.module.ts
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index 9b1c5509f479..daccbbf8a581 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -1,6 +1,9 @@
package de.tum.in.www1.artemis.service;
-import java.util.*;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.LongStream;
@@ -155,10 +158,14 @@ private void updateLearningPathProgress(final LearningPath learningPath) {
final var competencyIds = learningPath.getCompetencies().stream().map(Competency::getId).collect(Collectors.toSet());
final var competencyProgresses = competencyProgressRepository.findAllByCompetencyIdsAndUserId(competencyIds, userId);
- // TODO: consider optional competencies
final var completed = (float) competencyProgresses.stream().filter(CompetencyProgressService::isMastered).count();
final var numberOfCompetencies = learningPath.getCompetencies().size();
- learningPath.setProgress(numberOfCompetencies == 0 ? 0 : Math.round(completed * 100 / (float) numberOfCompetencies));
+ if (numberOfCompetencies == 0) {
+ learningPath.setProgress(0);
+ }
+ else {
+ learningPath.setProgress(Math.round(completed * 100 / (float) numberOfCompetencies));
+ }
learningPathRepository.save(learningPath);
log.debug("Updated LearningPath (id={}) for user (id={})", learningPath.getId(), userId);
}
@@ -312,11 +319,21 @@ private void generateNgxRepresentationForRelations(LearningPath learningPath, Se
private void generateNgxRepresentationForRelation(CompetencyRelation relation, Map competencyToMatchCluster, Set createdRelations,
Set edges) {
final var sourceId = relation.getHeadCompetency().getId();
- final String sourceNodeId = competencyToMatchCluster.containsKey(sourceId) ? getMatchingClusterEndNodeId(competencyToMatchCluster.get(sourceId))
- : getCompetencyEndNodeId(sourceId);
+ String sourceNodeId;
+ if (competencyToMatchCluster.containsKey(sourceId)) {
+ sourceNodeId = getMatchingClusterEndNodeId(competencyToMatchCluster.get(sourceId));
+ }
+ else {
+ sourceNodeId = getCompetencyEndNodeId(sourceId);
+ }
final var targetId = relation.getTailCompetency().getId();
- final String targetNodeId = competencyToMatchCluster.containsKey(targetId) ? getMatchingClusterStartNodeId(competencyToMatchCluster.get(targetId))
- : getCompetencyStartNodeId(targetId);
+ String targetNodeId;
+ if (competencyToMatchCluster.containsKey(targetId)) {
+ targetNodeId = getMatchingClusterStartNodeId(competencyToMatchCluster.get(targetId));
+ }
+ else {
+ targetNodeId = getCompetencyStartNodeId(targetId);
+ }
final String relationEdgeId = getRelationEdgeId(sourceNodeId, targetNodeId);
// skip if relation has already been created (possible for edges linked to matching cluster start/end nodes)
if (!createdRelations.contains(relationEdgeId)) {
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/CompetencyResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/CompetencyResource.java
index 27fc1cf75152..78cdff7931b4 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/CompetencyResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/CompetencyResource.java
@@ -25,7 +25,10 @@
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastEditor;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastInstructor;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
-import de.tum.in.www1.artemis.service.*;
+import de.tum.in.www1.artemis.service.AuthorizationCheckService;
+import de.tum.in.www1.artemis.service.CompetencyProgressService;
+import de.tum.in.www1.artemis.service.CompetencyService;
+import de.tum.in.www1.artemis.service.LearningPathService;
import de.tum.in.www1.artemis.service.util.RoundingUtil;
import de.tum.in.www1.artemis.web.rest.dto.CourseCompetencyProgressDTO;
import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO;
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 99bed802d99f..3c0968ee3519 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -12,7 +12,9 @@
import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.domain.competency.LearningPath;
import de.tum.in.www1.artemis.domain.lecture.LectureUnit;
-import de.tum.in.www1.artemis.repository.*;
+import de.tum.in.www1.artemis.repository.CourseRepository;
+import de.tum.in.www1.artemis.repository.LearningPathRepository;
+import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.security.Role;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastInstructor;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java
index 90ca65a77b43..9767380451be 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java
@@ -1,5 +1,6 @@
package de.tum.in.www1.artemis.web.rest.dto.learningpath;
+import java.util.Objects;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -10,6 +11,11 @@
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record NgxLearningPathDTO(Set nodes, Set edges, Set clusters) {
+ @Override
+ public int hashCode() {
+ return Objects.hash(nodes, edges, clusters);
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof NgxLearningPathDTO other)) {
@@ -41,6 +47,11 @@ public Node(String id, NodeType type) {
this(id, type, -1);
}
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id);
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Node other)) {
@@ -57,6 +68,11 @@ public String toString() {
public record Edge(String id, String source, String target) {
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id);
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Edge other)) {
@@ -73,6 +89,11 @@ public String toString() {
public record Cluster(String id, String label, Set childNodeIds) {
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id);
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Cluster other)) {
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss
index 38d1584c12c5..ee0763fe5218 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss
@@ -10,16 +10,6 @@ $graph-min-width: 215px;
min-height: 500px;
margin-left: auto;
- .scrollbar {
- position: relative;
- max-height: 700px;
- overflow: auto;
- }
-
- .wrapper-scroll-y {
- display: block;
- }
-
.draggable-right {
display: flex;
flex-direction: column;
diff --git a/src/main/webapp/app/overview/course-learning-path/course-learning-path.component.html b/src/main/webapp/app/overview/course-learning-path/course-learning-path.component.html
deleted file mode 100644
index 4cd61a1c7b63..000000000000
--- a/src/main/webapp/app/overview/course-learning-path/course-learning-path.component.html
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/main/webapp/app/overview/course-learning-path/course-learning-path.component.ts b/src/main/webapp/app/overview/course-learning-path/course-learning-path.component.ts
deleted file mode 100644
index c9d342f129c1..000000000000
--- a/src/main/webapp/app/overview/course-learning-path/course-learning-path.component.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Component, Input, OnInit } from '@angular/core';
-import { ActivatedRoute } from '@angular/router';
-
-@Component({
- selector: 'jhi-course-learning-path',
- templateUrl: './course-learning-path.component.html',
- styleUrls: ['../course-overview.scss'],
-})
-export class CourseLearningPathComponent implements OnInit {
- @Input()
- courseId: number;
-
- constructor(private activatedRoute: ActivatedRoute) {}
-
- ngOnInit(): void {
- this.activatedRoute.parent!.parent!.params.subscribe((params) => {
- this.courseId = +params['courseId'];
- });
- }
-}
diff --git a/src/main/webapp/app/overview/course-learning-path/course-learning-path.module.ts b/src/main/webapp/app/overview/course-learning-path/course-learning-path.module.ts
deleted file mode 100644
index 538538611bcf..000000000000
--- a/src/main/webapp/app/overview/course-learning-path/course-learning-path.module.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { ArtemisSharedModule } from 'app/shared/shared.module';
-import { NgModule } from '@angular/core';
-import { RouterModule, Routes } from '@angular/router';
-import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module';
-import { Authority } from 'app/shared/constants/authority.constants';
-import { UserRouteAccessService } from 'app/core/auth/user-route-access-service';
-import { ArtemisLearningPathsModule } from 'app/course/learning-paths/learning-paths.module';
-import { CourseLearningPathComponent } from 'app/overview/course-learning-path/course-learning-path.component';
-
-const routes: Routes = [
- {
- path: '',
- pathMatch: 'full',
- data: {
- authorities: [Authority.USER],
- pageTitle: 'overview.learningPath',
- },
- component: CourseLearningPathComponent,
- canActivate: [UserRouteAccessService],
- },
-];
-
-@NgModule({
- imports: [RouterModule.forChild(routes), ArtemisSharedModule, ArtemisSharedComponentModule, ArtemisLearningPathsModule],
- declarations: [CourseLearningPathComponent],
- exports: [CourseLearningPathComponent],
-})
-export class CourseLearningPathModule {}
diff --git a/src/main/webapp/app/overview/courses-routing.module.ts b/src/main/webapp/app/overview/courses-routing.module.ts
index 8fed271b0a52..41cb383835a2 100644
--- a/src/main/webapp/app/overview/courses-routing.module.ts
+++ b/src/main/webapp/app/overview/courses-routing.module.ts
@@ -67,7 +67,7 @@ const routes: Routes = [
},
{
path: 'learning-path',
- loadChildren: () => import('./course-learning-path/course-learning-path.module').then((m) => m.CourseLearningPathModule),
+ loadChildren: () => import('app/course/learning-paths/learning-paths.module').then((m) => m.ArtemisLearningPathsModule),
},
{
path: 'discussion',
diff --git a/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java b/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java
index ab93c366b286..f487324867b7 100644
--- a/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java
+++ b/src/test/java/de/tum/in/www1/artemis/competency/CompetencyUtilService.java
@@ -8,7 +8,10 @@
import de.tum.in.www1.artemis.domain.competency.Competency;
import de.tum.in.www1.artemis.domain.competency.CompetencyRelation;
import de.tum.in.www1.artemis.domain.lecture.LectureUnit;
-import de.tum.in.www1.artemis.repository.*;
+import de.tum.in.www1.artemis.repository.CompetencyRelationRepository;
+import de.tum.in.www1.artemis.repository.CompetencyRepository;
+import de.tum.in.www1.artemis.repository.ExerciseRepository;
+import de.tum.in.www1.artemis.repository.LectureUnitRepository;
/**
* Service responsible for initializing the database with specific testdata related to competencies for use in integration tests.
diff --git a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
index 7e49d00c7bb0..ed19e2442d81 100644
--- a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
+++ b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
@@ -44,6 +44,12 @@ public Course enableAndGenerateLearningPathsForCourse(Course course) {
return courseRepository.save(eagerlyLoadedCourse);
}
+ /**
+ * Creates learning path for course.
+ *
+ * @param course the course for which the learning path should be generated
+ * @return the persisted learning path
+ */
public LearningPath createLearningPathInCourse(Course course) {
final var competencies = competencyRepository.findAllForCourse(course.getId());
LearningPath learningPath = createLearningPath(competencies);
@@ -51,6 +57,12 @@ public LearningPath createLearningPathInCourse(Course course) {
return learningPathRepository.save(learningPath);
}
+ /**
+ * Creates learning path.
+ *
+ * @param competencies the competencies that will be linked to the learning path
+ * @return the persisted learning path
+ */
public LearningPath createLearningPath(Set competencies) {
LearningPath learningPath = new LearningPath();
learningPath.setCompetencies(competencies);
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 646ed5301c62..211ce23947c4 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -6,9 +6,14 @@
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.test.context.support.WithMockUser;
@@ -151,8 +156,8 @@ private void testAllPreAuthorize() throws Exception {
request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.FORBIDDEN, LearningPath.class, pageableSearchUtilService.searchMapping(search));
}
- private Course enableLearningPathsRESTCall(Course course) throws Exception {
- return request.putWithResponseBody("/api/courses/" + course.getId() + "/learning-paths/enable", course, Course.class, HttpStatus.OK);
+ private void enableLearningPathsRESTCall(Course course) throws Exception {
+ request.putWithResponseBody("/api/courses/" + course.getId() + "/learning-paths/enable", course, Course.class, HttpStatus.OK);
}
private Competency createCompetencyRESTCall() throws Exception {
@@ -275,33 +280,38 @@ void testGetLearningPathsOnPageForCourseExactlyStudent() throws Exception {
assertThat(result.getResultsOnPage()).hasSize(1);
}
- @Test
- @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
- void testAddCompetencyToLearningPathsOnCreateCompetency() throws Exception {
- course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
-
- final var createdCompetency = createCompetencyRESTCall();
-
- final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
- final var learningPathOptional = learningPathRepository.findWithEagerCompetenciesByCourseIdAndUserId(course.getId(), student.getId());
- assertThat(learningPathOptional).isPresent();
- assertThat(learningPathOptional.get().getCompetencies()).as("should contain new competency").contains(createdCompetency);
- assertThat(learningPathOptional.get().getCompetencies().size()).as("should not remove old competencies").isEqualTo(competencies.length + 1);
- final var oldCompetencies = Set.of(competencies[0], competencies[1], competencies[2], competencies[3], competencies[4]);
- assertThat(learningPathOptional.get().getCompetencies()).as("should not remove old competencies").containsAll(oldCompetencies);
+ private static Stream addCompetencyToLearningPathsOnCreateAndImportCompetencyTestProvider() {
+ final Function createCall = (reference) -> {
+ try {
+ return reference.createCompetencyRESTCall();
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ };
+ final Function importCall = (reference) -> {
+ try {
+ return reference.importCompetencyRESTCall();
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ };
+ return Stream.of(Arguments.of(createCall), Arguments.of(importCall));
}
- @Test
+ @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}")
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
- void testAddCompetencyToLearningPathsOnImportCompetency() throws Exception {
+ @MethodSource("addCompetencyToLearningPathsOnCreateAndImportCompetencyTestProvider")
+ void addCompetencyToLearningPaths(Function restCall) {
course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
- final var importedCompetency = importCompetencyRESTCall();
+ final var newCompetency = restCall.apply(this);
final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
final var learningPathOptional = learningPathRepository.findWithEagerCompetenciesByCourseIdAndUserId(course.getId(), student.getId());
assertThat(learningPathOptional).isPresent();
- assertThat(learningPathOptional.get().getCompetencies()).as("should contain new competency").contains(importedCompetency);
+ assertThat(learningPathOptional.get().getCompetencies()).as("should contain new competency").contains(newCompetency);
assertThat(learningPathOptional.get().getCompetencies().size()).as("should not remove old competencies").isEqualTo(competencies.length + 1);
final var oldCompetencies = Set.of(competencies[0], competencies[1], competencies[2], competencies[3], competencies[4]);
assertThat(learningPathOptional.get().getCompetencies()).as("should not remove old competencies").containsAll(oldCompetencies);
From 4c298cf8148c11121b20bf1ca7e314fd06ff7818 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 21 Jul 2023 13:55:26 +0200
Subject: [PATCH 065/215] Fix icon after merge
---
src/main/webapp/app/overview/course-overview.component.html | 4 ++--
src/main/webapp/app/overview/course-overview.component.ts | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/main/webapp/app/overview/course-overview.component.html b/src/main/webapp/app/overview/course-overview.component.html
index 028058227b1c..82e45fdbe442 100644
--- a/src/main/webapp/app/overview/course-overview.component.html
+++ b/src/main/webapp/app/overview/course-overview.component.html
@@ -20,8 +20,8 @@
Competencies
-
- Learning Path
+
+ Learning Path
diff --git a/src/main/webapp/app/overview/course-overview.component.ts b/src/main/webapp/app/overview/course-overview.component.ts
index 835ac782f5b4..b1a8a54fb56c 100644
--- a/src/main/webapp/app/overview/course-overview.component.ts
+++ b/src/main/webapp/app/overview/course-overview.component.ts
@@ -23,6 +23,7 @@ import {
faFlag,
faGraduationCap,
faListAlt,
+ faNetworkWired,
faPersonChalkboard,
faSync,
faTable,
@@ -82,6 +83,7 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit
faWrench = faWrench;
faTable = faTable;
faFlag = faFlag;
+ faNetworkWired = faNetworkWired;
faListAlt = faListAlt;
faChartBar = faChartBar;
faFilePdf = faFilePdf;
From 7cbf8cb0fc43c13136cff74979c390e118ab27c3 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Sat, 22 Jul 2023 12:50:38 +0200
Subject: [PATCH 066/215] Apply most of Johannes suggestions
---
.../de/tum/in/www1/artemis/domain/Course.java | 2 +-
.../artemis/service/LearningPathService.java | 41 +++++------
.../web/rest/LearningPathResource.java | 19 ++---
.../dto/learningpath/NgxLearningPathDTO.java | 73 -------------------
.../resources/config/liquibase/master.xml | 2 +-
.../competency/LearningPathUtilService.java | 2 +-
.../lecture/LearningPathIntegrationTest.java | 4 +-
7 files changed, 32 insertions(+), 111 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/Course.java b/src/main/java/de/tum/in/www1/artemis/domain/Course.java
index 5fc1c954150b..be0f1b9cbfc7 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/Course.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/Course.java
@@ -733,7 +733,7 @@ public boolean getLearningPathsEnabled() {
return learningPathsEnabled;
}
- public void setLeanringPathsEnabled(boolean learningPathsEnabled) {
+ public void setLearningPathsEnabled(boolean learningPathsEnabled) {
this.learningPathsEnabled = learningPathsEnabled;
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index daccbbf8a581..3fd0ec8dc887 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -79,15 +79,17 @@ public void generateLearningPaths(@NotNull Course course) {
*/
public void generateLearningPathForUser(@NotNull Course course, @NotNull User user) {
var existingLearningPath = learningPathRepository.findByCourseIdAndUserId(course.getId(), user.getId());
- if (existingLearningPath.isEmpty()) {
- LearningPath lpToCreate = new LearningPath();
- lpToCreate.setUser(user);
- lpToCreate.setCourse(course);
- lpToCreate.getCompetencies().addAll(course.getCompetencies());
- var persistedLearningPath = learningPathRepository.save(lpToCreate);
- log.debug("Created LearningPath (id={}) for user (id={}) in course (id={})", persistedLearningPath.getId(), user.getId(), course.getId());
- updateLearningPathProgress(persistedLearningPath);
+ // the learning path has not to be generated if it already exits
+ if (existingLearningPath.isPresent()) {
+ return;
}
+ LearningPath lpToCreate = new LearningPath();
+ lpToCreate.setUser(user);
+ lpToCreate.setCourse(course);
+ lpToCreate.getCompetencies().addAll(course.getCompetencies());
+ var persistedLearningPath = learningPathRepository.save(lpToCreate);
+ log.debug("Created LearningPath (id={}) for user (id={}) in course (id={})", persistedLearningPath.getId(), user.getId(), course.getId());
+ updateLearningPathProgress(persistedLearningPath);
}
/**
@@ -97,7 +99,7 @@ public void generateLearningPathForUser(@NotNull Course course, @NotNull User us
* @param course the course the learning paths are linked to
* @return A wrapper object containing a list of all found learning paths and the total number of pages
*/
- public SearchResultPageDTO getAllOfCourseOnPageWithSize(final PageableSearchDTO search, final Course course) {
+ public SearchResultPageDTO getAllOfCourseOnPageWithSize(@NotNull PageableSearchDTO search, @NotNull Course course) {
final var pageable = PageUtil.createLearningPathPageRequest(search);
final var searchTerm = search.getSearchTerm();
final Page learningPathPage = learningPathRepository.findByLoginOrNameInCourse(searchTerm, course.getId(), pageable);
@@ -110,7 +112,7 @@ public SearchResultPageDTO getAllOfCourseOnPageWithSize(final Page
* @param competency Competency that should be added to each learning path
* @param courseId course id that the learning paths belong to
*/
- public void linkCompetencyToLearningPathsOfCourse(Competency competency, long courseId) {
+ public void linkCompetencyToLearningPathsOfCourse(@NotNull Competency competency, long courseId) {
var course = courseRepository.findWithEagerLearningPathsAndCompetenciesByIdElseThrow(courseId);
var learningPaths = course.getLearningPaths();
learningPaths.forEach(learningPath -> learningPath.addCompetency(competency));
@@ -124,7 +126,7 @@ public void linkCompetencyToLearningPathsOfCourse(Competency competency, long co
* @param competency Competency that should be removed from each learning path
* @param courseId course id that the learning paths belong to
*/
- public void removeLinkedCompetencyFromLearningPathsOfCourse(Competency competency, long courseId) {
+ public void removeLinkedCompetencyFromLearningPathsOfCourse(@NotNull Competency competency, long courseId) {
var course = courseRepository.findWithEagerLearningPathsAndCompetenciesByIdElseThrow(courseId);
var learningPaths = course.getLearningPaths();
learningPaths.forEach(learningPath -> learningPath.removeCompetency(competency));
@@ -132,18 +134,13 @@ public void removeLinkedCompetencyFromLearningPathsOfCourse(Competency competenc
log.debug("Removed linked competency (id={}) from learning paths", competency.getId());
}
- public void updateLearningPathProgress(final long learningPathId) {
- final var learningPath = learningPathRepository.findWithEagerCompetenciesByIdElseThrow(learningPathId);
- this.updateLearningPathProgress(learningPath);
- }
-
/**
* Updates progress of the learning path specified by course and user id.
*
* @param courseId id of the course the learning path is linked to
* @param userId id of the user the learning path is linked to
*/
- public void updateLearningPathProgress(final long courseId, final long userId) {
+ public void updateLearningPathProgress(long courseId, long userId) {
final var learningPath = learningPathRepository.findWithEagerCompetenciesByCourseIdAndUserId(courseId, userId);
learningPath.ifPresent(this::updateLearningPathProgress);
}
@@ -153,18 +150,18 @@ public void updateLearningPathProgress(final long courseId, final long userId) {
*
* @param learningPath learning path that is updated
*/
- private void updateLearningPathProgress(final LearningPath learningPath) {
+ private void updateLearningPathProgress(@NotNull LearningPath learningPath) {
final var userId = learningPath.getUser().getId();
final var competencyIds = learningPath.getCompetencies().stream().map(Competency::getId).collect(Collectors.toSet());
final var competencyProgresses = competencyProgressRepository.findAllByCompetencyIdsAndUserId(competencyIds, userId);
- final var completed = (float) competencyProgresses.stream().filter(CompetencyProgressService::isMastered).count();
+ final float completed = competencyProgresses.stream().filter(CompetencyProgressService::isMastered).count();
final var numberOfCompetencies = learningPath.getCompetencies().size();
if (numberOfCompetencies == 0) {
learningPath.setProgress(0);
}
else {
- learningPath.setProgress(Math.round(completed * 100 / (float) numberOfCompetencies));
+ learningPath.setProgress(Math.round(completed * 100 / numberOfCompetencies));
}
learningPathRepository.save(learningPath);
log.debug("Updated LearningPath (id={}) for user (id={})", learningPath.getId(), userId);
@@ -177,7 +174,7 @@ private void updateLearningPathProgress(final LearningPath learningPath) {
* @return Ngx representation of the learning path
* @see NgxLearningPathDTO
*/
- public NgxLearningPathDTO generateNgxRepresentation(LearningPath learningPath) {
+ public NgxLearningPathDTO generateNgxRepresentation(@NotNull LearningPath learningPath) {
Set nodes = new HashSet<>();
Set edges = new HashSet<>();
Set clusters = new HashSet<>();
@@ -399,7 +396,7 @@ public static String getDirectEdgeId(long competencyId) {
return "edge-" + competencyId + "-direct";
}
- public LearningObject getRecommendation(LearningPath learningPath) {
+ public LearningObject getRecommendation(@NotNull LearningPath learningPath) {
return learningPath.getCompetencies().stream()
.flatMap(competency -> Stream.concat(
competency.getLectureUnits().stream()
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 3c0968ee3519..5c69f778ca2f 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -62,15 +62,13 @@ public LearningPathResource(CourseRepository courseRepository, AuthorizationChec
public ResponseEntity enableLearningPathsForCourse(@PathVariable Long courseId) {
log.debug("REST request to enable learning paths for course with id: {}", courseId);
Course course = courseRepository.findWithEagerCompetenciesByIdElseThrow(courseId);
- User user = userRepository.getUserWithGroupsAndAuthorities();
- authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, user);
+ authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
if (course.getLearningPathsEnabled()) {
throw new BadRequestException("Learning paths are already enabled for this course.");
}
+ course.setLearningPathsEnabled(true);
learningPathService.generateLearningPaths(course);
-
- course.setLeanringPathsEnabled(true);
course = courseRepository.save(course);
return ResponseEntity.ok(course);
@@ -88,8 +86,7 @@ public ResponseEntity enableLearningPathsForCourse(@PathVariable Long co
public ResponseEntity> getLearningPathsOnPage(@PathVariable Long courseId, PageableSearchDTO search) {
log.debug("REST request to get learning paths for course with id: {}", courseId);
Course course = courseRepository.findByIdElseThrow(courseId);
- User user = userRepository.getUserWithGroupsAndAuthorities();
- authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, user);
+ authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
if (!course.getLearningPathsEnabled()) {
throw new BadRequestException("Learning paths are not enabled for this course.");
}
@@ -108,11 +105,11 @@ public ResponseEntity> getLearningPathsOnPage(
public ResponseEntity getLearningPathId(@PathVariable Long courseId) {
log.debug("REST request to get learning path id for course with id: {}", courseId);
Course course = courseRepository.findByIdElseThrow(courseId);
- User user = userRepository.getUserWithGroupsAndAuthorities();
- authorizationCheckService.isStudentInCourse(course, user);
+ authorizationCheckService.isStudentInCourse(course, null);
if (!course.getLearningPathsEnabled()) {
throw new BadRequestException("Learning paths are not enabled for this course.");
}
+ User user = userRepository.getUser();
LearningPath learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), user.getId());
return ResponseEntity.ok(learningPath.getId());
}
@@ -132,13 +129,13 @@ public ResponseEntity getNgxLearningPath(@PathVariable Long
if (!course.getLearningPathsEnabled()) {
throw new BadRequestException("Learning paths are not enabled for this course.");
}
- User user = userRepository.getUserWithGroupsAndAuthorities();
- if (authorizationCheckService.isStudentInCourse(course, user)) {
+ if (authorizationCheckService.isStudentInCourse(course, null)) {
+ final var user = userRepository.getUser();
if (!user.getId().equals(learningPath.getUser().getId())) {
throw new AccessForbiddenException("You are not allowed to access another users learning path.");
}
}
- else if (!authorizationCheckService.isAtLeastInstructorInCourse(course, user) && !authorizationCheckService.isAdmin()) {
+ else if (!authorizationCheckService.isAtLeastInstructorInCourse(course, null) && !authorizationCheckService.isAdmin()) {
throw new AccessForbiddenException("You are not allowed to access another users learning path.");
}
NgxLearningPathDTO graph = learningPathService.generateNgxRepresentation(learningPath);
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java
index 9767380451be..5fec75ccb92c 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java
@@ -1,6 +1,5 @@
package de.tum.in.www1.artemis.web.rest.dto.learningpath;
-import java.util.Objects;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -11,24 +10,6 @@
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record NgxLearningPathDTO(Set nodes, Set edges, Set clusters) {
- @Override
- public int hashCode() {
- return Objects.hash(nodes, edges, clusters);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof NgxLearningPathDTO other)) {
- return false;
- }
- return nodes.equals(other.nodes) && edges.equals(other.edges) && clusters.equals(other.clusters);
- }
-
- @Override
- public String toString() {
- return "NgxLearningPathDTO{nodes=" + nodes + ", edges=" + edges + ", clusters=" + clusters + "}";
- }
-
public record Node(String id, NodeType type, long linkedResource, boolean completed, String label) {
public Node(String id, NodeType type, long linkedResource, String label) {
@@ -46,66 +27,12 @@ public Node(String id, NodeType type, long linkedResource) {
public Node(String id, NodeType type) {
this(id, type, -1);
}
-
- @Override
- public int hashCode() {
- return Objects.hashCode(id);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof Node other)) {
- return false;
- }
- return id.equals(other.id) && type.equals(other.type) && linkedResource == other.linkedResource && label.equals(other.label);
- }
-
- @Override
- public String toString() {
- return "Node{id=" + id + ", type=" + type.name() + ", linkedResource=" + linkedResource + ", completed=" + completed + ", label=" + label + "}";
- }
}
public record Edge(String id, String source, String target) {
-
- @Override
- public int hashCode() {
- return Objects.hashCode(id);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof Edge other)) {
- return false;
- }
- return id.equals(other.id) && source.equals(other.source) && target.equals(other.target);
- }
-
- @Override
- public String toString() {
- return "Edge{id=" + id + ", source=" + source + ", target=" + target + "}";
- }
}
public record Cluster(String id, String label, Set childNodeIds) {
-
- @Override
- public int hashCode() {
- return Objects.hashCode(id);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof Cluster other)) {
- return false;
- }
- return id.equals(other.id) && label.equals(other.label) && childNodeIds.equals(other.childNodeIds);
- }
-
- @Override
- public String toString() {
- return "Cluster{id=" + id + ", label=" + label + ", childNodeIds=" + childNodeIds + "}";
- }
}
public enum NodeType {
diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml
index 351c0edf3bc7..056d0bac0f47 100644
--- a/src/main/resources/config/liquibase/master.xml
+++ b/src/main/resources/config/liquibase/master.xml
@@ -43,10 +43,10 @@
-
+
diff --git a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
index ed19e2442d81..9c116978e58e 100644
--- a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
+++ b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
@@ -40,7 +40,7 @@ public class LearningPathUtilService {
public Course enableAndGenerateLearningPathsForCourse(Course course) {
var eagerlyLoadedCourse = courseRepository.findWithEagerLearningPathsAndCompetenciesByIdElseThrow(course.getId());
learningPathService.generateLearningPaths(eagerlyLoadedCourse);
- eagerlyLoadedCourse.setLeanringPathsEnabled(true);
+ eagerlyLoadedCourse.setLearningPathsEnabled(true);
return courseRepository.save(eagerlyLoadedCourse);
}
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 211ce23947c4..49b39ff537c5 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -223,7 +223,7 @@ void testEnableLearningPathsWithNoCompetencies() throws Exception {
@Test
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
void testEnableLearningPathsAlreadyEnabled() throws Exception {
- course.setLeanringPathsEnabled(true);
+ course.setLearningPathsEnabled(true);
courseRepository.save(course);
request.putWithResponseBody("/api/courses/" + course.getId() + "/learning-paths/enable", course, Course.class, HttpStatus.BAD_REQUEST);
}
@@ -358,7 +358,7 @@ void testGetNgxLearningPathForLearningPathsDisabled() throws Exception {
course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
- course.setLeanringPathsEnabled(false);
+ course.setLearningPathsEnabled(false);
courseRepository.save(course);
request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.BAD_REQUEST, NgxLearningPathDTO.class);
}
From 8c25501d2a2d0102eaf3adf7cb0a46f16becea43 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 24 Jul 2023 11:42:21 +0200
Subject: [PATCH 067/215] fix load learning paths after enable
---
.../learning-path-management.component.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index 46526a35925b..d831ae230e4c 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -154,7 +154,7 @@ export class LearningPathManagementComponent implements OnInit, OnDestroy {
.enableLearningPaths(this.courseId)
.pipe(
finalize(() => {
- this.isLoading = false;
+ this.loadData();
}),
)
.subscribe({
From 6d4995630f6f04e4856e79b427473a6c81446add Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 24 Jul 2023 13:59:24 +0200
Subject: [PATCH 068/215] add navigation to progress view
---
.../learning-path-graph.component.ts | 5 +++++
.../learning-path-progress-modal.component.html | 11 ++++++++++-
.../learning-path-progress-modal.component.scss | 13 +++++++++++++
.../learning-path-progress-modal.component.ts | 4 +++-
.../learning-path-progress-nav.component.html | 11 +++++++++++
.../learning-path-progress-nav.component.ts | 17 +++++++++++++++++
.../learning-paths/learning-paths.module.ts | 4 +++-
7 files changed, 62 insertions(+), 3 deletions(-)
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
index 860236e0481b..b1ed386de835 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
@@ -50,4 +50,9 @@ export class LearningPathGraphComponent implements OnInit {
this.center$.next(true);
this.zoomToFit$.next(true);
}
+
+ onCenterView() {
+ this.zoomToFit$.next(true);
+ this.center$.next(true);
+ }
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
index 5d15f51178bf..4b38a926a616 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
@@ -1,3 +1,12 @@
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
index 813a13004d4a..5db8905472ca 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
@@ -1,6 +1,19 @@
.modal-container {
display: flex;
+ flex-wrap: wrap;
height: 90vh;
+ width: 100%;
+ padding-top: 8px;
+
+ .row {
+ width: 100%;
+ margin-left: 0;
+ margin-right: 0;
+ }
+
+ .modal-nav {
+ height: max-content;
+ }
.graph {
width: 100%;
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
index 478979f37922..80fb732c23d3 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
@@ -1,5 +1,6 @@
-import { Component, Input } from '@angular/core';
+import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
@Component({
selector: 'jhi-learning-path-progress-modal',
styleUrls: ['./learning-path-progress-modal.component.scss'],
@@ -7,6 +8,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
})
export class LearningPathProgressModalComponent {
@Input() learningPathId: number;
+ @ViewChild('learningPathGraphComponent') learningPathGraphComponent: LearningPathGraphComponent;
constructor(private activeModal: NgbActiveModal) {}
close() {
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
new file mode 100644
index 000000000000..c56b9512b4e9
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
@@ -0,0 +1,11 @@
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
new file mode 100644
index 000000000000..3d968549da59
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
@@ -0,0 +1,17 @@
+import { Component, EventEmitter, Output } from '@angular/core';
+import { faArrowsRotate, faArrowsToEye, faXmark } from '@fortawesome/free-solid-svg-icons';
+
+@Component({
+ selector: 'jhi-learning-path-progress-nav',
+ templateUrl: './learning-path-progress-nav.component.html',
+})
+export class LearningPathProgressNavComponent {
+ @Output() onRefresh: EventEmitter = new EventEmitter();
+ @Output() onCenterView: EventEmitter = new EventEmitter();
+ @Output() onClose: EventEmitter = new EventEmitter();
+
+ // icons
+ faXmark = faXmark;
+ faArrowsToEye = faArrowsToEye;
+ faArrowsRotate = faArrowsRotate;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
index 7f03e4c6e2f0..3d635a6ea790 100644
--- a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
@@ -13,6 +13,7 @@ import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learni
import { NgxGraphModule } from '@swimlane/ngx-graph';
import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
+import { LearningPathProgressNavComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-nav.component';
const routes: Routes = [
{
@@ -58,10 +59,11 @@ const routes: Routes = [
declarations: [
LearningPathContainerComponent,
LearningPathManagementComponent,
+ LearningPathProgressModalComponent,
+ LearningPathProgressNavComponent,
LearningPathGraphSidebarComponent,
LearningPathGraphComponent,
LearningPathGraphNodeComponent,
- LearningPathProgressModalComponent,
],
exports: [LearningPathContainerComponent],
})
From e829eb2831c87c916994e73c67f314a38ec688f2 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 24 Jul 2023 15:12:56 +0200
Subject: [PATCH 069/215] fix learning content style
---
.../learning-path-container.component.html | 4 ++--
.../learning-path-container.component.scss | 13 +++++++++++++
.../learning-path-container.component.ts | 4 +++-
3 files changed, 18 insertions(+), 3 deletions(-)
create mode 100644 src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.scss
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
index 8c24c96375eb..ee54c1faa122 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
@@ -1,9 +1,9 @@
-
+
-
+
No task selected
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.scss b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.scss
new file mode 100644
index 000000000000..7b9a664c9b4b
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.scss
@@ -0,0 +1,13 @@
+.main-container {
+ width: 100%;
+ margin-left: 0;
+}
+
+.course-info-bar {
+ margin: 0 !important;
+}
+
+.tab-bar-exercise-details {
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
index 53793b40c7d5..6077305a828a 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
import { Exercise } from 'app/entities/exercise.model';
@@ -16,7 +16,9 @@ import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service'
@Component({
selector: 'jhi-learning-path-container',
+ styleUrls: ['./learning-path-container.component.scss'],
templateUrl: './learning-path-container.component.html',
+ encapsulation: ViewEncapsulation.None,
})
export class LearningPathContainerComponent implements OnInit {
@Input() courseId: number;
From 7e5e47b748dee6e564dbe1ef37469a72e5fc6c5a Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Tue, 25 Jul 2023 13:52:38 +0200
Subject: [PATCH 070/215] add improvements
---
.../learning-path-graph-node.component.html | 35 +++++-
.../learning-path-graph-node.component.ts | 7 +-
.../learning-path-graph.component.html | 2 +-
.../learning-path-graph.component.scss | 22 ++++
.../learning-path-graph.component.ts | 6 +-
.../competency-node-details.component.html | 18 +++
.../competency-node-details.component.ts | 58 ++++++++++
.../exercise-node-details.component.html | 0
.../exercise-node-details.component.ts | 35 ++++++
.../lecture-unit-node-details.component.html | 14 +++
.../lecture-unit-node-details.component.ts | 42 +++++++
.../learning-path-management.component.ts | 1 +
...earning-path-progress-modal.component.html | 2 +-
.../learning-path-progress-modal.component.ts | 1 +
.../learning-paths/learning-paths.module.ts | 18 ++-
.../lecture-unit/lectureUnit.model.ts | 35 +++++-
src/main/webapp/app/shared/shared.module.ts | 3 +
.../sticky-popover.directive.ts | 105 ++++++++++++++++++
18 files changed, 394 insertions(+), 10 deletions(-)
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
create mode 100644 src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
index dc85250d2733..ba069046e5d7 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
@@ -1,7 +1,36 @@
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
index 39d2baf34dbe..f18d36471db6 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
@@ -1,12 +1,14 @@
-import { Component, Input } from '@angular/core';
+import { Component, Input, ViewChild } from '@angular/core';
import { faCheckCircle, faCircle, faPlayCircle, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
import { NgxLearningPathNode, NodeType } from 'app/entities/learning-path.model';
+import { NgbPopover } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'jhi-learning-path-graph-node',
templateUrl: './learning-path-graph-node.component.html',
})
export class LearningPathGraphNodeComponent {
+ @Input() courseId: number;
@Input() node: NgxLearningPathNode;
//icons
@@ -16,4 +18,7 @@ export class LearningPathGraphNodeComponent {
faCircle = faCircle;
protected readonly NodeType = NodeType;
+
+ @ViewChild('inspectPopover')
+ private inspectPopover: NgbPopover;
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
index 8da15aa6ade3..c72307fe6a29 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
@@ -23,7 +23,7 @@
-
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
index 0a66e3296ee5..8b931cbdf212 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
@@ -13,3 +13,25 @@
height: 100%;
}
}
+
+jhi-learning-path-graph-node:hover {
+ cursor: pointer;
+}
+
+.node-icon-container {
+ width: 100%;
+ display: flex;
+
+ fa-icon {
+ width: 100%;
+ svg {
+ margin: 10%;
+ width: 80%;
+ height: 80%;
+ }
+ }
+}
+
+.completed {
+ color: var(--bs-success);
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
index 860236e0481b..dc13c262d8ad 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
@@ -1,20 +1,22 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Layout } from '@swimlane/ngx-graph';
import * as shape from 'd3-shape';
import { Subject } from 'rxjs';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
-import { NgxLearningPathDTO } from 'app/entities/learning-path.model';
+import { NgxLearningPathDTO, NgxLearningPathNode } from 'app/entities/learning-path.model';
@Component({
selector: 'jhi-learning-path-graph',
styleUrls: ['./learning-path-graph.component.scss'],
templateUrl: './learning-path-graph.component.html',
+ encapsulation: ViewEncapsulation.None,
})
export class LearningPathGraphComponent implements OnInit {
isLoading = false;
@Input() learningPathId: number;
@Input() courseId: number;
+ @Output() nodeClicked: EventEmitter
= new EventEmitter();
ngxLearningPath: NgxLearningPathDTO;
layout: string | Layout = 'dagreCluster';
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
new file mode 100644
index 000000000000..b84a4cc2cfcf
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
@@ -0,0 +1,18 @@
+
+
+
+
+ {{ competency!.title }}
+ = 100" class="badge text-white text-bg-success" jhiTranslate="artemisApp.competency.mastered">Mastered
+ Optional
+
+
{{ competency.description }}
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts
new file mode 100644
index 000000000000..24cb6455586b
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts
@@ -0,0 +1,58 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { HttpErrorResponse } from '@angular/common/http';
+import { onError } from 'app/shared/util/global.utils';
+import { CompetencyService } from 'app/course/competencies/competency.service';
+import { Competency, CompetencyProgress, getIcon, getIconTooltip } from 'app/entities/competency.model';
+import { AlertService } from 'app/core/util/alert.service';
+
+@Component({
+ selector: 'jhi-competency-node-details',
+ templateUrl: './competency-node-details.component.html',
+})
+export class CompetencyNodeDetailsComponent implements OnInit {
+ @Input() courseId: number;
+ @Input() competencyId: number;
+ competency: Competency;
+ competencyProgress: CompetencyProgress;
+
+ isLoading = false;
+
+ constructor(private competencyService: CompetencyService, private alertService: AlertService) {}
+
+ ngOnInit() {
+ if (this.competencyId && this.courseId) {
+ this.loadData();
+ }
+ }
+ private loadData() {
+ this.isLoading = true;
+ this.competencyService.findById(this.competencyId!, this.courseId!).subscribe({
+ next: (resp) => {
+ this.competency = resp.body!;
+ if (this.competency.userProgress?.length) {
+ this.competencyProgress = this.competency.userProgress.first()!;
+ } else {
+ this.competencyProgress = { progress: 0, confidence: 0 } as CompetencyProgress;
+ }
+ this.isLoading = false;
+ },
+ error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
+ });
+ }
+
+ get progress(): number {
+ return Math.round(this.competencyProgress.progress ?? 0);
+ }
+
+ get confidence(): number {
+ return Math.min(Math.round(((this.competencyProgress.confidence ?? 0) / (this.competency.masteryThreshold ?? 100)) * 100), 100);
+ }
+
+ get mastery(): number {
+ const weight = 2 / 3;
+ return Math.round((1 - weight) * this.progress + weight * this.confidence);
+ }
+
+ protected readonly getIcon = getIcon;
+ protected readonly getIconTooltip = getIconTooltip;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
new file mode 100644
index 000000000000..5f081d966294
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
@@ -0,0 +1,35 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { HttpErrorResponse } from '@angular/common/http';
+import { onError } from 'app/shared/util/global.utils';
+import { AlertService } from 'app/core/util/alert.service';
+import { Exercise } from 'app/entities/exercise.model';
+import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
+
+@Component({
+ selector: 'jhi-exercise-node-details',
+ templateUrl: './exercise-node-details.component.html',
+})
+export class ExerciseNodeDetailsComponent implements OnInit {
+ @Input() exerciseId: number;
+
+ exercise: Exercise;
+
+ isLoading = false;
+
+ constructor(private exerciseService: ExerciseService, private alertService: AlertService) {}
+
+ ngOnInit() {
+ if (this.exerciseId) {
+ this.loadData();
+ }
+ }
+ private loadData() {
+ this.isLoading = true;
+ this.exerciseService.find(this.exerciseId).subscribe({
+ next: (exerciseResponse) => {
+ this.exercise = exerciseResponse.body!;
+ },
+ error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
+ });
+ }
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
new file mode 100644
index 000000000000..fe46e533a422
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
@@ -0,0 +1,14 @@
+
+
+
+
+ {{ lectureUnit.name }}
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
new file mode 100644
index 000000000000..7c8a5245b9d2
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
@@ -0,0 +1,42 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { HttpErrorResponse } from '@angular/common/http';
+import { onError } from 'app/shared/util/global.utils';
+import { AlertService } from 'app/core/util/alert.service';
+import { LectureService } from 'app/lecture/lecture.service';
+import { Lecture } from 'app/entities/lecture.model';
+import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
+
+@Component({
+ selector: 'jhi-lecture-unit-node-details',
+ templateUrl: './lecture-unit-node-details.component.html',
+})
+export class LectureUnitNodeDetailsComponent implements OnInit {
+ @Input() lectureId: number;
+ @Input() lectureUnitId: number;
+
+ lecture: Lecture;
+ lectureUnit: LectureUnit;
+
+ isLoading = false;
+
+ constructor(private lectureService: LectureService, private alertService: AlertService) {}
+
+ ngOnInit() {
+ if (this.lectureId && this.lectureUnitId) {
+ this.loadData();
+ }
+ }
+ private loadData() {
+ this.isLoading = true;
+ this.lectureService.findWithDetails(this.lectureId!).subscribe({
+ next: (findLectureResult) => {
+ this.lecture = findLectureResult.body!;
+ if (this.lecture?.lectureUnits) {
+ this.lectureUnit = this.lecture.lectureUnits.find((lectureUnit) => lectureUnit.id === this.lectureUnitId)!;
+ }
+ this.isLoading = false;
+ },
+ error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
+ });
+ }
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index 46526a35925b..4753bb24d43b 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -210,6 +210,7 @@ export class LearningPathManagementComponent implements OnInit, OnDestroy {
backdrop: 'static',
windowClass: 'learning-path-modal',
});
+ modalRef.componentInstance.courseId = this.courseId;
modalRef.componentInstance.learningPathId = learningPath.id;
}
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
index 5d15f51178bf..fb22130021fe 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
@@ -1,3 +1,3 @@
-
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
index 478979f37922..5a7e48201527 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
@@ -6,6 +6,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
templateUrl: './learning-path-progress-modal.component.html',
})
export class LearningPathProgressModalComponent {
+ @Input() courseId: number;
@Input() learningPathId: number;
constructor(private activeModal: NgbActiveModal) {}
diff --git a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
index 7f03e4c6e2f0..dd2ebd8beba4 100644
--- a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
@@ -13,6 +13,10 @@ import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learni
import { NgxGraphModule } from '@swimlane/ngx-graph';
import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
+import { CompetencyNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component';
+import { ArtemisCompetenciesModule } from 'app/course/competencies/competency.module';
+import { LectureUnitNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component';
+import { ExerciseNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component';
const routes: Routes = [
{
@@ -54,7 +58,16 @@ const routes: Routes = [
];
@NgModule({
- imports: [ArtemisSharedModule, FormsModule, ReactiveFormsModule, ArtemisSharedComponentModule, NgxGraphModule, RouterModule.forChild(routes), ArtemisLectureUnitsModule],
+ imports: [
+ ArtemisSharedModule,
+ FormsModule,
+ ReactiveFormsModule,
+ ArtemisSharedComponentModule,
+ NgxGraphModule,
+ RouterModule.forChild(routes),
+ ArtemisLectureUnitsModule,
+ ArtemisCompetenciesModule,
+ ],
declarations: [
LearningPathContainerComponent,
LearningPathManagementComponent,
@@ -62,6 +75,9 @@ const routes: Routes = [
LearningPathGraphComponent,
LearningPathGraphNodeComponent,
LearningPathProgressModalComponent,
+ CompetencyNodeDetailsComponent,
+ LectureUnitNodeDetailsComponent,
+ ExerciseNodeDetailsComponent,
],
exports: [LearningPathContainerComponent],
})
diff --git a/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts b/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
index 647d462e0cb7..e47782e08fee 100644
--- a/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
+++ b/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
@@ -1,7 +1,9 @@
import { BaseEntity } from 'app/shared/model/base-entity';
import dayjs from 'dayjs/esm';
import { Lecture } from 'app/entities/lecture.model';
-import { Competency } from 'app/entities/competency.model';
+import { Competency, CompetencyTaxonomy } from 'app/entities/competency.model';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { faBrain, faComments, faDownload, faLink, faQuestion, faScroll, faVideo } from '@fortawesome/free-solid-svg-icons';
// IMPORTANT NOTICE: The following strings have to be consistent with
// the ones defined in LectureUnit.java
@@ -13,6 +15,22 @@ export enum LectureUnitType {
ONLINE = 'online',
}
+export const lectureUnitIcons = {
+ [LectureUnitType.ATTACHMENT]: faDownload,
+ [LectureUnitType.EXERCISE]: faQuestion,
+ [LectureUnitType.TEXT]: faScroll,
+ [LectureUnitType.VIDEO]: faVideo,
+ [LectureUnitType.ONLINE]: faLink,
+};
+
+export const lectureUnitTooltips = {
+ [LectureUnitType.ATTACHMENT]: 'artemisApp.attachmentUnit.tooltip',
+ [LectureUnitType.EXERCISE]: '',
+ [LectureUnitType.TEXT]: 'artemisApp.textUnit.tooltip',
+ [LectureUnitType.VIDEO]: 'artemisApp.videoUnit.tooltip',
+ [LectureUnitType.ONLINE]: 'artemisApp.onlineUnit.tooltip',
+};
+
export abstract class LectureUnit implements BaseEntity {
public id?: number;
public name?: string;
@@ -27,4 +45,19 @@ export abstract class LectureUnit implements BaseEntity {
protected constructor(type: LectureUnitType) {
this.type = type;
}
+
+ get getIcon(): IconProp {
+ if (!this.type) {
+ return faQuestion as IconProp;
+ }
+ return lectureUnitIcons[this.type] as IconProp;
+ }
+
+ getIconTooltip(): string {
+ if (!this.type) {
+ return '';
+ }
+
+ return lectureUnitTooltips[this.type];
+ }
}
diff --git a/src/main/webapp/app/shared/shared.module.ts b/src/main/webapp/app/shared/shared.module.ts
index 108a76462502..774efdf960a3 100644
--- a/src/main/webapp/app/shared/shared.module.ts
+++ b/src/main/webapp/app/shared/shared.module.ts
@@ -23,6 +23,7 @@ import { AssessmentWarningComponent } from 'app/assessment/assessment-warning/as
import { JhiConnectionWarningComponent } from 'app/shared/connection-warning/connection-warning.component';
import { LoadingIndicatorContainerComponent } from 'app/shared/loading-indicator-container/loading-indicator-container.component';
import { CompetencySelectionComponent } from 'app/shared/competency-selection/competency-selection.component';
+import { StickyPopoverDirective } from 'app/shared/sticky-popover/sticky-popover.directive';
@NgModule({
imports: [ArtemisSharedLibsModule, ArtemisSharedCommonModule, ArtemisSharedPipesModule, RouterModule],
@@ -47,6 +48,7 @@ import { CompetencySelectionComponent } from 'app/shared/competency-selection/co
ItemCountComponent,
ConsistencyCheckComponent,
AssessmentWarningComponent,
+ StickyPopoverDirective,
],
exports: [
ArtemisSharedLibsModule,
@@ -72,6 +74,7 @@ import { CompetencySelectionComponent } from 'app/shared/competency-selection/co
ConsistencyCheckComponent,
AssessmentWarningComponent,
CompetencySelectionComponent,
+ StickyPopoverDirective,
],
})
export class ArtemisSharedModule {}
diff --git a/src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts b/src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts
new file mode 100644
index 000000000000..4950c06f7937
--- /dev/null
+++ b/src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts
@@ -0,0 +1,105 @@
+import {
+ ApplicationRef,
+ ChangeDetectorRef,
+ Directive,
+ ElementRef,
+ Inject,
+ Injector,
+ Input,
+ NgZone,
+ OnDestroy,
+ OnInit,
+ Renderer2,
+ TemplateRef,
+ ViewContainerRef,
+} from '@angular/core';
+
+import { DOCUMENT } from '@angular/common';
+import { NgbPopover, NgbPopoverConfig } from '@ng-bootstrap/ng-bootstrap';
+@Directive({
+ selector: '[jhiStickyPopover]',
+})
+export class StickyPopoverDirective extends NgbPopover implements OnInit, OnDestroy {
+ @Input() jhiStickyPopover: TemplateRef;
+
+ popoverTitle: string;
+
+ triggers: string;
+ container: string;
+ ngpPopover: TemplateRef;
+ canClosePopover: boolean;
+
+ toggle(): void {
+ super.toggle();
+ }
+
+ isOpen(): boolean {
+ return super.isOpen();
+ }
+
+ constructor(
+ private _elRef: ElementRef,
+ private _render: Renderer2,
+ injector: Injector,
+ private viewContainerRef: ViewContainerRef,
+ config: NgbPopoverConfig,
+ ngZone: NgZone,
+ private changeRef: ChangeDetectorRef,
+ private applicationRef: ApplicationRef,
+ @Inject(DOCUMENT) _document: any,
+ ) {
+ super(_elRef, _render, injector, viewContainerRef, config, ngZone, _document, changeRef, applicationRef);
+ this.triggers = 'manual';
+ this.popoverTitle = '';
+ this.container = 'body';
+ }
+
+ ngOnInit(): void {
+ super.ngOnInit();
+ this.ngbPopover = this.jhiStickyPopover;
+
+ this._render.listen(this._elRef.nativeElement, 'mouseenter', () => {
+ this.canClosePopover = true;
+ this.open();
+ });
+
+ this._render.listen(this._elRef.nativeElement, 'mouseleave', () => {
+ setTimeout(() => {
+ if (this.canClosePopover) {
+ this.close();
+ }
+ }, 100);
+ });
+
+ this._render.listen(this._elRef.nativeElement, 'click', () => {
+ this.close();
+ });
+ }
+
+ ngOnDestroy(): void {
+ super.ngOnDestroy();
+ }
+
+ open() {
+ super.open();
+ setTimeout(() => {
+ const popover = window.document.querySelector('.popover');
+ this._render.listen(popover, 'mouseover', () => {
+ this.canClosePopover = false;
+ });
+
+ this._render.listen(popover, 'mouseout', () => {
+ this.canClosePopover = true;
+ setTimeout(() => {
+ if (this.canClosePopover) {
+ this.close();
+ }
+ }, 0);
+ });
+ }, 0);
+ }
+
+ close() {
+ super.close();
+ }
+}
From ab3fc1c576eb19a85c1581a11d0fdadfdbd3ca6a Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Tue, 25 Jul 2023 14:09:56 +0200
Subject: [PATCH 071/215] add missing JavaDoc
---
.../de/tum/in/www1/artemis/service/LearningPathService.java | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index 3fd0ec8dc887..e62dab7dc611 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -396,6 +396,12 @@ public static String getDirectEdgeId(long competencyId) {
return "edge-" + competencyId + "-direct";
}
+ /**
+ * Gets a recommended learning object based on the current state of the given learning path.
+ *
+ * @param learningPath the learning path for which the recommendation should be computed
+ * @return recommended learning object
+ */
public LearningObject getRecommendation(@NotNull LearningPath learningPath) {
return learningPath.getCompetencies().stream()
.flatMap(competency -> Stream.concat(
From 4968662211738030ce36342666f991ec6c21425e Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 26 Jul 2023 16:45:39 +0200
Subject: [PATCH 072/215] Adapt new DTO restriction
---
.../repository/LearningPathRepository.java | 2 +-
.../artemis/service/LearningPathService.java | 13 ++++++-------
.../web/rest/LearningPathResource.java | 19 ++++++++++---------
.../LearningPathPageableSearchDTO.java | 11 +++++++++++
.../LearningPathRecommendationDTO.java | 8 ++++++++
.../NgxLearningPathDTO.java | 2 +-
.../LearningPathRecommendation.java | 8 --------
.../rest/dto/user/UserNameAndLoginDTO.java | 10 ++++++++++
src/main/webapp/app/core/user/user.model.ts | 8 ++++++++
.../learning-path-graph-node.component.ts | 2 +-
.../learning-path-graph.component.ts | 2 +-
.../learning-path-management.component.html | 2 +-
.../learning-path-management.component.ts | 12 ++++++------
.../learning-path-paging.service.ts | 4 ++--
.../learning-paths/learning-path.service.ts | 8 ++++----
.../learning-path-container.component.ts | 2 +-
.../{ => competency}/learning-path.model.ts | 10 ++++++++--
src/main/webapp/app/entities/course.model.ts | 2 +-
.../lecture/LearningPathIntegrationTest.java | 12 +++++++-----
.../service/LearningPathServiceTest.java | 2 +-
.../course/course-management.service.spec.ts | 2 +-
...learning-path-graph-node.component.spec.ts | 2 +-
...learning-path-management.component.spec.ts | 2 +-
.../learning-path-container.component.spec.ts | 6 +++---
24 files changed, 94 insertions(+), 57 deletions(-)
create mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathPageableSearchDTO.java
create mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathRecommendationDTO.java
rename src/main/java/de/tum/in/www1/artemis/web/rest/dto/{learningpath => competency}/NgxLearningPathDTO.java (95%)
delete mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/LearningPathRecommendation.java
create mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/user/UserNameAndLoginDTO.java
rename src/main/webapp/app/entities/{ => competency}/learning-path.model.ts (85%)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
index 4a08dfcc341a..81acb13fbff2 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
@@ -44,7 +44,7 @@ default LearningPath findWithEagerCompetenciesByCourseIdAndUserIdElseThrow(long
@Query("""
SELECT lp
FROM LearningPath lp
- WHERE (lp.course.id = :courseId) AND (lp.user.login LIKE %:searchTerm% OR lp.user.firstName LIKE %:searchTerm% OR lp.user.lastName LIKE %:searchTerm%)
+ WHERE (lp.course.id = :courseId) AND (lp.user.login LIKE %:searchTerm% OR CONCAT(lp.user.firstName, ' ', lp.user.lastName) LIKE %:searchTerm%)
""")
Page findByLoginOrNameInCourse(@Param("searchTerm") String searchTerm, @Param("courseId") long courseId, Pageable pageable);
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index e62dab7dc611..9a2fc5ca4922 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -1,9 +1,6 @@
package de.tum.in.www1.artemis.service;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.LongStream;
@@ -26,7 +23,8 @@
import de.tum.in.www1.artemis.repository.*;
import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO;
import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO;
-import de.tum.in.www1.artemis.web.rest.dto.learningpath.NgxLearningPathDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathPageableSearchDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
import de.tum.in.www1.artemis.web.rest.util.PageUtil;
@Service
@@ -99,11 +97,12 @@ public void generateLearningPathForUser(@NotNull Course course, @NotNull User us
* @param course the course the learning paths are linked to
* @return A wrapper object containing a list of all found learning paths and the total number of pages
*/
- public SearchResultPageDTO getAllOfCourseOnPageWithSize(@NotNull PageableSearchDTO search, @NotNull Course course) {
+ public SearchResultPageDTO getAllOfCourseOnPageWithSize(@NotNull PageableSearchDTO search, @NotNull Course course) {
final var pageable = PageUtil.createLearningPathPageRequest(search);
final var searchTerm = search.getSearchTerm();
final Page learningPathPage = learningPathRepository.findByLoginOrNameInCourse(searchTerm, course.getId(), pageable);
- return new SearchResultPageDTO<>(learningPathPage.getContent(), learningPathPage.getTotalPages());
+ final List contentDTOs = learningPathPage.getContent().stream().map(LearningPathPageableSearchDTO::new).toList();
+ return new SearchResultPageDTO<>(contentDTOs, learningPathPage.getTotalPages());
}
/**
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 5c69f778ca2f..8f07ba2d9332 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -22,8 +22,9 @@
import de.tum.in.www1.artemis.service.LearningPathService;
import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO;
import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO;
-import de.tum.in.www1.artemis.web.rest.dto.learningpath.LearningPathRecommendation;
-import de.tum.in.www1.artemis.web.rest.dto.learningpath.NgxLearningPathDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathPageableSearchDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathRecommendationDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;
@RestController
@@ -59,7 +60,7 @@ public LearningPathResource(CourseRepository courseRepository, AuthorizationChec
*/
@PutMapping("/courses/{courseId}/learning-paths/enable")
@EnforceAtLeastInstructor
- public ResponseEntity enableLearningPathsForCourse(@PathVariable Long courseId) {
+ public ResponseEntity enableLearningPathsForCourse(@PathVariable Long courseId) {
log.debug("REST request to enable learning paths for course with id: {}", courseId);
Course course = courseRepository.findWithEagerCompetenciesByIdElseThrow(courseId);
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
@@ -71,7 +72,7 @@ public ResponseEntity enableLearningPathsForCourse(@PathVariable Long co
learningPathService.generateLearningPaths(course);
course = courseRepository.save(course);
- return ResponseEntity.ok(course);
+ return ResponseEntity.ok().build();
}
/**
@@ -83,7 +84,7 @@ public ResponseEntity enableLearningPathsForCourse(@PathVariable Long co
*/
@GetMapping("/courses/{courseId}/learning-paths")
@EnforceAtLeastInstructor
- public ResponseEntity> getLearningPathsOnPage(@PathVariable Long courseId, PageableSearchDTO search) {
+ public ResponseEntity> getLearningPathsOnPage(@PathVariable Long courseId, PageableSearchDTO search) {
log.debug("REST request to get learning paths for course with id: {}", courseId);
Course course = courseRepository.findByIdElseThrow(courseId);
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
@@ -150,19 +151,19 @@ else if (!authorizationCheckService.isAtLeastInstructorInCourse(course, null) &&
*/
@GetMapping("/learning-path/{learningPathId}/recommendation")
@EnforceAtLeastStudent
- public ResponseEntity getRecommendation(@PathVariable Long learningPathId) {
+ public ResponseEntity getRecommendation(@PathVariable Long learningPathId) {
log.debug("REST request to get recommendation for learning path with id: {}", learningPathId);
LearningPath learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(learningPathId);
LearningObject recommendation = learningPathService.getRecommendation(learningPath);
if (recommendation == null) {
- return ResponseEntity.ok(new LearningPathRecommendation(-1, -1, LearningPathRecommendation.RecommendationType.EMPTY));
+ return ResponseEntity.ok(new LearningPathRecommendationDTO(-1, -1, LearningPathRecommendationDTO.RecommendationType.EMPTY));
}
else if (recommendation instanceof LectureUnit lectureUnit) {
return ResponseEntity
- .ok(new LearningPathRecommendation(recommendation.getId(), lectureUnit.getLecture().getId(), LearningPathRecommendation.RecommendationType.LECTURE_UNIT));
+ .ok(new LearningPathRecommendationDTO(recommendation.getId(), lectureUnit.getLecture().getId(), LearningPathRecommendationDTO.RecommendationType.LECTURE_UNIT));
}
else {
- return ResponseEntity.ok(new LearningPathRecommendation(recommendation.getId(), -1, LearningPathRecommendation.RecommendationType.EXERCISE));
+ return ResponseEntity.ok(new LearningPathRecommendationDTO(recommendation.getId(), -1, LearningPathRecommendationDTO.RecommendationType.EXERCISE));
}
}
}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathPageableSearchDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathPageableSearchDTO.java
new file mode 100644
index 000000000000..9f49626f34af
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathPageableSearchDTO.java
@@ -0,0 +1,11 @@
+package de.tum.in.www1.artemis.web.rest.dto.competency;
+
+import de.tum.in.www1.artemis.domain.competency.LearningPath;
+import de.tum.in.www1.artemis.web.rest.dto.user.UserNameAndLoginDTO;
+
+public record LearningPathPageableSearchDTO(long id, UserNameAndLoginDTO user, int progress) {
+
+ public LearningPathPageableSearchDTO(LearningPath learningPath) {
+ this(learningPath.getId(), new UserNameAndLoginDTO(learningPath.getUser()), learningPath.getProgress());
+ }
+}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathRecommendationDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathRecommendationDTO.java
new file mode 100644
index 000000000000..e86c41615620
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathRecommendationDTO.java
@@ -0,0 +1,8 @@
+package de.tum.in.www1.artemis.web.rest.dto.competency;
+
+public record LearningPathRecommendationDTO(long learningObjectId, long lectureId, RecommendationType type) {
+
+ public enum RecommendationType {
+ EMPTY, LECTURE_UNIT, EXERCISE
+ }
+}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
similarity index 95%
rename from src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java
rename to src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
index 5fec75ccb92c..46c16afc2559 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/NgxLearningPathDTO.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
@@ -1,4 +1,4 @@
-package de.tum.in.www1.artemis.web.rest.dto.learningpath;
+package de.tum.in.www1.artemis.web.rest.dto.competency;
import java.util.Set;
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/LearningPathRecommendation.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/LearningPathRecommendation.java
deleted file mode 100644
index a43920d0262c..000000000000
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/learningpath/LearningPathRecommendation.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package de.tum.in.www1.artemis.web.rest.dto.learningpath;
-
-public record LearningPathRecommendation(long learningObjectId, long lectureId, RecommendationType type) {
-
- public enum RecommendationType {
- EMPTY, LECTURE_UNIT, EXERCISE
- }
-}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/user/UserNameAndLoginDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/user/UserNameAndLoginDTO.java
new file mode 100644
index 000000000000..615f36683c81
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/user/UserNameAndLoginDTO.java
@@ -0,0 +1,10 @@
+package de.tum.in.www1.artemis.web.rest.dto.user;
+
+import de.tum.in.www1.artemis.domain.User;
+
+public record UserNameAndLoginDTO(String name, String login) {
+
+ public UserNameAndLoginDTO(User user) {
+ this(user.getName(), user.getLogin());
+ }
+}
diff --git a/src/main/webapp/app/core/user/user.model.ts b/src/main/webapp/app/core/user/user.model.ts
index 12af0f1ecd85..840aecf34a82 100644
--- a/src/main/webapp/app/core/user/user.model.ts
+++ b/src/main/webapp/app/core/user/user.model.ts
@@ -64,3 +64,11 @@ export class UserPublicInfoDTO {
public isTeachingAssistant?: boolean;
public isStudent?: boolean;
}
+
+/**
+ * A DTO representing a user which contains only the name and login
+ */
+export class UserNameAndLoginDTO {
+ public name?: string;
+ public login?: string;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
index 39d2baf34dbe..74412fda79df 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
@@ -1,6 +1,6 @@
import { Component, Input } from '@angular/core';
import { faCheckCircle, faCircle, faPlayCircle, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
-import { NgxLearningPathNode, NodeType } from 'app/entities/learning-path.model';
+import { NgxLearningPathNode, NodeType } from 'app/entities/competency/learning-path.model';
@Component({
selector: 'jhi-learning-path-graph-node',
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
index 860236e0481b..fe1bd75e673a 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
@@ -4,7 +4,7 @@ import { Layout } from '@swimlane/ngx-graph';
import * as shape from 'd3-shape';
import { Subject } from 'rxjs';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
-import { NgxLearningPathDTO } from 'app/entities/learning-path.model';
+import { NgxLearningPathDTO } from 'app/entities/competency/learning-path.model';
@Component({
selector: 'jhi-learning-path-graph',
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
index f4e59a47562c..0bb39b84f150 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
@@ -54,7 +54,7 @@ Learning Pa
{{ learningPath.id }}
-
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index 46526a35925b..8b838e2f5a0c 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -11,7 +11,7 @@ import { AlertService } from 'app/core/util/alert.service';
import { PageableSearch, SearchResult, SortingOrder } from 'app/shared/table/pageable-table';
import { LearningPathPagingService } from 'app/course/learning-paths/learning-path-paging.service';
import { SortService } from 'app/shared/service/sort.service';
-import { LearningPath } from 'app/entities/learning-path.model';
+import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
import { faSort } from '@fortawesome/free-solid-svg-icons';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
@@ -44,7 +44,7 @@ export class LearningPathManagementComponent implements OnInit, OnDestroy {
sortingOrder: SortingOrder.ASCENDING,
sortedColumn: TableColumn.ID,
};
- content: SearchResult;
+ content: SearchResult;
total = 0;
private search = new Subject();
@@ -92,7 +92,7 @@ export class LearningPathManagementComponent implements OnInit, OnDestroy {
* @param item The item itself
* @returns The ID of the item
*/
- trackId(index: number, item: LearningPath): number {
+ trackId(index: number, item: LearningPathPageableSearchDTO): number {
return item.id!;
}
@@ -158,8 +158,8 @@ export class LearningPathManagementComponent implements OnInit, OnDestroy {
}),
)
.subscribe({
- next: (res) => {
- this.course = res.body!;
+ next: () => {
+ this.course.learningPathsEnabled = true;
},
error: (res: HttpErrorResponse) => onError(this.alertService, res),
});
@@ -204,7 +204,7 @@ export class LearningPathManagementComponent implements OnInit, OnDestroy {
this.page = pageNumber;
}
}
- viewLearningPath(learningPath: LearningPath) {
+ viewLearningPath(learningPath: LearningPathPageableSearchDTO) {
const modalRef = this.modalService.open(LearningPathProgressModalComponent, {
size: 'xl',
backdrop: 'static',
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-paging.service.ts b/src/main/webapp/app/course/learning-paths/learning-path-paging.service.ts
index 9b91d9922c1e..8a86f9a56e5a 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-paging.service.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-paging.service.ts
@@ -4,9 +4,9 @@ import { PagingService } from 'app/exercises/shared/manage/paging.service';
import { PageableSearch, SearchResult } from 'app/shared/table/pageable-table';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
-import { LearningPath } from 'app/entities/learning-path.model';
+import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
-type EntityResponseType = SearchResult;
+type EntityResponseType = SearchResult;
@Injectable({ providedIn: 'root' })
export class LearningPathPagingService extends PagingService {
public resourceUrl = 'api';
diff --git a/src/main/webapp/app/course/learning-paths/learning-path.service.ts b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
index 75f4f7ed6bc4..d20be322b513 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path.service.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { Course } from 'app/entities/course.model';
-import { LearningPathRecommendation, NgxLearningPathDTO } from 'app/entities/learning-path.model';
+import { LearningPathRecommendationDTO, NgxLearningPathDTO } from 'app/entities/competency/learning-path.model';
import { map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
@@ -11,8 +11,8 @@ export class LearningPathService {
constructor(private httpClient: HttpClient) {}
- enableLearningPaths(courseId: number): Observable> {
- return this.httpClient.put(`${this.resourceURL}/courses/${courseId}/learning-paths/enable`, null, { observe: 'response' });
+ enableLearningPaths(courseId: number): Observable> {
+ return this.httpClient.put(`${this.resourceURL}/courses/${courseId}/learning-paths/enable`, null, { observe: 'response' });
}
getLearningPathId(courseId: number) {
@@ -37,6 +37,6 @@ export class LearningPathService {
}
getRecommendation(learningPathId: number) {
- return this.httpClient.get(`${this.resourceURL}/learning-path/${learningPathId}/recommendation`, { observe: 'response' });
+ return this.httpClient.get(`${this.resourceURL}/learning-path/${learningPathId}/recommendation`, { observe: 'response' });
}
}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
index 53793b40c7d5..ea0756b434d1 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
@@ -5,7 +5,7 @@ import { Exercise } from 'app/entities/exercise.model';
import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
import { Lecture } from 'app/entities/lecture.model';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
-import { RecommendationType } from 'app/entities/learning-path.model';
+import { RecommendationType } from 'app/entities/competency/learning-path.model';
import { LectureService } from 'app/lecture/lecture.service';
import { onError } from 'app/shared/util/global.utils';
import { HttpErrorResponse } from '@angular/common/http';
diff --git a/src/main/webapp/app/entities/learning-path.model.ts b/src/main/webapp/app/entities/competency/learning-path.model.ts
similarity index 85%
rename from src/main/webapp/app/entities/learning-path.model.ts
rename to src/main/webapp/app/entities/competency/learning-path.model.ts
index 1208eeca9eba..018af1edf2b7 100644
--- a/src/main/webapp/app/entities/learning-path.model.ts
+++ b/src/main/webapp/app/entities/competency/learning-path.model.ts
@@ -1,6 +1,6 @@
import { BaseEntity } from 'app/shared/model/base-entity';
import { Course } from 'app/entities/course.model';
-import { User } from 'app/core/user/user.model';
+import { User, UserNameAndLoginDTO } from 'app/core/user/user.model';
import { Competency } from 'app/entities/competency.model';
import { ClusterNode, Edge, Node } from '@swimlane/ngx-graph';
@@ -14,6 +14,12 @@ export class LearningPath implements BaseEntity {
constructor() {}
}
+export class LearningPathPageableSearchDTO {
+ public id?: number;
+ public user?: UserNameAndLoginDTO;
+ public progress?: number;
+}
+
export class NgxLearningPathDTO {
public nodes: NgxLearningPathNode[];
public edges: NgxLearningPathEdge[];
@@ -48,7 +54,7 @@ export enum NodeType {
LECTURE_UNIT = 'LECTURE_UNIT',
}
-export class LearningPathRecommendation {
+export class LearningPathRecommendationDTO {
public learningObjectId: number;
public lectureId?: number;
public type: RecommendationType;
diff --git a/src/main/webapp/app/entities/course.model.ts b/src/main/webapp/app/entities/course.model.ts
index 9b7b28729f8a..aae8b0627951 100644
--- a/src/main/webapp/app/entities/course.model.ts
+++ b/src/main/webapp/app/entities/course.model.ts
@@ -11,7 +11,7 @@ import { ProgrammingLanguage } from 'app/entities/programming-exercise.model';
import { OnlineCourseConfiguration } from 'app/entities/online-course-configuration.model';
import { TutorialGroup } from 'app/entities/tutorial-group/tutorial-group.model';
import { TutorialGroupsConfiguration } from 'app/entities/tutorial-group/tutorial-groups-configuration.model';
-import { LearningPath } from 'app/entities/learning-path.model';
+import { LearningPath } from 'app/entities/competency/learning-path.model';
export enum CourseInformationSharingConfiguration {
COMMUNICATION_AND_MESSAGING = 'COMMUNICATION_AND_MESSAGING',
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 49b39ff537c5..2b35c4ef27f7 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -36,7 +36,8 @@
import de.tum.in.www1.artemis.service.LectureUnitService;
import de.tum.in.www1.artemis.user.UserUtilService;
import de.tum.in.www1.artemis.util.PageableSearchUtilService;
-import de.tum.in.www1.artemis.web.rest.dto.learningpath.NgxLearningPathDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathPageableSearchDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbucketJiraTest {
@@ -157,7 +158,7 @@ private void testAllPreAuthorize() throws Exception {
}
private void enableLearningPathsRESTCall(Course course) throws Exception {
- request.putWithResponseBody("/api/courses/" + course.getId() + "/learning-paths/enable", course, Course.class, HttpStatus.OK);
+ request.put("/api/courses/" + course.getId() + "/learning-paths/enable", null, HttpStatus.OK);
}
private Competency createCompetencyRESTCall() throws Exception {
@@ -257,7 +258,8 @@ private void setupEnrollmentRequestMocks() throws JsonProcessingException, URISy
@WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
void testGetLearningPathsOnPageForCourseLearningPathsDisabled() throws Exception {
final var search = pageableSearchUtilService.configureSearch("");
- request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.BAD_REQUEST, LearningPath.class, pageableSearchUtilService.searchMapping(search));
+ request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.BAD_REQUEST, LearningPathPageableSearchDTO.class,
+ pageableSearchUtilService.searchMapping(search));
}
@Test
@@ -265,7 +267,7 @@ void testGetLearningPathsOnPageForCourseLearningPathsDisabled() throws Exception
void testGetLearningPathsOnPageForCourseEmpty() throws Exception {
course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
final var search = pageableSearchUtilService.configureSearch(STUDENT_OF_COURSE + "SuffixThatAllowsTheResultToBeEmpty");
- final var result = request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.OK, LearningPath.class,
+ final var result = request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.OK, LearningPathPageableSearchDTO.class,
pageableSearchUtilService.searchMapping(search));
assertThat(result.getResultsOnPage()).isNullOrEmpty();
}
@@ -275,7 +277,7 @@ void testGetLearningPathsOnPageForCourseEmpty() throws Exception {
void testGetLearningPathsOnPageForCourseExactlyStudent() throws Exception {
course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
final var search = pageableSearchUtilService.configureSearch(STUDENT_OF_COURSE);
- final var result = request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.OK, LearningPath.class,
+ final var result = request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.OK, LearningPathPageableSearchDTO.class,
pageableSearchUtilService.searchMapping(search));
assertThat(result.getResultsOnPage()).hasSize(1);
}
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 1ccb42a0df64..0fc4c8e02c88 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -28,7 +28,7 @@
import de.tum.in.www1.artemis.repository.CompetencyRepository;
import de.tum.in.www1.artemis.repository.LearningPathRepository;
import de.tum.in.www1.artemis.security.SecurityUtils;
-import de.tum.in.www1.artemis.web.rest.dto.learningpath.NgxLearningPathDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
class LearningPathServiceTest extends AbstractSpringIntegrationBambooBitbucketJiraTest {
diff --git a/src/test/javascript/spec/component/course/course-management.service.spec.ts b/src/test/javascript/spec/component/course/course-management.service.spec.ts
index 6aaf0fa42ad9..30d2ccb42d81 100644
--- a/src/test/javascript/spec/component/course/course-management.service.spec.ts
+++ b/src/test/javascript/spec/component/course/course-management.service.spec.ts
@@ -26,7 +26,7 @@ import { CourseScores } from 'app/course/course-scores/course-scores';
import { ScoresStorageService } from 'app/course/course-scores/scores-storage.service';
import { CourseStorageService } from 'app/course/manage/course-storage.service';
import { Result } from 'app/entities/result.model';
-import { LearningPath } from 'app/entities/learning-path.model';
+import { LearningPath } from 'app/entities/competency/learning-path.model';
describe('Course Management Service', () => {
let courseManagementService: CourseManagementService;
diff --git a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
index 844a9462e972..38490538c73c 100644
--- a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ArtemisTestModule } from '../../../test.module';
import { By } from '@angular/platform-browser';
import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph-node.component';
-import { NgxLearningPathNode, NodeType } from 'app/entities/learning-path.model';
+import { NgxLearningPathNode, NodeType } from 'app/entities/competency/learning-path.model';
describe('LearningPathGraphNodeComponent', () => {
let fixture: ComponentFixture;
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
index b2784bccf534..ea127624ba5f 100644
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
@@ -3,7 +3,7 @@ import { LearningPathManagementComponent, TableColumn } from 'app/course/learnin
import { LearningPathPagingService } from 'app/course/learning-paths/learning-path-paging.service';
import { SortService } from 'app/shared/service/sort.service';
import { PageableSearch, SearchResult, SortingOrder } from 'app/shared/table/pageable-table';
-import { LearningPath } from 'app/entities/learning-path.model';
+import { LearningPath } from 'app/entities/competency/learning-path.model';
import { ArtemisTestModule } from '../../../test.module';
import { MockComponent, MockDirective, MockProvider } from 'ng-mocks';
import { ButtonComponent } from 'app/shared/components/button.component';
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
index a63cd02a0a3f..bc952252b548 100644
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
@@ -7,7 +7,7 @@ import { ActivatedRoute, RouterModule } from '@angular/router';
import { HttpResponse } from '@angular/common/http';
import { LearningPathContainerComponent } from 'app/course/learning-paths/participate/learning-path-container.component';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
-import { LearningPathRecommendation, RecommendationType } from 'app/entities/learning-path.model';
+import { LearningPathRecommendationDTO, RecommendationType } from 'app/entities/competency/learning-path.model';
import { LectureService } from 'app/lecture/lecture.service';
import { Lecture } from 'app/entities/lecture.model';
import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
@@ -95,7 +95,7 @@ describe('LearningPathContainerComponent', () => {
});
it('should load lecture unit on recommendation', () => {
- const recommendation = new LearningPathRecommendation();
+ const recommendation = new LearningPathRecommendationDTO();
recommendation.learningObjectId = lectureUnit.id!;
recommendation.lectureId = lecture.id;
recommendation.type = RecommendationType.LECTURE_UNIT;
@@ -107,7 +107,7 @@ describe('LearningPathContainerComponent', () => {
});
it('should load exercise on recommendation', () => {
- const recommendation = new LearningPathRecommendation();
+ const recommendation = new LearningPathRecommendationDTO();
recommendation.learningObjectId = exercise.id!;
recommendation.type = RecommendationType.EXERCISE;
getRecommendationStub.mockReturnValue(of(new HttpResponse({ body: recommendation })));
From 6740f276d9c798cb79738c517572371b5176e921 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 27 Jul 2023 01:22:59 +0200
Subject: [PATCH 073/215] Fix failing bamboo test
---
.../web/rest/LearningPathResource.java | 4 +++
.../lecture/LearningPathIntegrationTest.java | 25 +++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 8f07ba2d9332..37db0dff1934 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -154,6 +154,10 @@ else if (!authorizationCheckService.isAtLeastInstructorInCourse(course, null) &&
public ResponseEntity getRecommendation(@PathVariable Long learningPathId) {
log.debug("REST request to get recommendation for learning path with id: {}", learningPathId);
LearningPath learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(learningPathId);
+ final var user = userRepository.getUser();
+ if (!user.getId().equals(learningPath.getUser().getId())) {
+ throw new AccessForbiddenException("You are not allowed to access another users learning path.");
+ }
LearningObject recommendation = learningPathService.getRecommendation(learningPath);
if (recommendation == null) {
return ResponseEntity.ok(new LearningPathRecommendationDTO(-1, -1, LearningPathRecommendationDTO.RecommendationType.EMPTY));
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 2b35c4ef27f7..011ce9c9bdc7 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -37,6 +37,7 @@
import de.tum.in.www1.artemis.user.UserUtilService;
import de.tum.in.www1.artemis.util.PageableSearchUtilService;
import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathPageableSearchDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathRecommendationDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbucketJiraTest {
@@ -403,4 +404,28 @@ void testGetNgxLearningPathAsInstructor() throws Exception {
final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.OK, NgxLearningPathDTO.class);
}
+
+ @Test
+ @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER")
+ void testGetRecommendationAsOtherUser() throws Exception {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ request.get("/api/learning-path/" + learningPath.getId() + "/recommendation", HttpStatus.FORBIDDEN, LearningPathRecommendationDTO.class);
+ }
+
+ /**
+ * This only tests if the end point successfully retrieves the recommendation. The correctness of the response is tested in LearningPathServiceTest.
+ *
+ * @throws Exception the request failed
+ * @see de.tum.in.www1.artemis.service.LearningPathServiceTest
+ */
+ @Test
+ @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
+ void testGetRecommendationAsOwner() throws Exception {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ request.get("/api/learning-path/" + learningPath.getId() + "/recommendation", HttpStatus.OK, LearningPathRecommendationDTO.class);
+ }
}
From eb43150376d292ec8db5a6f71a2bd6ee7566aba2 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 27 Jul 2023 10:31:39 +0200
Subject: [PATCH 074/215] Add client tests
---
.../learning-path-management.component.html | 1 +
.../learning-path-progress-nav.component.html | 6 +-
.../learning-path-graph.component.spec.ts | 11 ++++
...learning-path-management.component.spec.ts | 24 +++++++-
...ning-path-progress-modal.component.spec.ts | 3 +-
...arning-path-progress-nav.component.spec.ts | 57 +++++++++++++++++++
6 files changed, 97 insertions(+), 5 deletions(-)
create mode 100644 src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
index 0bb39b84f150..8a3efb44a500 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
@@ -10,6 +10,7 @@ Learning Pa
Disabled
diff --git a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
index aac3ebb8ad1d..32dcaacea7b2 100644
--- a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
@@ -48,4 +48,15 @@ describe('LearningPathGraphComponent', () => {
expect(zoomToFitStub).toHaveBeenCalledOnce();
expect(zoomToFitStub).toHaveBeenCalledWith(true);
});
+
+ it('should zoom to fit and center on resize', () => {
+ const zoomToFitStub = jest.spyOn(comp.zoomToFit$, 'next');
+ const centerStub = jest.spyOn(comp.center$, 'next');
+ fixture.detectChanges();
+ comp.onCenterView();
+ expect(zoomToFitStub).toHaveBeenCalledOnce();
+ expect(zoomToFitStub).toHaveBeenCalledWith(true);
+ expect(centerStub).toHaveBeenCalledOnce();
+ expect(centerStub).toHaveBeenCalledWith(true);
+ });
});
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
index ea127624ba5f..78f4b25e8e6b 100644
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
@@ -15,6 +15,8 @@ import { CourseManagementService } from 'app/course/manage/course-management.ser
import { Course } from 'app/entities/course.model';
import { ActivatedRoute } from '@angular/router';
import { HttpResponse } from '@angular/common/http';
+import { By } from '@angular/platform-browser';
+import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
describe('LearningPathManagementComponent', () => {
let fixture: ComponentFixture;
@@ -29,6 +31,8 @@ describe('LearningPathManagementComponent', () => {
let state: PageableSearch;
let learningPath: LearningPath;
let course: Course;
+ let learningPathService: LearningPathService;
+ let enableLearningPathsStub: jest.SpyInstance;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ArtemisTestModule, MockComponent(NgbPagination)],
@@ -57,6 +61,8 @@ describe('LearningPathManagementComponent', () => {
sortService = TestBed.inject(SortService);
searchForLearningPathsStub = jest.spyOn(pagingService, 'searchForLearningPaths');
sortByPropertyStub = jest.spyOn(sortService, 'sortByProperty');
+ learningPathService = TestBed.inject(LearningPathService);
+ enableLearningPathsStub = jest.spyOn(learningPathService, 'enableLearningPaths');
});
});
@@ -71,7 +77,6 @@ describe('LearningPathManagementComponent', () => {
course = new Course();
course.id = 1;
course.learningPathsEnabled = true;
- course.learningPaths = [learningPath];
findCourseStub.mockReturnValue(of(new HttpResponse({ body: course })));
searchResult = { numberOfPages: 3, resultsOnPage: [learningPath] };
state = {
@@ -103,6 +108,23 @@ describe('LearningPathManagementComponent', () => {
});
}));
+ it('should allow to enable learning paths if learning paths disabled', () => {
+ course.learningPathsEnabled = false;
+ findCourseStub.mockReturnValue(of(new HttpResponse({ body: course })));
+ fixture.detectChanges();
+ comp.ngOnInit();
+ expect(comp.course).toEqual(course);
+ expect(comp.course.learningPathsEnabled).toBeFalsy();
+ expect(comp.isLoading).toBeFalsy();
+ const button = fixture.debugElement.query(By.css('div'));
+ console.log(fixture.debugElement);
+ expect(button).not.toBeNull();
+
+ button.nativeElement.click();
+ expect(enableLearningPathsStub).toHaveBeenCalledOnce();
+ expect(enableLearningPathsStub).toHaveBeenCalledWith(course.id);
+ });
+
it('should set content to paging result on sort', fakeAsync(() => {
expect(comp.listSorting).toBeTrue();
setStateAndCallOnInit(() => {
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
index 69044986dbba..dbe4e026f787 100644
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
@@ -5,6 +5,7 @@ import { MockComponent } from 'ng-mocks';
import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
import { By } from '@angular/platform-browser';
+import { LearningPathProgressNavComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-nav.component';
describe('LearningPathProgressModalComponent', () => {
let fixture: ComponentFixture;
@@ -14,7 +15,7 @@ describe('LearningPathProgressModalComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [ArtemisTestModule, MockComponent(LearningPathGraphComponent)],
+ imports: [ArtemisTestModule, MockComponent(LearningPathGraphComponent), MockComponent(LearningPathProgressNavComponent)],
declarations: [LearningPathProgressModalComponent],
providers: [],
})
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
new file mode 100644
index 000000000000..84eca7eebcb3
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
@@ -0,0 +1,57 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../test.module';
+import { By } from '@angular/platform-browser';
+import { LearningPathProgressNavComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-nav.component';
+
+describe('LearningPathProgressNavComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LearningPathProgressNavComponent;
+ let onRefreshStub: jest.SpyInstance;
+ let onCenterViewStub: jest.SpyInstance;
+ let onCloseStub: jest.SpyInstance;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule],
+ declarations: [LearningPathProgressNavComponent],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathProgressNavComponent);
+ comp = fixture.componentInstance;
+ onRefreshStub = jest.spyOn(comp.onRefresh, 'emit');
+ onCenterViewStub = jest.spyOn(comp.onCenterView, 'emit');
+ onCloseStub = jest.spyOn(comp.onClose, 'emit');
+ fixture.detectChanges();
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should emit refresh on click', () => {
+ const button = fixture.debugElement.query(By.css('#refresh-button'));
+ expect(button).not.toBeNull();
+
+ button.nativeElement.click();
+ expect(onRefreshStub).toHaveBeenCalledOnce();
+ });
+
+ it('should emit center view on click', () => {
+ const button = fixture.debugElement.query(By.css('#center-button'));
+ expect(button).not.toBeNull();
+
+ button.nativeElement.click();
+ expect(onCenterViewStub).toHaveBeenCalledOnce();
+ });
+
+ it('should emit close on click', () => {
+ const button = fixture.debugElement.query(By.css('#close-button'));
+ expect(button).not.toBeNull();
+
+ button.nativeElement.click();
+ expect(onCloseStub).toHaveBeenCalledOnce();
+ });
+});
From 402bdae46f2ae90c0b3a592af0ab43fb90c40609 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 27 Jul 2023 10:44:03 +0200
Subject: [PATCH 075/215] Remove unused property
---
.../learning-path-graph-node.component.ts | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
index a8c7d08cedfd..9bd4b5584569 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
@@ -1,7 +1,6 @@
-import { Component, Input, ViewChild } from '@angular/core';
+import { Component, Input } from '@angular/core';
import { faCheckCircle, faCircle, faPlayCircle, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
import { NgxLearningPathNode, NodeType } from 'app/entities/competency/learning-path.model';
-import { NgbPopover } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'jhi-learning-path-graph-node',
@@ -18,7 +17,4 @@ export class LearningPathGraphNodeComponent {
faCircle = faCircle;
protected readonly NodeType = NodeType;
-
- @ViewChild('inspectPopover')
- private inspectPopover: NgbPopover;
}
From a888497b376962364cf9586c83b30d0562cfb570 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 27 Jul 2023 13:28:05 +0200
Subject: [PATCH 076/215] Add client tests
---
...learning-path-graph-node.component.spec.ts | 4 +-
.../competency-node-details.component.spec.ts | 69 +++++++++++++++++++
.../exercise-node-details.component.spec.ts | 52 ++++++++++++++
...ecture-unit-node-details.component.spec.ts | 57 +++++++++++++++
...ning-path-progress-modal.component.spec.ts | 1 +
.../sticky-popover.directive.spec.ts | 60 ++++++++++++++++
6 files changed, 242 insertions(+), 1 deletion(-)
create mode 100644 src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts
create mode 100644 src/test/javascript/spec/directive/sticky-popover.directive.spec.ts
diff --git a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
index 38490538c73c..8371428d66ed 100644
--- a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
@@ -1,8 +1,10 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ArtemisTestModule } from '../../../test.module';
+import { MockDirective } from 'ng-mocks';
import { By } from '@angular/platform-browser';
import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph-node.component';
import { NgxLearningPathNode, NodeType } from 'app/entities/competency/learning-path.model';
+import { StickyPopoverDirective } from 'app/shared/sticky-popover/sticky-popover.directive';
describe('LearningPathGraphNodeComponent', () => {
let fixture: ComponentFixture;
@@ -11,7 +13,7 @@ describe('LearningPathGraphNodeComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ArtemisTestModule],
- declarations: [LearningPathGraphNodeComponent],
+ declarations: [LearningPathGraphNodeComponent, MockDirective(StickyPopoverDirective)],
providers: [],
})
.compileComponents()
diff --git a/src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts
new file mode 100644
index 000000000000..848c9a2cefe9
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts
@@ -0,0 +1,69 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../../test.module';
+import { MockComponent, MockPipe } from 'ng-mocks';
+import { of } from 'rxjs';
+import { HttpResponse } from '@angular/common/http';
+import { CompetencyNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component';
+import { Competency, CompetencyProgress, CompetencyTaxonomy } from 'app/entities/competency.model';
+import { CompetencyService } from 'app/course/competencies/competency.service';
+import { CompetencyRingsComponent } from 'app/course/competencies/competency-rings/competency-rings.component';
+import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
+import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module';
+
+describe('CompetencyNodeDetailsComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: CompetencyNodeDetailsComponent;
+ let competencyService: CompetencyService;
+ let findByIdStub: jest.SpyInstance;
+ let competency: Competency;
+ let competencyProgress: CompetencyProgress;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, NgbTooltipMocksModule],
+ declarations: [CompetencyNodeDetailsComponent, MockComponent(CompetencyRingsComponent), MockPipe(ArtemisTranslatePipe)],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(CompetencyNodeDetailsComponent);
+ comp = fixture.componentInstance;
+ competency = new Competency();
+ competency.id = 2;
+ competency.title = 'Some arbitrary title';
+ competency.description = 'Some description';
+ competency.taxonomy = CompetencyTaxonomy.ANALYZE;
+ competency.masteryThreshold = 50;
+ competencyProgress = new CompetencyProgress();
+ competencyProgress.progress = 80;
+ competencyProgress.confidence = 70;
+ competency.userProgress = [competencyProgress];
+
+ competencyService = TestBed.inject(CompetencyService);
+ findByIdStub = jest.spyOn(competencyService, 'findById').mockReturnValue(of(new HttpResponse({ body: competency })));
+ comp.courseId = 1;
+ comp.competencyId = competency.id;
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should load competency on init', () => {
+ fixture.detectChanges();
+ expect(findByIdStub).toHaveBeenCalledOnce();
+ expect(findByIdStub).toHaveBeenCalledWith(competency.id, 1);
+ expect(comp.competency).toEqual(competency);
+ expect(comp.competencyProgress).toEqual(competencyProgress);
+ });
+
+ it('should default progress to zero if empty', () => {
+ competency.userProgress = undefined;
+ fixture.detectChanges();
+ expect(findByIdStub).toHaveBeenCalledOnce();
+ expect(findByIdStub).toHaveBeenCalledWith(competency.id, 1);
+ expect(comp.competency).toEqual(competency);
+ expect(comp.competencyProgress).toEqual({ confidence: 0, progress: 0 } as CompetencyProgress);
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
new file mode 100644
index 000000000000..c1bc0d981769
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
@@ -0,0 +1,52 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../../test.module';
+import { MockComponent, MockPipe } from 'ng-mocks';
+import { of } from 'rxjs';
+import { HttpResponse } from '@angular/common/http';
+import { CompetencyNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component';
+import { Competency, CompetencyProgress, CompetencyTaxonomy } from 'app/entities/competency.model';
+import { CompetencyService } from 'app/course/competencies/competency.service';
+import { CompetencyRingsComponent } from 'app/course/competencies/competency-rings/competency-rings.component';
+import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
+import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module';
+import { ExerciseNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component';
+import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
+import { Exercise } from 'app/entities/exercise.model';
+
+describe('ExerciseNodeDetailsComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: ExerciseNodeDetailsComponent;
+ let exerciseService: ExerciseService;
+ let findStub: jest.SpyInstance;
+ let exercise: Exercise;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, NgbTooltipMocksModule],
+ declarations: [ExerciseNodeDetailsComponent],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(ExerciseNodeDetailsComponent);
+ comp = fixture.componentInstance;
+ exercise = new Exercise();
+ exercise.id = 1;
+
+ exerciseService = TestBed.inject(ExerciseService);
+ findStub = jest.spyOn(exerciseService, 'find').mockReturnValue(of(new HttpResponse({ body: exercise })));
+ comp.exerciseId = exercise.id;
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should load exercise on init', () => {
+ fixture.detectChanges();
+ expect(findStub).toHaveBeenCalledOnce();
+ expect(findStub).toHaveBeenCalledWith(exercise.id);
+ expect(comp.exercise).toEqual(exercise);
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts
new file mode 100644
index 000000000000..0c7a44364c97
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts
@@ -0,0 +1,57 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../../test.module';
+import { MockPipe } from 'ng-mocks';
+import { of } from 'rxjs';
+import { HttpResponse } from '@angular/common/http';
+import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
+import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module';
+import { LectureUnitNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component';
+import { LectureService } from 'app/lecture/lecture.service';
+import { Lecture } from 'app/entities/lecture.model';
+import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
+import { TextUnit } from 'app/entities/lecture-unit/textUnit.model';
+
+describe('LectureUnitNodeDetailsComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LectureUnitNodeDetailsComponent;
+ let lectureService: LectureService;
+ let findWithDetailsStub: jest.SpyInstance;
+ let lecture: Lecture;
+ let lectureUnit: LectureUnit;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, NgbTooltipMocksModule],
+ declarations: [LectureUnitNodeDetailsComponent, MockPipe(ArtemisTranslatePipe)],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LectureUnitNodeDetailsComponent);
+ comp = fixture.componentInstance;
+ lecture = new Lecture();
+ lecture.id = 1;
+ lectureUnit = new TextUnit();
+ lectureUnit.id = 2;
+ lectureUnit.name = 'Some arbitrary name';
+ lecture.lectureUnits = [lectureUnit];
+
+ lectureService = TestBed.inject(LectureService);
+ findWithDetailsStub = jest.spyOn(lectureService, 'findWithDetails').mockReturnValue(of(new HttpResponse({ body: lecture })));
+ comp.lectureId = lecture.id;
+ comp.lectureUnitId = lectureUnit.id;
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should load lecture unit on init', () => {
+ fixture.detectChanges();
+ expect(findWithDetailsStub).toHaveBeenCalledOnce();
+ expect(findWithDetailsStub).toHaveBeenCalledWith(lecture.id);
+ expect(comp.lecture).toEqual(lecture);
+ expect(comp.lectureUnit).toEqual(lectureUnit);
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
index 69044986dbba..aeb8130b5d9a 100644
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
@@ -34,6 +34,7 @@ describe('LearningPathProgressModalComponent', () => {
it('should display learning path graph if id is present', () => {
comp.learningPathId = 1;
+ comp.courseId = 2;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.graph')).nativeElement).toBeTruthy();
});
diff --git a/src/test/javascript/spec/directive/sticky-popover.directive.spec.ts b/src/test/javascript/spec/directive/sticky-popover.directive.spec.ts
new file mode 100644
index 000000000000..13262c4fcf9a
--- /dev/null
+++ b/src/test/javascript/spec/directive/sticky-popover.directive.spec.ts
@@ -0,0 +1,60 @@
+import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { Component, DebugElement } from '@angular/core';
+import { ArtemisTestModule } from '../test.module';
+import { By } from '@angular/platform-browser';
+import { StickyPopoverDirective } from 'app/shared/sticky-popover/sticky-popover.directive';
+
+@Component({
+ template: '
some content ',
+})
+class StickyPopoverComponent {
+ pattern: string;
+}
+
+describe('StickyPopoverDirective', () => {
+ let fixture: ComponentFixture;
+ let debugDirective: DebugElement;
+ let directive: StickyPopoverDirective;
+ let openStub: jest.SpyInstance;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule],
+ declarations: [StickyPopoverDirective, StickyPopoverComponent],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(StickyPopoverComponent);
+ debugDirective = fixture.debugElement.query(By.directive(StickyPopoverDirective));
+ directive = debugDirective.injector.get(StickyPopoverDirective);
+ openStub = jest.spyOn(directive, 'open');
+ fixture.detectChanges();
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should open on hover', fakeAsync(() => {
+ fixture.whenStable();
+ const div = fixture.debugElement.query(By.css('div'));
+ expect(div).not.toBeNull();
+ div.nativeElement.dispatchEvent(new MouseEvent('mouseenter'));
+ tick(10);
+ expect(openStub).toHaveBeenCalledOnce();
+ expect(directive.isOpen()).toBeTruthy();
+ const span = fixture.debugElement.query(By.css('span'));
+ expect(span).not.toBeNull();
+ }));
+
+ it('should display content on hover', fakeAsync(() => {
+ fixture.whenStable();
+ const div = fixture.debugElement.query(By.css('div'));
+ expect(div).not.toBeNull();
+ div.nativeElement.dispatchEvent(new MouseEvent('mouseenter'));
+ tick(10);
+ const span = fixture.debugElement.query(By.css('span'));
+ expect(span).not.toBeNull();
+ }));
+});
From 0e8d62b2b66f2cfa276ae32bd5995607baea0b44 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 27 Jul 2023 13:40:07 +0200
Subject: [PATCH 077/215] Add exercise node details
---
.../exercise-node-details.component.html | 14 ++++++++++++++
.../exercise-node-details.component.ts | 5 ++++-
.../exercise-node-details.component.spec.ts | 12 +++++-------
3 files changed, 23 insertions(+), 8 deletions(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html
index e69de29bb2d1..d364b09a85b2 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html
@@ -0,0 +1,14 @@
+
+
+
+
+ {{ exercise.title }}
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
index 5f081d966294..8f265a38fadf 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
@@ -2,7 +2,7 @@ import { Component, Input, OnInit } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { onError } from 'app/shared/util/global.utils';
import { AlertService } from 'app/core/util/alert.service';
-import { Exercise } from 'app/entities/exercise.model';
+import { Exercise, getIcon, getIconTooltip } from 'app/entities/exercise.model';
import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
@Component({
@@ -32,4 +32,7 @@ export class ExerciseNodeDetailsComponent implements OnInit {
error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
});
}
+
+ protected readonly getIcon = getIcon;
+ protected readonly getIconTooltip = getIconTooltip;
}
diff --git a/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
index c1bc0d981769..57141684de8a 100644
--- a/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
@@ -1,17 +1,14 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ArtemisTestModule } from '../../../../test.module';
-import { MockComponent, MockPipe } from 'ng-mocks';
+import { MockPipe } from 'ng-mocks';
import { of } from 'rxjs';
import { HttpResponse } from '@angular/common/http';
-import { CompetencyNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component';
-import { Competency, CompetencyProgress, CompetencyTaxonomy } from 'app/entities/competency.model';
-import { CompetencyService } from 'app/course/competencies/competency.service';
-import { CompetencyRingsComponent } from 'app/course/competencies/competency-rings/competency-rings.component';
import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module';
import { ExerciseNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component';
import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
import { Exercise } from 'app/entities/exercise.model';
+import { TextExercise } from 'app/entities/text-exercise.model';
describe('ExerciseNodeDetailsComponent', () => {
let fixture: ComponentFixture;
@@ -23,15 +20,16 @@ describe('ExerciseNodeDetailsComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ArtemisTestModule, NgbTooltipMocksModule],
- declarations: [ExerciseNodeDetailsComponent],
+ declarations: [ExerciseNodeDetailsComponent, MockPipe(ArtemisTranslatePipe)],
providers: [],
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(ExerciseNodeDetailsComponent);
comp = fixture.componentInstance;
- exercise = new Exercise();
+ exercise = new TextExercise();
exercise.id = 1;
+ exercise.title = 'Some arbitrary title';
exerciseService = TestBed.inject(ExerciseService);
findStub = jest.spyOn(exerciseService, 'find').mockReturnValue(of(new HttpResponse({ body: exercise })));
From 575fcba11d4256c0e89882ee0cceceb962cccd53 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 27 Jul 2023 14:09:59 +0200
Subject: [PATCH 078/215] Fix enableLearningPaths test
---
...learning-path-management.component.spec.ts | 24 +++++++++----------
1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
index 78f4b25e8e6b..e618d4f3e3f8 100644
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
@@ -15,8 +15,8 @@ import { CourseManagementService } from 'app/course/manage/course-management.ser
import { Course } from 'app/entities/course.model';
import { ActivatedRoute } from '@angular/router';
import { HttpResponse } from '@angular/common/http';
-import { By } from '@angular/platform-browser';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
+import { expectElementToBeDisabled } from '../../../helpers/utils/general.utils';
describe('LearningPathManagementComponent', () => {
let fixture: ComponentFixture;
@@ -88,6 +88,7 @@ describe('LearningPathManagementComponent', () => {
...searchResult,
};
searchForLearningPathsStub.mockReturnValue(of(searchResult));
+ enableLearningPathsStub.mockReturnValue(of(new HttpResponse()));
});
const setStateAndCallOnInit = (middleExpectation: () => void) => {
@@ -108,22 +109,19 @@ describe('LearningPathManagementComponent', () => {
});
}));
- it('should allow to enable learning paths if learning paths disabled', () => {
- course.learningPathsEnabled = false;
- findCourseStub.mockReturnValue(of(new HttpResponse({ body: course })));
+ it('should enable learning paths and load data', fakeAsync(() => {
+ const disabledCourse = Object.assign({}, course);
+ disabledCourse.learningPathsEnabled = false;
+ findCourseStub.mockReturnValueOnce(of(new HttpResponse({ body: disabledCourse }))).mockReturnValueOnce(course);
+ const loadDataStub = jest.spyOn(comp, 'loadData');
fixture.detectChanges();
comp.ngOnInit();
- expect(comp.course).toEqual(course);
- expect(comp.course.learningPathsEnabled).toBeFalsy();
- expect(comp.isLoading).toBeFalsy();
- const button = fixture.debugElement.query(By.css('div'));
- console.log(fixture.debugElement);
- expect(button).not.toBeNull();
-
- button.nativeElement.click();
+ comp.enableLearningPaths();
expect(enableLearningPathsStub).toHaveBeenCalledOnce();
expect(enableLearningPathsStub).toHaveBeenCalledWith(course.id);
- });
+ expect(loadDataStub).toHaveBeenCalledTimes(2);
+ expect(comp.course.learningPathsEnabled).toBeTruthy();
+ }));
it('should set content to paging result on sort', fakeAsync(() => {
expect(comp.listSorting).toBeTrue();
From 3ecd4dc66789a773e83cecc03b6f940e1d83a7da Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 27 Jul 2023 14:33:34 +0200
Subject: [PATCH 079/215] Fix build fail
---
.../management/learning-path-management.component.spec.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
index e618d4f3e3f8..0300847c271e 100644
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
@@ -113,13 +113,12 @@ describe('LearningPathManagementComponent', () => {
const disabledCourse = Object.assign({}, course);
disabledCourse.learningPathsEnabled = false;
findCourseStub.mockReturnValueOnce(of(new HttpResponse({ body: disabledCourse }))).mockReturnValueOnce(course);
- const loadDataStub = jest.spyOn(comp, 'loadData');
fixture.detectChanges();
comp.ngOnInit();
comp.enableLearningPaths();
expect(enableLearningPathsStub).toHaveBeenCalledOnce();
expect(enableLearningPathsStub).toHaveBeenCalledWith(course.id);
- expect(loadDataStub).toHaveBeenCalledTimes(2);
+ expect(findCourseStub).toHaveBeenCalledTimes(3);
expect(comp.course.learningPathsEnabled).toBeTruthy();
}));
From 167d50cc255baf4a163eee40fab3018085f058cc Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 28 Jul 2023 12:12:48 +0200
Subject: [PATCH 080/215] Remove unused Imports
---
.../learning-path-progress-modal.component.ts | 2 +-
.../management/learning-path-management.component.spec.ts | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
index 80fb732c23d3..c898cfde1f28 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnInit, ViewChild } from '@angular/core';
+import { Component, Input, ViewChild } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
@Component({
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
index 0300847c271e..be735930ae59 100644
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
@@ -16,7 +16,6 @@ import { Course } from 'app/entities/course.model';
import { ActivatedRoute } from '@angular/router';
import { HttpResponse } from '@angular/common/http';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
-import { expectElementToBeDisabled } from '../../../helpers/utils/general.utils';
describe('LearningPathManagementComponent', () => {
let fixture: ComponentFixture;
From 85a4d85fb03e23d815a5ab200a97a7240c01a028 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 28 Jul 2023 12:17:48 +0200
Subject: [PATCH 081/215] Remove unused import
---
.../webapp/app/course/learning-paths/learning-path.service.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path.service.ts b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
index d20be322b513..5f2f66d54d8d 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path.service.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
@@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpResponse } from '@angular/common/http';
-import { Course } from 'app/entities/course.model';
import { LearningPathRecommendationDTO, NgxLearningPathDTO } from 'app/entities/competency/learning-path.model';
import { map } from 'rxjs/operators';
From 6beb5bc9f500ac20e448d57aba47e6b23c63f169 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 28 Jul 2023 12:41:43 +0200
Subject: [PATCH 082/215] Fix popover width
---
.../learning-path-graph-node.component.html | 6 +++---
.../learning-path-graph/learning-path-graph.component.scss | 5 +++++
.../node-details/competency-node-details.component.html | 4 ++--
3 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
index ba069046e5d7..99e15a52615d 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
@@ -27,10 +27,10 @@
-
-
+
+
-
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
index 8b931cbdf212..443b23c9e23f 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
@@ -35,3 +35,8 @@ jhi-learning-path-graph-node:hover {
.completed {
color: var(--bs-success);
}
+
+.node-details {
+ display: block;
+ max-width: 90vh;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
index b84a4cc2cfcf..78bc30b342fa 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
@@ -1,5 +1,5 @@
-
+
{{ competency.description }}
-
+
From b8c4e771b04a64fac2cf377a3806be97734b5e2d Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 28 Jul 2023 13:03:03 +0200
Subject: [PATCH 083/215] fix graph-container overflow
---
.../learning-path-graph/learning-path-graph.component.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
index 443b23c9e23f..9ee5c9bb5f03 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
@@ -2,6 +2,7 @@
display: block;
width: 100%;
height: 100%;
+ overflow: hidden;
.ngx-graph {
width: auto;
From 19fc5251941d51a58ef631017c419f829d232375 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 28 Jul 2023 14:10:01 +0200
Subject: [PATCH 084/215] fix node-details and navigations via graph
---
.../artemis/service/LearningPathService.java | 2 +-
.../dto/competency/NgxLearningPathDTO.java | 6 +++++-
.../learning-path-graph-node.component.html | 7 ++++++-
.../lecture-unit-node-details.component.html | 4 ++--
.../lecture-unit-node-details.component.ts | 5 ++++-
.../learning-path-container.component.html | 7 ++++++-
.../learning-path-container.component.ts | 20 +++++++++++++++++-
...learning-path-graph-sidebar.component.html | 8 ++++++-
.../learning-path-graph-sidebar.component.ts | 6 +++++-
.../competency/learning-path.model.ts | 1 +
.../lecture-unit/lectureUnit.model.ts | 21 +++++++++----------
.../service/LearningPathServiceTest.java | 9 ++++----
12 files changed, 71 insertions(+), 25 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index 9a2fc5ca4922..8df97477a170 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -213,7 +213,7 @@ private void generateNgxRepresentationForCompetency(LearningPath learningPath, C
// generate nodes and edges for lecture units
competency.getLectureUnits().forEach(lectureUnit -> {
currentCluster.add(new NgxLearningPathDTO.Node(getLectureUnitNodeId(competency.getId(), lectureUnit.getId()), NgxLearningPathDTO.NodeType.LECTURE_UNIT,
- lectureUnit.getId(), lectureUnit.isCompletedFor(learningPath.getUser()), lectureUnit.getName()));
+ lectureUnit.getId(), lectureUnit.getLecture().getId(), lectureUnit.isCompletedFor(learningPath.getUser()), lectureUnit.getName()));
edges.add(new NgxLearningPathDTO.Edge(getLectureUnitInEdgeId(competency.getId(), lectureUnit.getId()), startNodeId,
getLectureUnitNodeId(competency.getId(), lectureUnit.getId())));
edges.add(new NgxLearningPathDTO.Edge(getLectureUnitOutEdgeId(competency.getId(), lectureUnit.getId()), getLectureUnitNodeId(competency.getId(), lectureUnit.getId()),
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
index 46c16afc2559..fa676c14a702 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
@@ -10,7 +10,11 @@
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record NgxLearningPathDTO(Set
nodes, Set edges, Set clusters) {
- public record Node(String id, NodeType type, long linkedResource, boolean completed, String label) {
+ public record Node(String id, NodeType type, long linkedResource, long linkedResourceParent, boolean completed, String label) {
+
+ public Node(String id, NodeType type, long linkedResource, boolean completed, String label) {
+ this(id, type, linkedResource, -1, completed, label);
+ }
public Node(String id, NodeType type, long linkedResource, String label) {
this(id, type, linkedResource, false, label);
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
index 99e15a52615d..5d537b33656e 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
@@ -28,7 +28,12 @@
-
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
index fe46e533a422..4f6553e2ef8e 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
@@ -3,9 +3,9 @@
{{ lectureUnit.name }}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
index 7c8a5245b9d2..97aa66e1c311 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
@@ -4,7 +4,7 @@ import { onError } from 'app/shared/util/global.utils';
import { AlertService } from 'app/core/util/alert.service';
import { LectureService } from 'app/lecture/lecture.service';
import { Lecture } from 'app/entities/lecture.model';
-import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
+import { LectureUnit, getIcon, getIconTooltip } from 'app/entities/lecture-unit/lectureUnit.model';
@Component({
selector: 'jhi-lecture-unit-node-details',
@@ -39,4 +39,7 @@ export class LectureUnitNodeDetailsComponent implements OnInit {
error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
});
}
+
+ protected readonly getIcon = getIcon;
+ protected readonly getIconTooltip = getIconTooltip;
}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
index 8c24c96375eb..e8d240d59786 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
@@ -1,6 +1,11 @@
-
+
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
index ea0756b434d1..999f1c52d27a 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
@@ -5,7 +5,7 @@ import { Exercise } from 'app/entities/exercise.model';
import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
import { Lecture } from 'app/entities/lecture.model';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
-import { RecommendationType } from 'app/entities/competency/learning-path.model';
+import { NgxLearningPathNode, NodeType, RecommendationType } from 'app/entities/competency/learning-path.model';
import { LectureService } from 'app/lecture/lecture.service';
import { onError } from 'app/shared/util/global.utils';
import { HttpErrorResponse } from '@angular/common/http';
@@ -143,4 +143,22 @@ export class LearningPathContainerComponent implements OnInit {
instance.exerciseId = this.learningObjectId;
}
}
+
+ onNodeClicked(node: NgxLearningPathNode) {
+ if (node.type === NodeType.LECTURE_UNIT || node.type === NodeType.EXERCISE) {
+ if (this.lectureUnit?.id) {
+ this.history.push([this.lectureUnit.id, this.lectureId!]);
+ } else if (this.exercise?.id) {
+ this.history.push([this.exercise.id, -1]);
+ }
+ this.undefineAll();
+ this.learningObjectId = node.linkedResource!;
+ this.lectureId = node.linkedResourceParent;
+ if (node.type === NodeType.LECTURE_UNIT) {
+ this.loadLectureUnit();
+ } else if (node.type === NodeType.EXERCISE) {
+ this.loadExercise();
+ }
+ }
+ }
}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html
index 08a8a91621f6..0c64d339f083 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html
@@ -13,7 +13,13 @@
-
+
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
index cf6ff5060559..68cc20c26bcf 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
@@ -1,7 +1,8 @@
-import { AfterViewInit, Component, Input, ViewChild } from '@angular/core';
+import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import interact from 'interactjs';
import { faChevronLeft, faChevronRight, faGripLinesVertical, faNetworkWired } from '@fortawesome/free-solid-svg-icons';
import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
+import { NgxLearningPathNode } from 'app/entities/competency/learning-path.model';
@Component({
selector: 'jhi-learning-path-graph-sidebar',
@@ -9,6 +10,7 @@ import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-p
templateUrl: './learning-path-graph-sidebar.component.html',
})
export class LearningPathGraphSidebarComponent implements AfterViewInit {
+ @Input() courseId: number;
@Input() learningPathId: number;
collapsed: boolean;
// Icons
@@ -20,6 +22,8 @@ export class LearningPathGraphSidebarComponent implements AfterViewInit {
@ViewChild(`learningPathGraphComponent`, { static: false })
learningPathGraphComponent: LearningPathGraphComponent;
+ @Output() nodeClicked: EventEmitter
= new EventEmitter();
+
ngAfterViewInit(): void {
// allows the conversation sidebar to be resized towards the right-hand side
interact('.expanded-graph')
diff --git a/src/main/webapp/app/entities/competency/learning-path.model.ts b/src/main/webapp/app/entities/competency/learning-path.model.ts
index 018af1edf2b7..7cee6e4ad2e4 100644
--- a/src/main/webapp/app/entities/competency/learning-path.model.ts
+++ b/src/main/webapp/app/entities/competency/learning-path.model.ts
@@ -30,6 +30,7 @@ export class NgxLearningPathNode implements Node {
public id: string;
public type?: NodeType;
public linkedResource?: number;
+ public linkedResourceParent?: number;
public completed?: boolean;
public label?: string;
}
diff --git a/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts b/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
index e47782e08fee..f525b0753661 100644
--- a/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
+++ b/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
@@ -45,19 +45,18 @@ export abstract class LectureUnit implements BaseEntity {
protected constructor(type: LectureUnitType) {
this.type = type;
}
+}
- get getIcon(): IconProp {
- if (!this.type) {
- return faQuestion as IconProp;
- }
- return lectureUnitIcons[this.type] as IconProp;
+export function getIcon(lectureUnitType: LectureUnitType): IconProp {
+ if (!lectureUnitType) {
+ return faQuestion as IconProp;
}
+ return lectureUnitIcons[lectureUnitType] as IconProp;
+}
- getIconTooltip(): string {
- if (!this.type) {
- return '';
- }
-
- return lectureUnitTooltips[this.type];
+export function getIconTooltip(lectureUnitType: LectureUnitType) {
+ if (!lectureUnitType) {
+ return '';
}
+ return lectureUnitTooltips[lectureUnitType];
}
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 0fc4c8e02c88..dabe91025d79 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -2,9 +2,8 @@
import static org.assertj.core.api.Assertions.assertThat;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
+import java.time.ZonedDateTime;
+import java.util.*;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
@@ -107,7 +106,9 @@ void testEmptyCompetency() {
@Test
void testCompetencyWithLectureUnitAndExercise() {
var competency = competencyUtilService.createCompetency(course);
+ var lecture = lectureUtilService.createLecture(course, ZonedDateTime.now());
final var lectureUnit = lectureUtilService.createTextUnit();
+ lectureUtilService.addLectureUnitsToLecture(lecture, List.of(lectureUnit));
competencyUtilService.linkLectureUnitToCompetency(competency, lectureUnit);
final var exercise = programmingExerciseUtilService.addProgrammingExerciseToCourse(course, false, false, ProgrammingLanguage.JAVA, "Some Title", "someshortname");
competencyUtilService.linkExerciseToCompetency(competency, exercise);
@@ -263,7 +264,7 @@ private static Set getExpectedNodesOfEmptyCompetency(Co
private static NgxLearningPathDTO.Node getNodeForLectureUnit(Competency competency, LectureUnit lectureUnit) {
return new NgxLearningPathDTO.Node(LearningPathService.getLectureUnitNodeId(competency.getId(), lectureUnit.getId()), NgxLearningPathDTO.NodeType.LECTURE_UNIT,
- lectureUnit.getId(), lectureUnit.getName());
+ lectureUnit.getId(), lectureUnit.getLecture().getId(), false, lectureUnit.getName());
}
private static NgxLearningPathDTO.Node getNodeForExercise(Competency competency, Exercise exercise) {
From b958d7cd5af3e5cd43c7ec36095c00bb926bb437 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 28 Jul 2023 14:56:38 +0200
Subject: [PATCH 085/215] add tests for node click event in graph container
---
.../learning-path-container.component.spec.ts | 37 ++++++++++++++++++-
1 file changed, 36 insertions(+), 1 deletion(-)
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
index bc952252b548..e9d2e74ca2ce 100644
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
@@ -7,7 +7,7 @@ import { ActivatedRoute, RouterModule } from '@angular/router';
import { HttpResponse } from '@angular/common/http';
import { LearningPathContainerComponent } from 'app/course/learning-paths/participate/learning-path-container.component';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
-import { LearningPathRecommendationDTO, RecommendationType } from 'app/entities/competency/learning-path.model';
+import { LearningPathRecommendationDTO, NgxLearningPathNode, NodeType, RecommendationType } from 'app/entities/competency/learning-path.model';
import { LectureService } from 'app/lecture/lecture.service';
import { Lecture } from 'app/entities/lecture.model';
import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
@@ -180,4 +180,39 @@ describe('LearningPathContainerComponent', () => {
expect(instance.courseId).toBe(1);
expect(instance.exerciseId).toEqual(exercise.id);
});
+
+ it('should handle lecture unit node click', () => {
+ const node = { id: 1, type: NodeType.LECTURE_UNIT, linkedResource: 2, linkedResourceParent: 3 } as NgxLearningPathNode;
+ comp.onNodeClicked(node);
+ expect(comp.learningObjectId).toBe(node.linkedResource);
+ expect(comp.lectureId).toBe(node.linkedResourceParent);
+ expect(findWithDetailsStub).toHaveBeenCalledWith(node.linkedResourceParent);
+ });
+
+ it('should handle exercise node click', () => {
+ const node = { id: 1, type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
+ comp.onNodeClicked(node);
+ expect(comp.learningObjectId).toBe(node.linkedResource);
+ expect(getExerciseDetailsStub).toHaveBeenCalledWith(node.linkedResource);
+ });
+
+ it('should handle store current lecture unit in history on node click', () => {
+ comp.learningObjectId = lectureUnit.id!;
+ comp.lectureUnit = lectureUnit;
+ comp.lectureId = lecture.id;
+ comp.lecture = lecture;
+ fixture.detectChanges();
+ const node = { id: 1, type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
+ comp.onNodeClicked(node);
+ expect(comp.history).toEqual([[lectureUnit.id!, lecture.id!]]);
+ });
+
+ it('should handle store current exercise in history on node click', () => {
+ comp.learningObjectId = exercise.id!;
+ comp.exercise = exercise;
+ fixture.detectChanges();
+ const node = { id: 1, type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
+ comp.onNodeClicked(node);
+ expect(comp.history).toEqual([[exercise.id!, -1]]);
+ });
});
From d7fb00743f792614cb8933be677a8b4d0e6a0238 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 28 Jul 2023 15:03:34 +0200
Subject: [PATCH 086/215] fix compiler error
---
.../node-details/exercise-node-details.component.spec.ts | 2 +-
.../participate/learning-path-container.component.spec.ts | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
index 57141684de8a..839aeccf50de 100644
--- a/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
@@ -27,7 +27,7 @@ describe('ExerciseNodeDetailsComponent', () => {
.then(() => {
fixture = TestBed.createComponent(ExerciseNodeDetailsComponent);
comp = fixture.componentInstance;
- exercise = new TextExercise();
+ exercise = new TextExercise(undefined, undefined);
exercise.id = 1;
exercise.title = 'Some arbitrary title';
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
index e9d2e74ca2ce..d0ec5a7fd897 100644
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
@@ -182,7 +182,7 @@ describe('LearningPathContainerComponent', () => {
});
it('should handle lecture unit node click', () => {
- const node = { id: 1, type: NodeType.LECTURE_UNIT, linkedResource: 2, linkedResourceParent: 3 } as NgxLearningPathNode;
+ const node = { id: 'some-id', type: NodeType.LECTURE_UNIT, linkedResource: 2, linkedResourceParent: 3 } as NgxLearningPathNode;
comp.onNodeClicked(node);
expect(comp.learningObjectId).toBe(node.linkedResource);
expect(comp.lectureId).toBe(node.linkedResourceParent);
@@ -190,7 +190,7 @@ describe('LearningPathContainerComponent', () => {
});
it('should handle exercise node click', () => {
- const node = { id: 1, type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
+ const node = { id: 'some-id', type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
comp.onNodeClicked(node);
expect(comp.learningObjectId).toBe(node.linkedResource);
expect(getExerciseDetailsStub).toHaveBeenCalledWith(node.linkedResource);
@@ -202,7 +202,7 @@ describe('LearningPathContainerComponent', () => {
comp.lectureId = lecture.id;
comp.lecture = lecture;
fixture.detectChanges();
- const node = { id: 1, type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
+ const node = { id: 'some-id', type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
comp.onNodeClicked(node);
expect(comp.history).toEqual([[lectureUnit.id!, lecture.id!]]);
});
@@ -211,7 +211,7 @@ describe('LearningPathContainerComponent', () => {
comp.learningObjectId = exercise.id!;
comp.exercise = exercise;
fixture.detectChanges();
- const node = { id: 1, type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
+ const node = { id: 'some-id', type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
comp.onNodeClicked(node);
expect(comp.history).toEqual([[exercise.id!, -1]]);
});
From bc131e3043d1621c86db831e8bb3245896c40cea Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 28 Jul 2023 15:29:05 +0200
Subject: [PATCH 087/215] remove native types from DTO
---
.../web/rest/dto/competency/NgxLearningPathDTO.java | 13 ++++++++-----
.../artemis/service/LearningPathServiceTest.java | 8 ++++----
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
index 46c16afc2559..749da948dba3 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
@@ -10,28 +10,31 @@
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record NgxLearningPathDTO(Set nodes, Set edges, Set clusters) {
- public record Node(String id, NodeType type, long linkedResource, boolean completed, String label) {
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public record Node(String id, NodeType type, Long linkedResource, boolean completed, String label) {
- public Node(String id, NodeType type, long linkedResource, String label) {
+ public Node(String id, NodeType type, Long linkedResource, String label) {
this(id, type, linkedResource, false, label);
}
public Node(String id, NodeType type, String label) {
- this(id, type, -1, label);
+ this(id, type, null, label);
}
- public Node(String id, NodeType type, long linkedResource) {
+ public Node(String id, NodeType type, Long linkedResource) {
this(id, type, linkedResource, "");
}
public Node(String id, NodeType type) {
- this(id, type, -1);
+ this(id, type, null, "");
}
}
+ @JsonInclude(JsonInclude.Include.NON_NULL)
public record Edge(String id, String source, String target) {
}
+ @JsonInclude(JsonInclude.Include.NON_NULL)
public record Cluster(String id, String label, Set childNodeIds) {
}
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 0fc4c8e02c88..1588ae37b389 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -205,8 +205,8 @@ void testSingleExtends() {
@Test
void testSingleMatches() {
competencyUtilService.addRelation(competency1, CompetencyRelation.RelationType.MATCHES, competency2);
- expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterStartNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_START, -1, ""));
- expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterEndNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_END, -1, ""));
+ expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterStartNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_START, null, ""));
+ expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterEndNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_END, null, ""));
expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency1.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
LearningPathService.getCompetencyStartNodeId(competency1.getId())));
expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency1.getId()), LearningPathService.getCompetencyEndNodeId(competency1.getId()),
@@ -226,8 +226,8 @@ void testMatchesTransitive() {
competencyUtilService.addRelation(competency1, CompetencyRelation.RelationType.MATCHES, competency2);
competencyUtilService.addRelation(competency2, CompetencyRelation.RelationType.MATCHES, competency3);
- expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterStartNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_START, -1, ""));
- expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterEndNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_END, -1, ""));
+ expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterStartNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_START, null, ""));
+ expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterEndNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_END, null, ""));
expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency1.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
LearningPathService.getCompetencyStartNodeId(competency1.getId())));
expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency1.getId()), LearningPathService.getCompetencyEndNodeId(competency1.getId()),
From 8f9781ec05e7341d81f0419bda18ee2f382e8c1c Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 28 Jul 2023 16:42:47 +0200
Subject: [PATCH 088/215] minimize transfered data
---
.../artemis/repository/CourseRepository.java | 14 ------
.../www1/artemis/web/rest/CourseResource.java | 30 ++++++------
.../learning-path-management.component.html | 4 +-
.../learning-path-management.component.ts | 49 +++++++++----------
.../manage/course-management.service.ts | 18 +++----
.../artemis/course/CourseTestService.java | 34 ++++++-------
...rseBitbucketBambooJiraIntegrationTest.java | 4 +-
.../CourseGitlabJenkinsIntegrationTest.java | 4 +-
.../course/course-management.service.spec.ts | 13 -----
...learning-path-management.component.spec.ts | 30 +++++-------
10 files changed, 81 insertions(+), 119 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
index 597f44c0f646..14d3251ccdc3 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
@@ -131,15 +131,6 @@ SELECT CASE WHEN (count(c) > 0) THEN true ELSE false END
@EntityGraph(type = LOAD, attributePaths = { "competencies", "prerequisites" })
Optional findWithEagerCompetenciesById(long courseId);
- @Query("""
- SELECT c
- FROM Course c
- LEFT JOIN FETCH c.learningPaths lp
- LEFT JOIN FETCH lp.user
- WHERE c.id = :courseId
- """)
- Optional findWithEagerLearningPathsById(@Param("courseId") long courseId);
-
@EntityGraph(type = LOAD, attributePaths = { "competencies", "learningPaths", "learningPaths.competencies" })
Optional findWithEagerLearningPathsAndCompetenciesById(long courseId);
@@ -407,11 +398,6 @@ default Course findWithEagerCompetenciesByIdElseThrow(long courseId) {
return findWithEagerCompetenciesById(courseId).orElseThrow(() -> new EntityNotFoundException("Course", courseId));
}
- @NotNull
- default Course findWithEagerLearningPathsByIdElseThrow(long courseId) {
- return findWithEagerLearningPathsById(courseId).orElseThrow(() -> new EntityNotFoundException("Course", courseId));
- }
-
@NotNull
default Course findWithEagerLearningPathsAndCompetenciesByIdElseThrow(long courseId) {
return findWithEagerLearningPathsAndCompetenciesById(courseId).orElseThrow(() -> new EntityNotFoundException("Course", courseId));
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
index 9d7e0048246d..81ced00f114a 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
@@ -629,21 +629,6 @@ public ResponseEntity getCourseWithOrganizations(@PathVariable Long cour
return ResponseEntity.ok(course);
}
- /**
- * GET /courses/:courseId/with-learning-paths Get a course by id with eagerly loaded learning paths
- *
- * @param courseId the id of the course
- * @return the course with eagerly loaded learning paths
- */
- @GetMapping("courses/{courseId}/with-learning-paths")
- @EnforceAtLeastInstructor
- public ResponseEntity getCourseWithLearningPaths(@PathVariable Long courseId) {
- log.debug("REST request to get a course with its organizations : {}", courseId);
- Course course = courseRepository.findWithEagerLearningPathsByIdElseThrow(courseId);
- authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
- return ResponseEntity.ok(course);
- }
-
/**
* GET /courses/:courseId/lockedSubmissions Get locked submissions for course for user
*
@@ -1219,4 +1204,19 @@ public ResponseEntity> addUsersToCourseGroup(@PathVariable Long
List notFoundStudentsDtos = courseService.registerUsersForCourseGroup(courseId, studentDtos, courseGroup);
return ResponseEntity.ok().body(notFoundStudentsDtos);
}
+
+ /**
+ * GET /courses/:courseId/learning-paths-enabled Get a course by id with eagerly loaded learning paths
+ *
+ * @param courseId the id of the course
+ * @return the course with eagerly loaded learning paths
+ */
+ @GetMapping("courses/{courseId}/learning-paths-enabled")
+ @EnforceAtLeastInstructor
+ public ResponseEntity getCourseLearningPathsEnabled(@PathVariable Long courseId) {
+ log.debug("REST request to get if course has learning paths enabled : {}", courseId);
+ Course course = courseRepository.findByIdElseThrow(courseId);
+ authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
+ return ResponseEntity.ok(course.getLearningPathsEnabled());
+ }
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
index 0bb39b84f150..8ac77b855294 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
@@ -6,7 +6,7 @@
Learning Path Management
-
-
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index b27a8d185900..721dde1dd465 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -205,6 +205,6 @@ export class LearningPathManagementComponent implements OnInit {
backdrop: 'static',
windowClass: 'learning-path-modal',
});
- modalRef.componentInstance.learningPathId = learningPath.id;
+ modalRef.componentInstance.learningPath = learningPath;
}
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
index 4b38a926a616..4d1b65360501 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
@@ -1,12 +1,13 @@
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
index 5db8905472ca..cc798cd46adb 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
@@ -17,6 +17,7 @@
.graph {
width: 100%;
+ overflow: hidden;
}
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
index c898cfde1f28..3e87482443c0 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
@@ -1,13 +1,14 @@
import { Component, Input, ViewChild } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
+import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
@Component({
selector: 'jhi-learning-path-progress-modal',
styleUrls: ['./learning-path-progress-modal.component.scss'],
templateUrl: './learning-path-progress-modal.component.html',
})
export class LearningPathProgressModalComponent {
- @Input() learningPathId: number;
+ @Input() learningPath: LearningPathPageableSearchDTO;
@ViewChild('learningPathGraphComponent') learningPathGraphComponent: LearningPathGraphComponent;
constructor(private activeModal: NgbActiveModal) {}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
index b5da609387c9..66f5c6778206 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
@@ -1,11 +1,16 @@
-
-
+
+
{{ learningPath.user?.name }} ({{ learningPath.user?.login }})
-
-
-
-
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
index 3d968549da59..c050632cc577 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
@@ -1,11 +1,13 @@
-import { Component, EventEmitter, Output } from '@angular/core';
+import { Component, EventEmitter, Input, Output } from '@angular/core';
import { faArrowsRotate, faArrowsToEye, faXmark } from '@fortawesome/free-solid-svg-icons';
+import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
@Component({
selector: 'jhi-learning-path-progress-nav',
templateUrl: './learning-path-progress-nav.component.html',
})
export class LearningPathProgressNavComponent {
+ @Input() learningPath: LearningPathPageableSearchDTO;
@Output() onRefresh: EventEmitter
= new EventEmitter();
@Output() onCenterView: EventEmitter = new EventEmitter();
@Output() onClose: EventEmitter = new EventEmitter();
diff --git a/src/main/webapp/i18n/de/competency.json b/src/main/webapp/i18n/de/competency.json
index 74db1b570c43..05ef50f5b864 100644
--- a/src/main/webapp/i18n/de/competency.json
+++ b/src/main/webapp/i18n/de/competency.json
@@ -160,8 +160,12 @@
"table": {
"name": "Name",
"login": "Login",
- "progress": "Fortschritt",
- "view": "Ansehen"
+ "progress": "Fortschritt"
+ },
+ "progressNav": {
+ "header": "Lernpfad",
+ "refresh": "Refresh",
+ "center": "Center view"
}
},
"sideBar": {
diff --git a/src/main/webapp/i18n/en/competency.json b/src/main/webapp/i18n/en/competency.json
index 3b7cb1d04568..a299129239be 100644
--- a/src/main/webapp/i18n/en/competency.json
+++ b/src/main/webapp/i18n/en/competency.json
@@ -159,8 +159,12 @@
"table": {
"name": "Name",
"login": "Login",
- "progress": "Progress",
- "view": "View"
+ "progress": "Progress"
+ },
+ "progressNav": {
+ "header": "Learning Path",
+ "refresh": "Refresh",
+ "center": "Center view"
}
},
"sideBar": {
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
index dbe4e026f787..7c57307bcbb5 100644
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
@@ -6,6 +6,7 @@ import { LearningPathProgressModalComponent } from 'app/course/learning-paths/le
import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
import { By } from '@angular/platform-browser';
import { LearningPathProgressNavComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-nav.component';
+import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
describe('LearningPathProgressModalComponent', () => {
let fixture: ComponentFixture;
@@ -33,8 +34,8 @@ describe('LearningPathProgressModalComponent', () => {
jest.restoreAllMocks();
});
- it('should display learning path graph if id is present', () => {
- comp.learningPathId = 1;
+ it('should display learning path graph if learning path is present', () => {
+ comp.learningPath = { id: 1 } as LearningPathPageableSearchDTO;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.graph')).nativeElement).toBeTruthy();
});
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
index 84eca7eebcb3..437b2781c3ea 100644
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
@@ -2,6 +2,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ArtemisTestModule } from '../../../test.module';
import { By } from '@angular/platform-browser';
import { LearningPathProgressNavComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-nav.component';
+import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
+import { UserNameAndLoginDTO } from 'app/core/user/user.model';
+import { MockPipe } from 'ng-mocks';
+import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
+import { NgbTooltipMocksModule } from '../../../helpers/mocks/directive/ngbTooltipMocks.module';
describe('LearningPathProgressNavComponent', () => {
let fixture: ComponentFixture;
@@ -9,11 +14,12 @@ describe('LearningPathProgressNavComponent', () => {
let onRefreshStub: jest.SpyInstance;
let onCenterViewStub: jest.SpyInstance;
let onCloseStub: jest.SpyInstance;
+ let learningPath: LearningPathPageableSearchDTO;
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [ArtemisTestModule],
- declarations: [LearningPathProgressNavComponent],
+ imports: [ArtemisTestModule, NgbTooltipMocksModule],
+ declarations: [LearningPathProgressNavComponent, MockPipe(ArtemisTranslatePipe)],
providers: [],
})
.compileComponents()
@@ -23,6 +29,11 @@ describe('LearningPathProgressNavComponent', () => {
onRefreshStub = jest.spyOn(comp.onRefresh, 'emit');
onCenterViewStub = jest.spyOn(comp.onCenterView, 'emit');
onCloseStub = jest.spyOn(comp.onClose, 'emit');
+ learningPath = new LearningPathPageableSearchDTO();
+ learningPath.user = new UserNameAndLoginDTO();
+ learningPath.user.name = 'some arbitrary name';
+ learningPath.user.login = 'somearbitrarylogin';
+ comp.learningPath = learningPath;
fixture.detectChanges();
});
});
@@ -31,6 +42,11 @@ describe('LearningPathProgressNavComponent', () => {
jest.restoreAllMocks();
});
+ it('should create', () => {
+ expect(fixture).toBeTruthy();
+ expect(comp).toBeTruthy();
+ });
+
it('should emit refresh on click', () => {
const button = fixture.debugElement.query(By.css('#refresh-button'));
expect(button).not.toBeNull();
From c66f26e669caa80c62b85f8f23df765581c25e17 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Sat, 29 Jul 2023 18:44:24 +0200
Subject: [PATCH 093/215] add learning path health check endpoint
---
.../repository/LearningPathRepository.java | 7 ++
.../artemis/service/LearningPathService.java | 23 +++++++
.../web/rest/LearningPathResource.java | 23 ++++++-
.../dto/competency/LearningPathHealthDTO.java | 17 +++++
.../lecture/LearningPathIntegrationTest.java | 17 ++++-
.../service/LearningPathServiceTest.java | 67 ++++++++++++++-----
6 files changed, 133 insertions(+), 21 deletions(-)
create mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathHealthDTO.java
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
index 81acb13fbff2..4a9cca28bcd1 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
@@ -72,4 +72,11 @@ default LearningPath findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(lo
default LearningPath findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersByIdElseThrow(long learningPathId) {
return findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersById(learningPathId).orElseThrow(() -> new EntityNotFoundException("LearningPath", learningPathId));
}
+
+ @Query("""
+ SELECT COUNT (learningPath)
+ FROM LearningPath learningPath
+ WHERE learningPath.course.id = :#{#courseId}
+ """)
+ Long countByCourseId(@Param("courseId") long courseId);
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index 9a2fc5ca4922..4009c7314fae 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -23,6 +23,7 @@
import de.tum.in.www1.artemis.repository.*;
import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO;
import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathHealthDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathPageableSearchDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
import de.tum.in.www1.artemis.web.rest.util.PageUtil;
@@ -410,4 +411,26 @@ public LearningObject getRecommendation(@NotNull LearningPath learningPath) {
.filter(exercise -> !exerciseRepository.findByIdWithStudentParticipationsElseThrow(exercise.getId()).isCompletedFor(learningPath.getUser()))))
.findFirst().orElse(null);
}
+
+ /**
+ * Gets the health status of learning paths for the given course.
+ *
+ * @param course the course for which the health status should be generated
+ * @return dto containing the health status and additional information (missing learning paths) if needed
+ */
+ public LearningPathHealthDTO getHealthStatusForCourse(Course course) {
+ if (!course.getLearningPathsEnabled()) {
+ return new LearningPathHealthDTO(LearningPathHealthDTO.HealthStatus.DISABLED);
+ }
+
+ var numberOfStudents = userRepository.countUserInGroup(course.getStudentGroupName());
+ var numberOfLearningPaths = learningPathRepository.countByCourseId(course.getId());
+
+ if (numberOfStudents.equals(numberOfLearningPaths)) {
+ return new LearningPathHealthDTO(LearningPathHealthDTO.HealthStatus.OK);
+ }
+ else {
+ return new LearningPathHealthDTO(LearningPathHealthDTO.HealthStatus.MISSING, numberOfStudents - numberOfLearningPaths);
+ }
+ }
}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 37db0dff1934..424794338f08 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -22,9 +22,7 @@
import de.tum.in.www1.artemis.service.LearningPathService;
import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO;
import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO;
-import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathPageableSearchDTO;
-import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathRecommendationDTO;
-import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.*;
import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;
@RestController
@@ -170,4 +168,23 @@ else if (recommendation instanceof LectureUnit lectureUnit) {
return ResponseEntity.ok(new LearningPathRecommendationDTO(recommendation.getId(), -1, LearningPathRecommendationDTO.RecommendationType.EXERCISE));
}
}
+
+ /**
+ * GET /courses/:courseId/learning-path-health : Gets the health status of learning paths for the course.
+ *
+ * @param courseId the id of the course for which the health status should be fetched
+ * @return the ResponseEntity with status 200 (OK) and with body the health status
+ */
+ @GetMapping("/courses/{courseId}/learning-path-health")
+ @EnforceAtLeastInstructor
+ public ResponseEntity getHealthStatusForCourse(@PathVariable long courseId) {
+ log.debug("REST request to get health status of learning paths in course with id: {}", courseId);
+
+ Course course = courseRepository.findByIdElseThrow(courseId);
+ if (!authorizationCheckService.isAtLeastInstructorInCourse(course, null) && !authorizationCheckService.isAdmin()) {
+ throw new AccessForbiddenException("You are not allowed to access the health status of learning paths for this course.");
+ }
+
+ return ResponseEntity.ok(learningPathService.getHealthStatusForCourse(course));
+ }
}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathHealthDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathHealthDTO.java
new file mode 100644
index 000000000000..f261bf7cb26a
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathHealthDTO.java
@@ -0,0 +1,17 @@
+package de.tum.in.www1.artemis.web.rest.dto.competency;
+
+import javax.validation.constraints.NotNull;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record LearningPathHealthDTO(@NotNull HealthStatus status, Long missingLearningPaths) {
+
+ public LearningPathHealthDTO(HealthStatus status) {
+ this(status, null);
+ }
+
+ public enum HealthStatus {
+ OK, DISABLED, MISSING
+ }
+}
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 011ce9c9bdc7..ec17beeb9ef7 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -36,9 +36,7 @@
import de.tum.in.www1.artemis.service.LectureUnitService;
import de.tum.in.www1.artemis.user.UserUtilService;
import de.tum.in.www1.artemis.util.PageableSearchUtilService;
-import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathPageableSearchDTO;
-import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathRecommendationDTO;
-import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.*;
class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbucketJiraTest {
@@ -156,6 +154,7 @@ private void testAllPreAuthorize() throws Exception {
request.putWithResponseBody("/api/courses/" + course.getId() + "/learning-paths/enable", null, Course.class, HttpStatus.FORBIDDEN);
final var search = pageableSearchUtilService.configureSearch("");
request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.FORBIDDEN, LearningPath.class, pageableSearchUtilService.searchMapping(search));
+ request.get("/api/courses/" + course.getId() + "/learning-path-health", HttpStatus.FORBIDDEN, LearningPathHealthDTO.class);
}
private void enableLearningPathsRESTCall(Course course) throws Exception {
@@ -428,4 +427,16 @@ void testGetRecommendationAsOwner() throws Exception {
final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
request.get("/api/learning-path/" + learningPath.getId() + "/recommendation", HttpStatus.OK, LearningPathRecommendationDTO.class);
}
+
+ /**
+ * This only tests if the end point successfully retrieves the health status. The correctness of the health status is tested in LearningPathServiceTest.
+ *
+ * @throws Exception the request failed
+ * @see de.tum.in.www1.artemis.service.LearningPathServiceTest
+ */
+ @Test
+ @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
+ void testGetHealthStatusForCourse() throws Exception {
+ request.get("/api/courses/" + course.getId() + "/learning-path-health", HttpStatus.OK, LearningPathHealthDTO.class);
+ }
}
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 1588ae37b389..c878177504de 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -2,6 +2,7 @@
import static org.assertj.core.api.Assertions.assertThat;
+import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -15,6 +16,7 @@
import de.tum.in.www1.artemis.AbstractSpringIntegrationBambooBitbucketJiraTest;
import de.tum.in.www1.artemis.competency.CompetencyUtilService;
import de.tum.in.www1.artemis.competency.LearningPathUtilService;
+import de.tum.in.www1.artemis.course.CourseFactory;
import de.tum.in.www1.artemis.course.CourseUtilService;
import de.tum.in.www1.artemis.domain.Course;
import de.tum.in.www1.artemis.domain.Exercise;
@@ -26,12 +28,17 @@
import de.tum.in.www1.artemis.exercise.programmingexercise.ProgrammingExerciseUtilService;
import de.tum.in.www1.artemis.lecture.LectureUtilService;
import de.tum.in.www1.artemis.repository.CompetencyRepository;
+import de.tum.in.www1.artemis.repository.CourseRepository;
import de.tum.in.www1.artemis.repository.LearningPathRepository;
import de.tum.in.www1.artemis.security.SecurityUtils;
+import de.tum.in.www1.artemis.user.UserUtilService;
+import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathHealthDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
class LearningPathServiceTest extends AbstractSpringIntegrationBambooBitbucketJiraTest {
+ private static final String TEST_PREFIX = "learningpathservice";
+
@Autowired
LearningPathService learningPathService;
@@ -56,6 +63,12 @@ class LearningPathServiceTest extends AbstractSpringIntegrationBambooBitbucketJi
@Autowired
CompetencyRepository competencyRepository;
+ @Autowired
+ UserUtilService userUtilService;
+
+ @Autowired
+ CourseRepository courseRepository;
+
private Course course;
private void generateAndAssert(NgxLearningPathDTO expected) {
@@ -72,6 +85,11 @@ private void assertNgxRepEquals(NgxLearningPathDTO was, NgxLearningPathDTO expec
assertThat(was.clusters()).as("correct clusters").containsExactlyInAnyOrderElementsOf(expected.clusters());
}
+ @BeforeEach
+ void setAuthorizationForRepositoryRequests() {
+ SecurityUtils.setAuthorizationObject();
+ }
+
@BeforeEach
void setup() {
course = courseUtilService.createCourse();
@@ -80,11 +98,6 @@ void setup() {
@Nested
class GenerateNgxRepresentationBaseTest {
- @BeforeEach
- void setAuthorizationForRepositoryRequests() {
- SecurityUtils.setAuthorizationObject();
- }
-
@Test
void testEmptyLearningPath() {
NgxLearningPathDTO expected = new NgxLearningPathDTO(Set.of(), Set.of(), Set.of());
@@ -163,11 +176,6 @@ class GenerateNgxRepresentationRelationTest {
Set expectedClusters;
- @BeforeEach
- void setAuthorizationForRepositoryRequests() {
- SecurityUtils.setAuthorizationObject();
- }
-
@BeforeEach
void setup() {
competency1 = competencyUtilService.createCompetency(course);
@@ -274,11 +282,6 @@ private static NgxLearningPathDTO.Node getNodeForExercise(Competency competency,
@Nested
class RecommendationTest {
- @BeforeEach
- void setAuthorizationForRepositoryRequests() {
- SecurityUtils.setAuthorizationObject();
- }
-
@Test
void testGetRecommendationEmpty() {
competencyUtilService.createCompetency(course);
@@ -297,4 +300,38 @@ void testGetRecommendationNotEmpty() {
assertThat(learningPathService.getRecommendation(learningPath)).isNotNull();
}
}
+
+ @Nested
+ class HeathCheckTest {
+
+ @BeforeEach
+ void setup() {
+ userUtilService.addUsers(TEST_PREFIX, 5, 1, 1, 1);
+ course = CourseFactory.generateCourse(null, ZonedDateTime.now().minusDays(8), ZonedDateTime.now().minusDays(8), new HashSet<>(), TEST_PREFIX + "tumuser",
+ TEST_PREFIX + "tutor", TEST_PREFIX + "editor", TEST_PREFIX + "instructor");
+ course = courseRepository.save(course);
+ }
+
+ @Test
+ void testHealthStatusDisabled() {
+ var healthStatus = learningPathService.getHealthStatusForCourse(course);
+ assertThat(healthStatus.status()).isEqualTo(LearningPathHealthDTO.HealthStatus.DISABLED);
+ }
+
+ @Test
+ void testHealthStatusOK() {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ var healthStatus = learningPathService.getHealthStatusForCourse(course);
+ assertThat(healthStatus.status()).isEqualTo(LearningPathHealthDTO.HealthStatus.OK);
+ }
+
+ @Test
+ void testHealthStatusMissing() {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ userUtilService.addStudent(TEST_PREFIX + "tumuser", TEST_PREFIX + "student1337");
+ var healthStatus = learningPathService.getHealthStatusForCourse(course);
+ assertThat(healthStatus.status()).isEqualTo(LearningPathHealthDTO.HealthStatus.MISSING);
+ assertThat(healthStatus.missingLearningPaths()).isEqualTo(1);
+ }
+ }
}
From 6a1ad9e9158e2cf709c865329c6457996b07d53a Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Sun, 30 Jul 2023 03:21:09 +0200
Subject: [PATCH 094/215] add learning path health check client side
---
.../web/rest/LearningPathResource.java | 23 ++++++++-
.../learning-path-management.component.html | 15 +++++-
.../learning-path-management.component.ts | 47 ++++++++++---------
.../learning-paths/learning-path.service.ts | 9 ++++
.../competency/learning-path-health.model.ts | 14 ++++++
src/main/webapp/i18n/de/competency.json | 8 ++++
src/main/webapp/i18n/en/competency.json | 8 ++++
.../competency/LearningPathUtilService.java | 26 ++++++++--
.../lecture/LearningPathIntegrationTest.java | 26 +++++++++-
...learning-path-management.component.spec.ts | 45 ++++++++++++------
.../service/learning-path.service.spec.ts | 12 +++++
11 files changed, 189 insertions(+), 44 deletions(-)
create mode 100644 src/main/webapp/app/entities/competency/learning-path-health.model.ts
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 424794338f08..5c1bae9fd858 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -54,7 +54,7 @@ public LearningPathResource(CourseRepository courseRepository, AuthorizationChec
* PUT /courses/:courseId/learning-paths/enable : Enables and generates learning paths for the course
*
* @param courseId the id of the course for which the learning paths should be enabled
- * @return the ResponseEntity with status 200 (OK) and with body the updated course
+ * @return the ResponseEntity with status 200 (OK)
*/
@PutMapping("/courses/{courseId}/learning-paths/enable")
@EnforceAtLeastInstructor
@@ -68,11 +68,30 @@ public ResponseEntity enableLearningPathsForCourse(@PathVariable Long cour
course.setLearningPathsEnabled(true);
learningPathService.generateLearningPaths(course);
- course = courseRepository.save(course);
+ courseRepository.save(course);
return ResponseEntity.ok().build();
}
+ /**
+ * PUT /courses/:courseId/learning-paths/generate-missing : Generates missing learning paths for the course
+ *
+ * @param courseId the id of the course for which the learning paths should be created
+ * @return the ResponseEntity with status 200 (OK)
+ */
+ @PutMapping("/courses/{courseId}/learning-paths/generate-missing")
+ @EnforceAtLeastInstructor
+ public ResponseEntity generateMissingLearningPathsForCourse(@PathVariable Long courseId) {
+ log.debug("REST request to generate missing learning paths for course with id: {}", courseId);
+ Course course = courseRepository.findWithEagerCompetenciesByIdElseThrow(courseId);
+ authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
+ if (!course.getLearningPathsEnabled()) {
+ throw new BadRequestException("Learning paths not enabled for this course.");
+ }
+ learningPathService.generateLearningPaths(course);
+ return ResponseEntity.ok().build();
+ }
+
/**
* GET /courses/:courseId/learning-paths : Gets all the learning paths of a course. The result is pageable.
*
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
index 7036b9a7016c..4d85780caa13 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
@@ -6,7 +6,7 @@
Learning Path Management
-
+
-
+
+
+
{{ 'artemisApp.learningPath.manageLearningPaths.health.missing.title' | artemisTranslate }}
+
{{ 'artemisApp.learningPath.manageLearningPaths.health.missing.body' | artemisTranslate }}
+
+ {{
+ 'artemisApp.learningPath.manageLearningPaths.health.missing.action' | artemisTranslate
+ }}
+
+
+
+
Search for Learning Path:
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index 721dde1dd465..11ce6158e1f7 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -1,6 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
-import { CourseManagementService } from 'app/course/manage/course-management.service';
import { Subject } from 'rxjs';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
import { debounceTime, finalize, switchMap, tap } from 'rxjs/operators';
@@ -11,9 +10,10 @@ import { PageableSearch, SearchResult, SortingOrder } from 'app/shared/table/pag
import { LearningPathPagingService } from 'app/course/learning-paths/learning-path-paging.service';
import { SortService } from 'app/shared/service/sort.service';
import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
-import { faSort } from '@fortawesome/free-solid-svg-icons';
+import { faSort, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
+import { HealthStatus, LearningPathHealthDTO } from 'app/entities/competency/learning-path-health.model';
export enum TableColumn {
ID = 'ID',
@@ -30,7 +30,7 @@ export class LearningPathManagementComponent implements OnInit {
isLoading = false;
courseId: number;
- learningPathsEnabled: boolean;
+ health: LearningPathHealthDTO;
searchLoading = false;
readonly column = TableColumn;
@@ -49,10 +49,10 @@ export class LearningPathManagementComponent implements OnInit {
// icons
faSort = faSort;
+ faTriangleExclamation = faTriangleExclamation;
constructor(
private activatedRoute: ActivatedRoute,
- private courseManagementService: CourseManagementService,
private learningPathService: LearningPathService,
private alertService: AlertService,
private pagingService: LearningPathPagingService,
@@ -124,8 +124,8 @@ export class LearningPathManagementComponent implements OnInit {
private loadData() {
this.isLoading = true;
- this.courseManagementService
- .getCourseLearningPathsEnabled(this.courseId)
+ this.learningPathService
+ .getHealthStatusForCourse(this.courseId)
.pipe(
finalize(() => {
this.isLoading = false;
@@ -133,8 +133,8 @@ export class LearningPathManagementComponent implements OnInit {
)
.subscribe({
next: (res) => {
- this.learningPathsEnabled = res.body!;
- if (this.learningPathsEnabled) {
+ this.health = res.body!;
+ if (this.health.status !== HealthStatus.DISABLED) {
this.performSearch(this.sort, 0);
this.performSearch(this.search, 300);
}
@@ -145,19 +145,22 @@ export class LearningPathManagementComponent implements OnInit {
enableLearningPaths() {
this.isLoading = true;
- this.learningPathService
- .enableLearningPaths(this.courseId)
- .pipe(
- finalize(() => {
- this.loadData();
- }),
- )
- .subscribe({
- next: () => {
- this.learningPathsEnabled = true;
- },
- error: (res: HttpErrorResponse) => onError(this.alertService, res),
- });
+ this.learningPathService.enableLearningPaths(this.courseId).subscribe({
+ next: () => {
+ this.loadData();
+ },
+ error: (res: HttpErrorResponse) => onError(this.alertService, res),
+ });
+ }
+
+ generateMissing() {
+ this.isLoading = true;
+ this.learningPathService.generateMissingLearningPathsForCourse(this.courseId).subscribe({
+ next: () => {
+ this.loadData();
+ },
+ error: (res: HttpErrorResponse) => onError(this.alertService, res),
+ });
}
/**
@@ -207,4 +210,6 @@ export class LearningPathManagementComponent implements OnInit {
});
modalRef.componentInstance.learningPath = learningPath;
}
+
+ protected readonly HealthStatus = HealthStatus;
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path.service.ts b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
index 5f2f66d54d8d..7fc0e33a1a3c 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path.service.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
@@ -3,6 +3,7 @@ import { Observable } from 'rxjs';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { LearningPathRecommendationDTO, NgxLearningPathDTO } from 'app/entities/competency/learning-path.model';
import { map } from 'rxjs/operators';
+import { LearningPathHealthDTO } from 'app/entities/competency/learning-path-health.model';
@Injectable({ providedIn: 'root' })
export class LearningPathService {
@@ -14,6 +15,10 @@ export class LearningPathService {
return this.httpClient.put
(`${this.resourceURL}/courses/${courseId}/learning-paths/enable`, null, { observe: 'response' });
}
+ generateMissingLearningPathsForCourse(courseId: number): Observable> {
+ return this.httpClient.put(`${this.resourceURL}/courses/${courseId}/learning-paths/generate-missing`, null, { observe: 'response' });
+ }
+
getLearningPathId(courseId: number) {
return this.httpClient.get(`${this.resourceURL}/courses/${courseId}/learning-path-id`, { observe: 'response' });
}
@@ -38,4 +43,8 @@ export class LearningPathService {
getRecommendation(learningPathId: number) {
return this.httpClient.get(`${this.resourceURL}/learning-path/${learningPathId}/recommendation`, { observe: 'response' });
}
+
+ getHealthStatusForCourse(courseId: number) {
+ return this.httpClient.get(`${this.resourceURL}/courses/${courseId}/learning-path-health`, { observe: 'response' });
+ }
}
diff --git a/src/main/webapp/app/entities/competency/learning-path-health.model.ts b/src/main/webapp/app/entities/competency/learning-path-health.model.ts
new file mode 100644
index 000000000000..bf3a9794d178
--- /dev/null
+++ b/src/main/webapp/app/entities/competency/learning-path-health.model.ts
@@ -0,0 +1,14 @@
+export class LearningPathHealthDTO {
+ public status?: HealthStatus;
+ public missingLearningPaths?: number;
+
+ constructor(status: HealthStatus) {
+ this.status = status;
+ }
+}
+
+export enum HealthStatus {
+ OK = 'OK',
+ DISABLED = 'DISABLED',
+ MISSING = 'MISSING',
+}
diff --git a/src/main/webapp/i18n/de/competency.json b/src/main/webapp/i18n/de/competency.json
index 05ef50f5b864..c2050cb16f39 100644
--- a/src/main/webapp/i18n/de/competency.json
+++ b/src/main/webapp/i18n/de/competency.json
@@ -153,6 +153,14 @@
"learningPathButton": "Lernpfade",
"manageLearningPaths": {
"title": "Lernpfadmanagement",
+ "health": {
+ "missing": {
+ "title": "Fehlende Lernpfade",
+ "body": "Für einige Studierende wurde noch kein Lernpfad erstellt. Dies ist nicht kritisch. Ihre Lernpfade werden generiert, wenn sie ihren Lernpfad das erste mal anfragen.",
+ "action": "Erstellen",
+ "hint": "Erstellen der fehlenden Lernpfade"
+ }
+ },
"isDisabled": "Lernpfade sind für diesen Kurs nicht aktiviert.",
"enable": "Lernpfade aktivieren",
"enableHint": "Die Erstellung von Lernpfaden für alle Studierende kann einige Minuten dauern.",
diff --git a/src/main/webapp/i18n/en/competency.json b/src/main/webapp/i18n/en/competency.json
index a299129239be..195dd6b9f885 100644
--- a/src/main/webapp/i18n/en/competency.json
+++ b/src/main/webapp/i18n/en/competency.json
@@ -152,6 +152,14 @@
"learningPathButton": "Learning Paths",
"manageLearningPaths": {
"title": "Learning Path Management",
+ "health": {
+ "missing": {
+ "title": "Missing Learning Paths",
+ "body": "Some student's have not generated their Learning Paths yet. This is not critical. Their Learning Paths will be created once they request their learning path for the first time.",
+ "action": "Generate",
+ "hint": "Generate missing Learning Paths"
+ }
+ },
"isDisabled": "Learning Paths are currently disabled for this course.",
"enable": "Enable Learning Paths",
"enableHint": "The creation of Learning Paths for every student may take a few minutes.",
diff --git a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
index 9c116978e58e..e6f580eddb99 100644
--- a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
+++ b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
@@ -6,11 +6,10 @@
import org.springframework.stereotype.Service;
import de.tum.in.www1.artemis.domain.Course;
+import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.domain.competency.Competency;
import de.tum.in.www1.artemis.domain.competency.LearningPath;
-import de.tum.in.www1.artemis.repository.CompetencyRepository;
-import de.tum.in.www1.artemis.repository.CourseRepository;
-import de.tum.in.www1.artemis.repository.LearningPathRepository;
+import de.tum.in.www1.artemis.repository.*;
import de.tum.in.www1.artemis.service.LearningPathService;
/**
@@ -31,6 +30,9 @@ public class LearningPathUtilService {
@Autowired
CompetencyRepository competencyRepository;
+ @Autowired
+ UserRepository userRepository;
+
/**
* Enable and generate learning paths for course.
*
@@ -68,4 +70,22 @@ public LearningPath createLearningPath(Set competencies) {
learningPath.setCompetencies(competencies);
return learningPathRepository.save(learningPath);
}
+
+ /**
+ * Deletes all learning paths of given user.
+ *
+ * @param user the user for which all learning paths should be deleted
+ */
+ public void deleteLearningPaths(User user) {
+ learningPathRepository.deleteAll(user.getLearningPaths());
+ }
+
+ /**
+ * Deletes all learning paths of all given user.
+ *
+ * @param users the users for which all learning paths should be deleted
+ */
+ public void deleteLearningPaths(Iterable users) {
+ users.forEach(this::deleteLearningPaths);
+ }
}
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index ec17beeb9ef7..6d7d1365ace8 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -151,7 +151,8 @@ private ZonedDateTime future(long days) {
}
private void testAllPreAuthorize() throws Exception {
- request.putWithResponseBody("/api/courses/" + course.getId() + "/learning-paths/enable", null, Course.class, HttpStatus.FORBIDDEN);
+ request.put("/api/courses/" + course.getId() + "/learning-paths/enable", null, HttpStatus.FORBIDDEN);
+ request.put("/api/courses/" + course.getId() + "/learning-paths/generate-missing", null, HttpStatus.FORBIDDEN);
final var search = pageableSearchUtilService.configureSearch("");
request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.FORBIDDEN, LearningPath.class, pageableSearchUtilService.searchMapping(search));
request.get("/api/courses/" + course.getId() + "/learning-path-health", HttpStatus.FORBIDDEN, LearningPathHealthDTO.class);
@@ -226,7 +227,28 @@ void testEnableLearningPathsWithNoCompetencies() throws Exception {
void testEnableLearningPathsAlreadyEnabled() throws Exception {
course.setLearningPathsEnabled(true);
courseRepository.save(course);
- request.putWithResponseBody("/api/courses/" + course.getId() + "/learning-paths/enable", course, Course.class, HttpStatus.BAD_REQUEST);
+ request.put("/api/courses/" + course.getId() + "/learning-paths/enable", null, HttpStatus.BAD_REQUEST);
+ }
+
+ @Test
+ @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
+ void testGenerateMissingLearningPathsForCourse() throws Exception {
+ course.setLearningPathsEnabled(true);
+ courseRepository.save(course);
+ final var students = userRepository.getStudents(course);
+ students.stream().map(User::getId).map(userRepository::findWithLearningPathsByIdElseThrow).forEach(learningPathUtilService::deleteLearningPaths);
+ request.put("/api/courses/" + course.getId() + "/learning-paths/generate-missing", null, HttpStatus.OK);
+ students.forEach(user -> {
+ user = userRepository.findWithLearningPathsByIdElseThrow(user.getId());
+ assertThat(user.getLearningPaths().size()).isEqualTo(1);
+ });
+
+ }
+
+ @Test
+ @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
+ void testGenerateMissingLearningPathsForCourseNotEnabled() throws Exception {
+ request.put("/api/courses/" + course.getId() + "/learning-paths/generate-missing", null, HttpStatus.BAD_REQUEST);
}
@Test
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
index 9d4fddf49670..e2ab1499590a 100644
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
@@ -5,22 +5,20 @@ import { SortService } from 'app/shared/service/sort.service';
import { PageableSearch, SearchResult, SortingOrder } from 'app/shared/table/pageable-table';
import { LearningPath } from 'app/entities/competency/learning-path.model';
import { ArtemisTestModule } from '../../../test.module';
-import { MockComponent, MockDirective, MockProvider } from 'ng-mocks';
+import { MockComponent, MockDirective } from 'ng-mocks';
import { ButtonComponent } from 'app/shared/components/button.component';
import { NgbPagination } from '@ng-bootstrap/ng-bootstrap';
import { SortByDirective } from 'app/shared/sort/sort-by.directive';
import { SortDirective } from 'app/shared/sort/sort.directive';
import { of } from 'rxjs';
-import { CourseManagementService } from 'app/course/manage/course-management.service';
import { ActivatedRoute } from '@angular/router';
import { HttpResponse } from '@angular/common/http';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
+import { HealthStatus, LearningPathHealthDTO } from 'app/entities/competency/learning-path-health.model';
describe('LearningPathManagementComponent', () => {
let fixture: ComponentFixture;
let comp: LearningPathManagementComponent;
- let courseManagementService: CourseManagementService;
- let getCourseLearningPathsEnabledStub: jest.SpyInstance;
let pagingService: LearningPathPagingService;
let sortService: SortService;
let searchForLearningPathsStub: jest.SpyInstance;
@@ -30,6 +28,9 @@ describe('LearningPathManagementComponent', () => {
let learningPath: LearningPath;
let learningPathService: LearningPathService;
let enableLearningPathsStub: jest.SpyInstance;
+ let generateMissingLearningPathsForCourseStub: jest.SpyInstance;
+ let getHealthStatusForCourseStub: jest.SpyInstance;
+ let health: LearningPathHealthDTO;
let courseId: number;
beforeEach(() => {
TestBed.configureTestingModule({
@@ -46,21 +47,20 @@ describe('LearningPathManagementComponent', () => {
},
},
},
- MockProvider(CourseManagementService),
],
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(LearningPathManagementComponent);
comp = fixture.componentInstance;
- courseManagementService = TestBed.inject(CourseManagementService);
- getCourseLearningPathsEnabledStub = jest.spyOn(courseManagementService, 'getCourseLearningPathsEnabled');
pagingService = TestBed.inject(LearningPathPagingService);
sortService = TestBed.inject(SortService);
searchForLearningPathsStub = jest.spyOn(pagingService, 'searchForLearningPaths');
sortByPropertyStub = jest.spyOn(sortService, 'sortByProperty');
learningPathService = TestBed.inject(LearningPathService);
enableLearningPathsStub = jest.spyOn(learningPathService, 'enableLearningPaths');
+ generateMissingLearningPathsForCourseStub = jest.spyOn(learningPathService, 'generateMissingLearningPathsForCourse');
+ getHealthStatusForCourseStub = jest.spyOn(learningPathService, 'getHealthStatusForCourse');
});
});
@@ -73,7 +73,6 @@ describe('LearningPathManagementComponent', () => {
learningPath = new LearningPath();
learningPath.id = 2;
courseId = 1;
- getCourseLearningPathsEnabledStub.mockReturnValue(of(new HttpResponse({ body: true })));
searchResult = { numberOfPages: 3, resultsOnPage: [learningPath] };
state = {
page: 1,
@@ -85,6 +84,9 @@ describe('LearningPathManagementComponent', () => {
};
searchForLearningPathsStub.mockReturnValue(of(searchResult));
enableLearningPathsStub.mockReturnValue(of(new HttpResponse()));
+ generateMissingLearningPathsForCourseStub.mockReturnValue(of(new HttpResponse()));
+ health = new LearningPathHealthDTO(HealthStatus.OK);
+ getHealthStatusForCourseStub.mockReturnValue(of(new HttpResponse({ body: health })));
});
const setStateAndCallOnInit = (middleExpectation: () => void) => {
@@ -96,24 +98,39 @@ describe('LearningPathManagementComponent', () => {
expect(sortByPropertyStub).toHaveBeenCalledWith(searchResult.resultsOnPage, comp.sortedColumn, comp.listSorting);
};
- it('should load learning paths enabled on init', fakeAsync(() => {
+ it('should load health status on init', fakeAsync(() => {
setStateAndCallOnInit(() => {
comp.listSorting = true;
tick(10);
- expect(getCourseLearningPathsEnabledStub).toHaveBeenCalledWith(courseId);
- expect(comp.learningPathsEnabled).toBeTrue();
+ expect(getHealthStatusForCourseStub).toHaveBeenCalledWith(courseId);
+ expect(comp.health).toEqual(health);
});
}));
it('should enable learning paths and load data', fakeAsync(() => {
- getCourseLearningPathsEnabledStub.mockReturnValueOnce(of(new HttpResponse({ body: false }))).mockReturnValueOnce(of(new HttpResponse({ body: true })));
+ const healthDisabled = new LearningPathHealthDTO(HealthStatus.DISABLED);
+ getHealthStatusForCourseStub.mockReturnValueOnce(of(new HttpResponse({ body: healthDisabled }))).mockReturnValueOnce(of(new HttpResponse({ body: health })));
fixture.detectChanges();
comp.ngOnInit();
+ expect(comp.health).toEqual(healthDisabled);
comp.enableLearningPaths();
expect(enableLearningPathsStub).toHaveBeenCalledOnce();
expect(enableLearningPathsStub).toHaveBeenCalledWith(courseId);
- expect(getCourseLearningPathsEnabledStub).toHaveBeenCalledTimes(3);
- expect(comp.learningPathsEnabled).toBeTruthy();
+ expect(getHealthStatusForCourseStub).toHaveBeenCalledTimes(3);
+ expect(comp.health).toEqual(health);
+ }));
+
+ it('should generate missing learning paths and load data', fakeAsync(() => {
+ const healthMissing = new LearningPathHealthDTO(HealthStatus.MISSING);
+ getHealthStatusForCourseStub.mockReturnValueOnce(of(new HttpResponse({ body: healthMissing }))).mockReturnValueOnce(of(new HttpResponse({ body: health })));
+ fixture.detectChanges();
+ comp.ngOnInit();
+ expect(comp.health).toEqual(healthMissing);
+ comp.generateMissing();
+ expect(generateMissingLearningPathsForCourseStub).toHaveBeenCalledOnce();
+ expect(generateMissingLearningPathsForCourseStub).toHaveBeenCalledWith(courseId);
+ expect(getHealthStatusForCourseStub).toHaveBeenCalledTimes(3);
+ expect(comp.health).toEqual(health);
}));
it('should set content to paging result on sort', fakeAsync(() => {
diff --git a/src/test/javascript/spec/service/learning-path.service.spec.ts b/src/test/javascript/spec/service/learning-path.service.spec.ts
index 6748f72a1fad..ca10b4b2ade4 100644
--- a/src/test/javascript/spec/service/learning-path.service.spec.ts
+++ b/src/test/javascript/spec/service/learning-path.service.spec.ts
@@ -34,6 +34,12 @@ describe('LearningPathService', () => {
expect(putStub).toHaveBeenCalledWith('api/courses/1/learning-paths/enable', null, { observe: 'response' });
});
+ it('should send request to the server to generate missing learning paths for course', () => {
+ learningPathService.generateMissingLearningPathsForCourse(1).subscribe();
+ expect(putStub).toHaveBeenCalledOnce();
+ expect(putStub).toHaveBeenCalledWith('api/courses/1/learning-paths/generate-missing', null, { observe: 'response' });
+ });
+
it('should send a request to the server to get learning path id of the current user in the course', () => {
learningPathService.getLearningPathId(1).subscribe();
expect(getStub).toHaveBeenCalledOnce();
@@ -51,4 +57,10 @@ describe('LearningPathService', () => {
expect(getStub).toHaveBeenCalledOnce();
expect(getStub).toHaveBeenCalledWith('api/learning-path/1/recommendation', { observe: 'response' });
});
+
+ it('should send a request to the server to get health status of learning paths for course', () => {
+ learningPathService.getHealthStatusForCourse(1).subscribe();
+ expect(getStub).toHaveBeenCalledOnce();
+ expect(getStub).toHaveBeenCalledWith('api/courses/1/learning-path-health', { observe: 'response' });
+ });
});
From ac46483ad9a2f5abbe7aa4deb46951e1d03bdfb5 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Sun, 30 Jul 2023 14:02:44 +0200
Subject: [PATCH 095/215] add LP generation on first request
---
.../artemis/service/LearningPathService.java | 5 +++--
.../web/rest/LearningPathResource.java | 13 ++++++++++-
.../lecture/LearningPathIntegrationTest.java | 22 +++++++++++++++++++
3 files changed, 37 insertions(+), 3 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index 9a2fc5ca4922..d9650a7fb2f3 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -75,11 +75,11 @@ public void generateLearningPaths(@NotNull Course course) {
* @param course course that defines the learning path
* @param user student for which the learning path is generated
*/
- public void generateLearningPathForUser(@NotNull Course course, @NotNull User user) {
+ public LearningPath generateLearningPathForUser(@NotNull Course course, @NotNull User user) {
var existingLearningPath = learningPathRepository.findByCourseIdAndUserId(course.getId(), user.getId());
// the learning path has not to be generated if it already exits
if (existingLearningPath.isPresent()) {
- return;
+ return existingLearningPath.get();
}
LearningPath lpToCreate = new LearningPath();
lpToCreate.setUser(user);
@@ -88,6 +88,7 @@ public void generateLearningPathForUser(@NotNull Course course, @NotNull User us
var persistedLearningPath = learningPathRepository.save(lpToCreate);
log.debug("Created LearningPath (id={}) for user (id={}) in course (id={})", persistedLearningPath.getId(), user.getId(), course.getId());
updateLearningPathProgress(persistedLearningPath);
+ return persistedLearningPath;
}
/**
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 37db0dff1934..526b3c9d850b 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -97,6 +97,7 @@ public ResponseEntity> getLea
/**
* GET /courses/:courseId/learning-path-id : Gets the id of the learning path.
+ * If the learning path has not been generated although the course has learning paths enabled, the corresponding learning path will be created.
*
* @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
@@ -110,8 +111,18 @@ public ResponseEntity getLearningPathId(@PathVariable Long courseId) {
if (!course.getLearningPathsEnabled()) {
throw new BadRequestException("Learning paths are not enabled for this course.");
}
+
+ // generate learning path if missing
User user = userRepository.getUser();
- LearningPath learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), user.getId());
+ final var learningPathOptional = learningPathRepository.findByCourseIdAndUserId(course.getId(), user.getId());
+ LearningPath learningPath;
+ if (learningPathOptional.isEmpty()) {
+ course = courseRepository.findWithEagerCompetenciesByIdElseThrow(courseId);
+ learningPath = learningPathService.generateLearningPathForUser(course, user);
+ }
+ else {
+ learningPath = learningPathOptional.get();
+ }
return ResponseEntity.ok(learningPath.getId());
}
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 011ce9c9bdc7..e9f9eef7ae3f 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -283,6 +283,28 @@ void testGetLearningPathsOnPageForCourseExactlyStudent() throws Exception {
assertThat(result.getResultsOnPage()).hasSize(1);
}
+ @Test
+ @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
+ void getLearningPathId() throws Exception {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ final var result = request.get("/api/courses/" + course.getId() + "/learning-path-id", HttpStatus.OK, Long.class);
+ assertThat(result).isEqualTo(learningPath.getId());
+ }
+
+ @Test
+ @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
+ void getLearningPathIdNotExisting() throws Exception {
+ course.setLearningPathsEnabled(true);
+ course = courseRepository.save(course);
+ var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ student = userRepository.findWithLearningPathsByIdElseThrow(student.getId());
+ learningPathRepository.deleteAll(student.getLearningPaths());
+ final var result = request.get("/api/courses/" + course.getId() + "/learning-path-id", HttpStatus.OK, Long.class);
+ assertThat(result).isNotNull();
+ }
+
private static Stream addCompetencyToLearningPathsOnCreateAndImportCompetencyTestProvider() {
final Function createCall = (reference) -> {
try {
From 1cd8687116e8e05e1483aca97daa80318b2aadd0 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Sun, 30 Jul 2023 21:43:20 +0200
Subject: [PATCH 096/215] integrate Tobias' suggestions
---
.../de/tum/in/www1/artemis/domain/Course.java | 1 -
.../domain/competency/LearningPath.java | 1 +
.../CompetencyProgressRepository.java | 2 +-
.../service/CompetencyProgressService.java | 1 +
.../artemis/service/LearningPathService.java | 16 ++++++++++--
.../web/rest/LearningPathResource.java | 13 +++++-----
.../competency-management.component.ts | 2 +-
.../learning-path-management.component.ts | 2 +-
.../learning-path-container.component.ts | 2 +-
src/main/webapp/i18n/de/competency.json | 4 +--
src/main/webapp/i18n/en/competency.json | 2 +-
.../competency/LearningPathUtilService.java | 8 +++---
.../artemis/course/CourseTestService.java | 4 ---
.../lecture/LearningPathIntegrationTest.java | 26 +++++++++----------
.../service/LearningPathServiceTest.java | 22 +++++++---------
15 files changed, 55 insertions(+), 51 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/Course.java b/src/main/java/de/tum/in/www1/artemis/domain/Course.java
index be0f1b9cbfc7..b70bd517e4b1 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/Course.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/Course.java
@@ -211,7 +211,6 @@ public class Course extends DomainObject {
@OneToMany(mappedBy = "course", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
@JsonIgnoreProperties("course")
- @OrderBy("id")
private Set learningPaths = new HashSet<>();
@OneToMany(mappedBy = "course", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/competency/LearningPath.java b/src/main/java/de/tum/in/www1/artemis/domain/competency/LearningPath.java
index c3897b5e7f46..e6df62ef4e36 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/competency/LearningPath.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/competency/LearningPath.java
@@ -19,6 +19,7 @@
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class LearningPath extends DomainObject {
+ // number in [0, 100] representing the progress in percentage
@Column(name = "progress")
private int progress;
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/CompetencyProgressRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/CompetencyProgressRepository.java
index 691a488d3f8c..6ec922248bb8 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/CompetencyProgressRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/CompetencyProgressRepository.java
@@ -56,7 +56,7 @@ public interface CompetencyProgressRepository extends JpaRepository findAllByCompetencyIdsAndUserId(@Param("competencyIds") Set competencyIds, @Param("userId") Long userId);
+ Set findAllByCompetencyIdsAndUserId(@Param("competencyIds") Set competencyIds, @Param("userId") long userId);
@Query("""
SELECT AVG(cp.confidence)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/CompetencyProgressService.java b/src/main/java/de/tum/in/www1/artemis/service/CompetencyProgressService.java
index 7277448eb189..d2e3f5466a34 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/CompetencyProgressService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/CompetencyProgressService.java
@@ -276,6 +276,7 @@ else if (learningObject instanceof Exercise exercise) {
* @return True if the user mastered the competency, false otherwise
*/
public static boolean isMastered(@NotNull CompetencyProgress competencyProgress) {
+ // weight taken from client
final double weight = 2.0 / 3.0;
final double mastery = (1 - weight) * competencyProgress.getProgress() + weight * competencyProgress.getConfidence();
return mastery >= competencyProgress.getCompetency().getMasteryThreshold();
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index 9a2fc5ca4922..8ecdbe94f3f5 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -27,6 +27,17 @@
import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
import de.tum.in.www1.artemis.web.rest.util.PageUtil;
+/**
+ * Service Implementation for managing Learning Paths.
+ *
+ * This includes
+ *
+ * the generation of learning paths in courses,
+ * performing pageable searches for learning paths,
+ * generation of the Ngx representation of learning paths,
+ * and the computation of recommended learning objects for a specific learning path.
+ *
+ */
@Service
public class LearningPathService {
@@ -401,13 +412,14 @@ public static String getDirectEdgeId(long competencyId) {
* @param learningPath the learning path for which the recommendation should be computed
* @return recommended learning object
*/
- public LearningObject getRecommendation(@NotNull LearningPath learningPath) {
- return learningPath.getCompetencies().stream()
+ public Optional getRecommendation(@NotNull LearningPath learningPath) {
+ LearningObject learningObject = learningPath.getCompetencies().stream()
.flatMap(competency -> Stream.concat(
competency.getLectureUnits().stream()
.filter(lectureUnit -> !lectureUnitRepository.findWithEagerCompletedUsersByIdElseThrow(lectureUnit.getId()).isCompletedFor(learningPath.getUser())),
competency.getExercises().stream()
.filter(exercise -> !exerciseRepository.findByIdWithStudentParticipationsElseThrow(exercise.getId()).isCompletedFor(learningPath.getUser()))))
.findFirst().orElse(null);
+ return Optional.ofNullable(learningObject);
}
}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 37db0dff1934..392a524051ad 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -8,7 +8,6 @@
import org.springframework.web.bind.annotation.*;
import de.tum.in.www1.artemis.domain.Course;
-import de.tum.in.www1.artemis.domain.LearningObject;
import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.domain.competency.LearningPath;
import de.tum.in.www1.artemis.domain.lecture.LectureUnit;
@@ -158,16 +157,16 @@ public ResponseEntity getRecommendation(@PathVari
if (!user.getId().equals(learningPath.getUser().getId())) {
throw new AccessForbiddenException("You are not allowed to access another users learning path.");
}
- LearningObject recommendation = learningPathService.getRecommendation(learningPath);
- if (recommendation == null) {
+ final var recommendation = learningPathService.getRecommendation(learningPath);
+ if (recommendation.isEmpty()) {
return ResponseEntity.ok(new LearningPathRecommendationDTO(-1, -1, LearningPathRecommendationDTO.RecommendationType.EMPTY));
}
- else if (recommendation instanceof LectureUnit lectureUnit) {
- return ResponseEntity
- .ok(new LearningPathRecommendationDTO(recommendation.getId(), lectureUnit.getLecture().getId(), LearningPathRecommendationDTO.RecommendationType.LECTURE_UNIT));
+ else if (recommendation.get() instanceof LectureUnit lectureUnit) {
+ return ResponseEntity.ok(new LearningPathRecommendationDTO(recommendation.get().getId(), lectureUnit.getLecture().getId(),
+ LearningPathRecommendationDTO.RecommendationType.LECTURE_UNIT));
}
else {
- return ResponseEntity.ok(new LearningPathRecommendationDTO(recommendation.getId(), -1, LearningPathRecommendationDTO.RecommendationType.EXERCISE));
+ return ResponseEntity.ok(new LearningPathRecommendationDTO(recommendation.get().getId(), -1, LearningPathRecommendationDTO.RecommendationType.EXERCISE));
}
}
}
diff --git a/src/main/webapp/app/course/competencies/competency-management/competency-management.component.ts b/src/main/webapp/app/course/competencies/competency-management/competency-management.component.ts
index eef1883389a7..c83c25c9e9b4 100644
--- a/src/main/webapp/app/course/competencies/competency-management/competency-management.component.ts
+++ b/src/main/webapp/app/course/competencies/competency-management/competency-management.component.ts
@@ -65,7 +65,7 @@ export class CompetencyManagementComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.showRelations = this.accountService.isAdmin(); // beta feature
this.activatedRoute.parent!.params.subscribe((params) => {
- this.courseId = +params['courseId'];
+ this.courseId = params['courseId'];
if (this.courseId) {
this.loadData();
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index 04fe47a1f7e3..2e6d585502ac 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -114,7 +114,7 @@ export class LearningPathManagementComponent implements OnInit {
this.content = { resultsOnPage: [], numberOfPages: 0 };
this.activatedRoute.parent!.params.subscribe((params) => {
- this.courseId = +params['courseId'];
+ this.courseId = params['courseId'];
if (this.courseId) {
this.loadData();
}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
index ea0756b434d1..c505e6fb9ff5 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
@@ -45,7 +45,7 @@ export class LearningPathContainerComponent implements OnInit {
ngOnInit() {
if (!this.courseId) {
this.activatedRoute.parent!.parent!.params.subscribe((params) => {
- this.courseId = +params['courseId'];
+ this.courseId = params['courseId'];
});
}
this.learningPathService.getLearningPathId(this.courseId).subscribe((learningPathIdResponse) => {
diff --git a/src/main/webapp/i18n/de/competency.json b/src/main/webapp/i18n/de/competency.json
index 74db1b570c43..061eb65dc34e 100644
--- a/src/main/webapp/i18n/de/competency.json
+++ b/src/main/webapp/i18n/de/competency.json
@@ -155,7 +155,7 @@
"title": "Lernpfadmanagement",
"isDisabled": "Lernpfade sind für diesen Kurs nicht aktiviert.",
"enable": "Lernpfade aktivieren",
- "enableHint": "Die Erstellung von Lernpfaden für alle Studierende kann einige Minuten dauern.",
+ "enableHint": "Die Erstellung von Lernpfaden für alle Studierende kann einen Moment dauern.",
"search": "Suche nach Lernpfaden:",
"table": {
"name": "Name",
@@ -165,7 +165,7 @@
}
},
"sideBar": {
- "hide": "Lernpfad ausbleden",
+ "hide": "Lernpfad ausblenden",
"show": "Lernpfad einblenden",
"header": "Lernpfad"
},
diff --git a/src/main/webapp/i18n/en/competency.json b/src/main/webapp/i18n/en/competency.json
index 3b7cb1d04568..caf0b4adaf4f 100644
--- a/src/main/webapp/i18n/en/competency.json
+++ b/src/main/webapp/i18n/en/competency.json
@@ -154,7 +154,7 @@
"title": "Learning Path Management",
"isDisabled": "Learning Paths are currently disabled for this course.",
"enable": "Enable Learning Paths",
- "enableHint": "The creation of Learning Paths for every student may take a few minutes.",
+ "enableHint": "The creation of Learning Paths for every student may take a moment.",
"search": "Search for Learning Path:",
"table": {
"name": "Name",
diff --git a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
index 9c116978e58e..49d087b295a2 100644
--- a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
+++ b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
@@ -20,16 +20,16 @@
public class LearningPathUtilService {
@Autowired
- CourseRepository courseRepository;
+ private CourseRepository courseRepository;
@Autowired
- LearningPathService learningPathService;
+ private LearningPathService learningPathService;
@Autowired
- LearningPathRepository learningPathRepository;
+ private LearningPathRepository learningPathRepository;
@Autowired
- CompetencyRepository competencyRepository;
+ private CompetencyRepository competencyRepository;
/**
* Enable and generate learning paths for course.
diff --git a/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java b/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java
index e88e16c8d8f0..cb5f24895039 100644
--- a/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java
+++ b/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java
@@ -41,7 +41,6 @@
import de.tum.in.www1.artemis.assessment.ComplaintUtilService;
import de.tum.in.www1.artemis.competency.CompetencyUtilService;
-import de.tum.in.www1.artemis.competency.LearningPathUtilService;
import de.tum.in.www1.artemis.config.Constants;
import de.tum.in.www1.artemis.domain.*;
import de.tum.in.www1.artemis.domain.competency.Competency;
@@ -211,9 +210,6 @@ public class CourseTestService {
@Autowired
private TeamUtilService teamUtilService;
- @Autowired
- private LearningPathUtilService learningPathUtilService;
-
private static final int numberOfStudents = 8;
private static final int numberOfTutors = 5;
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 011ce9c9bdc7..eb3a6d28457c 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -57,40 +57,40 @@ class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbuck
private CourseUtilService courseUtilService;
@Autowired
- CompetencyUtilService competencyUtilService;
+ private CompetencyUtilService competencyUtilService;
@Autowired
- PageableSearchUtilService pageableSearchUtilService;
+ private PageableSearchUtilService pageableSearchUtilService;
@Autowired
- LearningPathRepository learningPathRepository;
+ private LearningPathRepository learningPathRepository;
@Autowired
- ExerciseUtilService exerciseUtilService;
+ private ExerciseUtilService exerciseUtilService;
@Autowired
- TextExerciseUtilService textExerciseUtilService;
+ private TextExerciseUtilService textExerciseUtilService;
@Autowired
- ParticipationUtilService participationUtilService;
+ private ParticipationUtilService participationUtilService;
@Autowired
- LectureRepository lectureRepository;
+ private LectureRepository lectureRepository;
@Autowired
- LectureUtilService lectureUtilService;
+ private LectureUtilService lectureUtilService;
@Autowired
- GradingCriterionRepository gradingCriterionRepository;
+ private GradingCriterionRepository gradingCriterionRepository;
@Autowired
- LectureUnitService lectureUnitService;
+ private LectureUnitService lectureUnitService;
@Autowired
- CompetencyProgressService competencyProgressService;
+ private CompetencyProgressService competencyProgressService;
@Autowired
- LearningPathUtilService learningPathUtilService;
+ private LearningPathUtilService learningPathUtilService;
private Course course;
@@ -100,7 +100,7 @@ class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbuck
private TextUnit textUnit;
- private final int NUMBER_OF_STUDENTS = 5;
+ private static final int NUMBER_OF_STUDENTS = 5;
private static final String STUDENT_OF_COURSE = TEST_PREFIX + "student1";
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 1588ae37b389..2c73f212a94e 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -25,7 +25,6 @@
import de.tum.in.www1.artemis.domain.lecture.LectureUnit;
import de.tum.in.www1.artemis.exercise.programmingexercise.ProgrammingExerciseUtilService;
import de.tum.in.www1.artemis.lecture.LectureUtilService;
-import de.tum.in.www1.artemis.repository.CompetencyRepository;
import de.tum.in.www1.artemis.repository.LearningPathRepository;
import de.tum.in.www1.artemis.security.SecurityUtils;
import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
@@ -33,28 +32,25 @@
class LearningPathServiceTest extends AbstractSpringIntegrationBambooBitbucketJiraTest {
@Autowired
- LearningPathService learningPathService;
+ private LearningPathService learningPathService;
@Autowired
- LearningPathUtilService learningPathUtilService;
+ private LearningPathUtilService learningPathUtilService;
@Autowired
- CourseUtilService courseUtilService;
+ private CourseUtilService courseUtilService;
@Autowired
- CompetencyUtilService competencyUtilService;
+ private CompetencyUtilService competencyUtilService;
@Autowired
- LearningPathRepository learningPathRepository;
+ private LearningPathRepository learningPathRepository;
@Autowired
- LectureUtilService lectureUtilService;
+ private LectureUtilService lectureUtilService;
@Autowired
- ProgrammingExerciseUtilService programmingExerciseUtilService;
-
- @Autowired
- CompetencyRepository competencyRepository;
+ private ProgrammingExerciseUtilService programmingExerciseUtilService;
private Course course;
@@ -284,7 +280,7 @@ void testGetRecommendationEmpty() {
competencyUtilService.createCompetency(course);
LearningPath learningPath = learningPathUtilService.createLearningPathInCourse(course);
learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(learningPath.getId());
- assertThat(learningPathService.getRecommendation(learningPath)).isNull();
+ assertThat(learningPathService.getRecommendation(learningPath)).isEmpty();
}
@Test
@@ -294,7 +290,7 @@ void testGetRecommendationNotEmpty() {
competencyUtilService.linkLectureUnitToCompetency(competency, lectureUnit);
LearningPath learningPath = learningPathUtilService.createLearningPathInCourse(course);
learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(learningPath.getId());
- assertThat(learningPathService.getRecommendation(learningPath)).isNotNull();
+ assertThat(learningPathService.getRecommendation(learningPath)).isPresent();
}
}
}
From b52e296c44af447b38ef25220bf99bb367cbf9fb Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Sun, 30 Jul 2023 22:05:32 +0200
Subject: [PATCH 097/215] resolve merge conflict
---
.../de/tum/in/www1/artemis/service/LearningPathServiceTest.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index a2d38e6efc20..a28843d342db 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -27,6 +27,7 @@
import de.tum.in.www1.artemis.domain.lecture.LectureUnit;
import de.tum.in.www1.artemis.exercise.programmingexercise.ProgrammingExerciseUtilService;
import de.tum.in.www1.artemis.lecture.LectureUtilService;
+import de.tum.in.www1.artemis.repository.CourseRepository;
import de.tum.in.www1.artemis.repository.LearningPathRepository;
import de.tum.in.www1.artemis.security.SecurityUtils;
import de.tum.in.www1.artemis.user.UserUtilService;
From 52049e4a4ef57a221334c9711eb18f7325c600a8 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Sun, 30 Jul 2023 22:13:47 +0200
Subject: [PATCH 098/215] integrate Johannes' suggestions
---
.../in/www1/artemis/repository/LearningPathRepository.java | 2 +-
.../de/tum/in/www1/artemis/service/LearningPathService.java | 6 +++---
src/main/webapp/i18n/de/competency.json | 4 ++--
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
index 4a9cca28bcd1..81d8e9929523 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
@@ -78,5 +78,5 @@ SELECT COUNT (learningPath)
FROM LearningPath learningPath
WHERE learningPath.course.id = :#{#courseId}
""")
- Long countByCourseId(@Param("courseId") long courseId);
+ long countByCourseId(@Param("courseId") long courseId);
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index 47baec42ffce..d2b1aadb221b 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -435,10 +435,10 @@ public LearningPathHealthDTO getHealthStatusForCourse(Course course) {
return new LearningPathHealthDTO(LearningPathHealthDTO.HealthStatus.DISABLED);
}
- var numberOfStudents = userRepository.countUserInGroup(course.getStudentGroupName());
- var numberOfLearningPaths = learningPathRepository.countByCourseId(course.getId());
+ long numberOfStudents = userRepository.countUserInGroup(course.getStudentGroupName());
+ long numberOfLearningPaths = learningPathRepository.countByCourseId(course.getId());
- if (numberOfStudents.equals(numberOfLearningPaths)) {
+ if (numberOfStudents == numberOfLearningPaths) {
return new LearningPathHealthDTO(LearningPathHealthDTO.HealthStatus.OK);
}
else {
diff --git a/src/main/webapp/i18n/de/competency.json b/src/main/webapp/i18n/de/competency.json
index 78e22fc4188e..c71470bfdea6 100644
--- a/src/main/webapp/i18n/de/competency.json
+++ b/src/main/webapp/i18n/de/competency.json
@@ -172,8 +172,8 @@
},
"progressNav": {
"header": "Lernpfad",
- "refresh": "Refresh",
- "center": "Center view"
+ "refresh": "Aktualisieren",
+ "center": "Zentrieren"
}
},
"sideBar": {
From b246cfc45320884f81476866a2e96b014354a554 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 31 Jul 2023 16:52:01 +0200
Subject: [PATCH 099/215] Update LearningPathServiceTest.java
---
.../tum/in/www1/artemis/service/LearningPathServiceTest.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 2c73f212a94e..c7f600efd0a9 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -21,7 +21,6 @@
import de.tum.in.www1.artemis.domain.competency.Competency;
import de.tum.in.www1.artemis.domain.competency.CompetencyRelation;
import de.tum.in.www1.artemis.domain.competency.LearningPath;
-import de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage;
import de.tum.in.www1.artemis.domain.lecture.LectureUnit;
import de.tum.in.www1.artemis.exercise.programmingexercise.ProgrammingExerciseUtilService;
import de.tum.in.www1.artemis.lecture.LectureUtilService;
@@ -105,7 +104,7 @@ void testCompetencyWithLectureUnitAndExercise() {
var competency = competencyUtilService.createCompetency(course);
final var lectureUnit = lectureUtilService.createTextUnit();
competencyUtilService.linkLectureUnitToCompetency(competency, lectureUnit);
- final var exercise = programmingExerciseUtilService.addProgrammingExerciseToCourse(course, false, false, ProgrammingLanguage.JAVA, "Some Title", "someshortname");
+ final var exercise = programmingExerciseUtilService.addProgrammingExerciseToCourse(course, false);
competencyUtilService.linkExerciseToCompetency(competency, exercise);
final var startNodeId = LearningPathService.getCompetencyStartNodeId(competency.getId());
final var endNodeId = LearningPathService.getCompetencyEndNodeId(competency.getId());
From bfc0eee0a1d8289113a23c1951610039c1242f94 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 31 Jul 2023 18:51:08 +0200
Subject: [PATCH 100/215] fix enable learning paths button tooltip
---
.../learning-path-management.component.html | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
index 4d85780caa13..f1634b94c66c 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
@@ -8,7 +8,7 @@
Learning Path Management
Disabled
-
+
Learning Pa
class="btn btn-primary"
(click)="enableLearningPaths()"
jhiTranslate="artemisApp.learningPath.manageLearningPaths.enable"
+ [ngbTooltip]="'artemisApp.learningPath.manageLearningPaths.enableHint' | artemisTranslate"
>
Enable
From 0afac6f5ec08ad1acc30f2e520a25c466e854f85 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 31 Jul 2023 18:57:31 +0200
Subject: [PATCH 101/215] fix position of learning paths tab in course
managment
---
.../course-management-tab-bar.component.html | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html
index 7d9f4770ea43..22d9121ccc06 100644
--- a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html
+++ b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html
@@ -27,10 +27,6 @@
Messages
-
-
- Learning Paths
-
Competencies
+
+
+ Learning Paths
+
Assessment
From 5e81ccefc03759f6c6d08a9206a61d8da3ddd15b Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 31 Jul 2023 19:00:40 +0200
Subject: [PATCH 102/215] fix position of learning path tab in course overview
---
.../webapp/app/overview/course-overview.component.html | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/main/webapp/app/overview/course-overview.component.html b/src/main/webapp/app/overview/course-overview.component.html
index 2d4ae93b811d..b1bbce889f7e 100644
--- a/src/main/webapp/app/overview/course-overview.component.html
+++ b/src/main/webapp/app/overview/course-overview.component.html
@@ -15,10 +15,6 @@
Lectures
-
-
- Learning Path
-
Statistics
@@ -55,6 +51,10 @@
Competencies
+
+
+ Learning Path
+
From 40b7b30fe0730a871258415339bea63aa15376b5 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 31 Jul 2023 19:05:37 +0200
Subject: [PATCH 103/215] add whitespace below warning card
---
.../learning-path-management.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
index f1634b94c66c..c7360be50569 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
@@ -22,7 +22,7 @@ Learning Pa
-
+
{{ 'artemisApp.learningPath.manageLearningPaths.health.missing.title' | artemisTranslate }}
{{ 'artemisApp.learningPath.manageLearningPaths.health.missing.body' | artemisTranslate }}
From 469bf356032b72ea198dc5f93a2d5e5228837556 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 31 Jul 2023 19:07:00 +0200
Subject: [PATCH 104/215] fix translation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Johannes Stöhr <38322605+JohannesStoehr@users.noreply.github.com>
---
src/main/webapp/i18n/en/competency.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/webapp/i18n/en/competency.json b/src/main/webapp/i18n/en/competency.json
index c3be27fb29a6..5c08d79245a2 100644
--- a/src/main/webapp/i18n/en/competency.json
+++ b/src/main/webapp/i18n/en/competency.json
@@ -155,7 +155,7 @@
"health": {
"missing": {
"title": "Missing Learning Paths",
- "body": "Some student's have not generated their Learning Paths yet. This is not critical. Their Learning Paths will be created once they request their learning path for the first time.",
+ "body": "Some students have not generated their learning paths yet. This is not critical. Their learning paths will be created once they request their learning path for the first time.",
"action": "Generate",
"hint": "Generate missing Learning Paths"
}
From 74509f839e0ec123839e39edb51b5f980b46510f Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 31 Jul 2023 19:23:09 +0200
Subject: [PATCH 105/215] fix health status stuck
---
.../in/www1/artemis/repository/LearningPathRepository.java | 4 ++--
.../de/tum/in/www1/artemis/service/LearningPathService.java | 2 +-
.../learning-path-management.component.html | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
index 81d8e9929523..9e18387ce318 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
@@ -76,7 +76,7 @@ default LearningPath findWithEagerCompetenciesAndLearningObjectsAndCompletedUser
@Query("""
SELECT COUNT (learningPath)
FROM LearningPath learningPath
- WHERE learningPath.course.id = :#{#courseId}
+ WHERE learningPath.course.id = :courseId AND learningPath.user.isDeleted = false AND learningPath.course.studentGroupName MEMBER OF learningPath.user.groups
""")
- long countByCourseId(@Param("courseId") long courseId);
+ long countLearningPathsOfEnrolledStudentsInCourse(@Param("courseId") long courseId);
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index d2b1aadb221b..84ae45467d4c 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -436,7 +436,7 @@ public LearningPathHealthDTO getHealthStatusForCourse(Course course) {
}
long numberOfStudents = userRepository.countUserInGroup(course.getStudentGroupName());
- long numberOfLearningPaths = learningPathRepository.countByCourseId(course.getId());
+ long numberOfLearningPaths = learningPathRepository.countLearningPathsOfEnrolledStudentsInCourse(course.getId());
if (numberOfStudents == numberOfLearningPaths) {
return new LearningPathHealthDTO(LearningPathHealthDTO.HealthStatus.OK);
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
index c7360be50569..9447ba7680ea 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
@@ -22,7 +22,7 @@
Learning Pa
-
+
{{ 'artemisApp.learningPath.manageLearningPaths.health.missing.title' | artemisTranslate }}
{{ 'artemisApp.learningPath.manageLearningPaths.health.missing.body' | artemisTranslate }}
From 77efd3ac847f67abe5bc8365f86c5b1067ff21b7 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 31 Jul 2023 19:46:54 +0200
Subject: [PATCH 106/215] generate learning path when student is added by
instructor
---
.../in/www1/artemis/repository/CourseRepository.java | 8 ++++++++
.../tum/in/www1/artemis/web/rest/CourseResource.java | 10 +++++++++-
.../tum/in/www1/artemis/course/CourseTestService.java | 4 +++-
3 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
index 9c2dd9fc6b7d..5b8e5f1422f9 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/CourseRepository.java
@@ -131,6 +131,9 @@ SELECT CASE WHEN (count(c) > 0) THEN true ELSE false END
@EntityGraph(type = LOAD, attributePaths = { "competencies", "prerequisites" })
Optional
findWithEagerCompetenciesById(long courseId);
+ @EntityGraph(type = LOAD, attributePaths = { "learningPaths" })
+ Optional findWithEagerLearningPathsById(long courseId);
+
@EntityGraph(type = LOAD, attributePaths = { "competencies", "learningPaths", "learningPaths.competencies" })
Optional findWithEagerLearningPathsAndCompetenciesById(long courseId);
@@ -379,6 +382,11 @@ default Course findWithEagerCompetenciesByIdElseThrow(long courseId) {
return findWithEagerCompetenciesById(courseId).orElseThrow(() -> new EntityNotFoundException("Course", courseId));
}
+ @NotNull
+ default Course findWithEagerLearningPathsByIdElseThrow(long courseId) {
+ return findWithEagerLearningPathsById(courseId).orElseThrow(() -> new EntityNotFoundException("Course", courseId));
+ }
+
@NotNull
default Course findWithEagerLearningPathsAndCompetenciesByIdElseThrow(long courseId) {
return findWithEagerLearningPathsAndCompetenciesById(courseId).orElseThrow(() -> new EntityNotFoundException("Course", courseId));
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
index 905b332ff37b..feaf469135a8 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
@@ -113,12 +113,15 @@ public class CourseResource {
private final ChannelService channelService;
+ private final LearningPathService learningPathService;
+
public CourseResource(UserRepository userRepository, CourseService courseService, CourseRepository courseRepository, ExerciseService exerciseService,
OAuth2JWKSService oAuth2JWKSService, OnlineCourseConfigurationService onlineCourseConfigurationService, AuthorizationCheckService authCheckService,
TutorParticipationRepository tutorParticipationRepository, SubmissionService submissionService, Optional optionalVcsUserManagementService,
AssessmentDashboardService assessmentDashboardService, ExerciseRepository exerciseRepository, Optional optionalCiUserManagementService,
FileService fileService, TutorialGroupsConfigurationService tutorialGroupsConfigurationService, GradingScaleService gradingScaleService,
- CourseScoreCalculationService courseScoreCalculationService, GradingScaleRepository gradingScaleRepository, ChannelService channelService) {
+ CourseScoreCalculationService courseScoreCalculationService, GradingScaleRepository gradingScaleRepository, ChannelService channelService,
+ LearningPathService learningPathService) {
this.courseService = courseService;
this.courseRepository = courseRepository;
this.exerciseService = exerciseService;
@@ -138,6 +141,7 @@ public CourseResource(UserRepository userRepository, CourseService courseService
this.courseScoreCalculationService = courseScoreCalculationService;
this.gradingScaleRepository = gradingScaleRepository;
this.channelService = channelService;
+ this.learningPathService = learningPathService;
}
/**
@@ -1035,6 +1039,10 @@ public ResponseEntity addUserToCourseGroup(String userLogin, User instruct
}
courseService.addUserToGroup(userToAddToGroup.get(), group, role);
channelService.registerUserToDefaultChannels(userToAddToGroup.get(), group, role);
+ if (role == Role.STUDENT && course.getLearningPathsEnabled()) {
+ Course courseWithCompetencies = courseRepository.findWithEagerCompetenciesByIdElseThrow(course.getId());
+ learningPathService.generateLearningPathForUser(courseWithCompetencies, userToAddToGroup.get());
+ }
return ResponseEntity.ok().body(null);
}
else {
diff --git a/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java b/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java
index cb5f24895039..6f85679b4ae3 100644
--- a/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java
+++ b/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java
@@ -1733,9 +1733,11 @@ public void testAddStudentOrTutorOrEditorOrInstructorToCourse() throws Exception
adjustUserGroupsToCustomGroups();
Course course = CourseFactory.generateCourse(null, null, null, new HashSet<>(), userPrefix + "student", userPrefix + "tutor", userPrefix + "editor",
userPrefix + "instructor");
+ course.setLearningPathsEnabled(true);
course = courseRepo.save(course);
testAddStudentOrTutorOrEditorOrInstructorToCourse(course, HttpStatus.OK);
-
+ course = courseRepo.findWithEagerLearningPathsByIdElseThrow(course.getId());
+ assertThat(course.getLearningPaths()).isNotEmpty();
// TODO check that the roles have changed accordingly
}
From 8f76721683a1c26210f75b4def4737191c8bdb19 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 31 Jul 2023 20:15:09 +0200
Subject: [PATCH 107/215] Update CourseService.java
---
.../java/de/tum/in/www1/artemis/service/CourseService.java | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/CourseService.java b/src/main/java/de/tum/in/www1/artemis/service/CourseService.java
index dd3242d2df14..f26c1b5daf26 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/CourseService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/CourseService.java
@@ -496,6 +496,9 @@ public void enrollUserForCourseOrThrow(User user, Course course) {
*/
public List registerUsersForCourseGroup(Long courseId, List studentDTOs, String courseGroup) {
var course = courseRepository.findByIdElseThrow(courseId);
+ if (course.getLearningPathsEnabled()) {
+ course = courseRepository.findWithEagerCompetenciesByIdElseThrow(course.getId());
+ }
String courseGroupName = course.defineCourseGroupName(courseGroup);
Role courseGroupRole = Role.fromString(courseGroup);
List notFoundStudentsDTOs = new ArrayList<>();
@@ -507,6 +510,9 @@ public List registerUsersForCourseGroup(Long courseId, List
Date: Tue, 1 Aug 2023 00:59:52 +0200
Subject: [PATCH 108/215] fix breadcrumbs
---
src/main/webapp/app/shared/layouts/navbar/navbar.component.ts | 2 ++
src/main/webapp/i18n/de/competency.json | 4 ++++
src/main/webapp/i18n/en/competency.json | 4 ++++
3 files changed, 10 insertions(+)
diff --git a/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts b/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts
index 417e4a0e648a..b0bb13855a86 100644
--- a/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts
+++ b/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts
@@ -337,6 +337,8 @@ export class NavbarComponent implements OnInit, OnDestroy {
lectures: 'artemisApp.courseOverview.menu.lectures',
competencies: 'artemisApp.courseOverview.menu.competencies',
learning_path: 'artemisApp.courseOverview.menu.learningPath',
+ lecture_unit: 'artemisApp.learningPath.breadcrumbs.lectureUnit',
+ exercise: 'artemisApp.learningPath.breadcrumbs.exercise',
statistics: 'artemisApp.courseOverview.menu.statistics',
discussion: 'artemisApp.metis.communication.label',
messages: 'artemisApp.conversationsLayout.breadCrumbLabel',
diff --git a/src/main/webapp/i18n/de/competency.json b/src/main/webapp/i18n/de/competency.json
index 061eb65dc34e..201f8fed2de4 100644
--- a/src/main/webapp/i18n/de/competency.json
+++ b/src/main/webapp/i18n/de/competency.json
@@ -151,6 +151,10 @@
},
"learningPath": {
"learningPathButton": "Lernpfade",
+ "breadcrumbs": {
+ "lectureUnit": "Vorlesungseinheit",
+ "exercise": "Aufgabe"
+ },
"manageLearningPaths": {
"title": "Lernpfadmanagement",
"isDisabled": "Lernpfade sind für diesen Kurs nicht aktiviert.",
diff --git a/src/main/webapp/i18n/en/competency.json b/src/main/webapp/i18n/en/competency.json
index caf0b4adaf4f..22a39cf4cc52 100644
--- a/src/main/webapp/i18n/en/competency.json
+++ b/src/main/webapp/i18n/en/competency.json
@@ -150,6 +150,10 @@
},
"learningPath": {
"learningPathButton": "Learning Paths",
+ "breadcrumbs": {
+ "lectureUnit": "Lecture Unit",
+ "exercise": "Exercise"
+ },
"manageLearningPaths": {
"title": "Learning Path Management",
"isDisabled": "Learning Paths are currently disabled for this course.",
From 02a0c6ea265cd8b127c98c0ba102d0c7bf5711d1 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Tue, 1 Aug 2023 11:20:35 +0200
Subject: [PATCH 109/215] refactor participation history
---
.../learning-paths/learning-paths.module.ts | 1 +
.../learning-path-container.component.html | 2 +-
.../learning-path-container.component.ts | 28 ++--
.../learning-path-history-storage.service.ts | 85 ++++++++++++
src/main/webapp/i18n/de/competency.json | 6 +-
src/main/webapp/i18n/en/competency.json | 10 +-
.../learning-path-container.component.spec.ts | 29 +++-
...rning-path-history-storage.service.spec.ts | 127 ++++++++++++++++++
8 files changed, 265 insertions(+), 23 deletions(-)
create mode 100644 src/main/webapp/app/course/learning-paths/participate/learning-path-history-storage.service.ts
create mode 100644 src/test/javascript/spec/service/learning-path-history-storage.service.spec.ts
diff --git a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
index 7f03e4c6e2f0..a4e62c655efe 100644
--- a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
@@ -13,6 +13,7 @@ import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learni
import { NgxGraphModule } from '@swimlane/ngx-graph';
import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
+import { LearningPathHistoryStorageService } from 'app/course/learning-paths/participate/learning-path-history-storage.service';
const routes: Routes = [
{
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
index ee54c1faa122..40389ae34f04 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
@@ -11,7 +11,7 @@
-
+
Previous
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
index ca75546a39dd..292907f0c495 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
@@ -13,6 +13,7 @@ import { AlertService } from 'app/core/util/alert.service';
import { LearningPathLectureUnitViewComponent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
import { CourseExerciseDetailsComponent } from 'app/overview/exercise-details/course-exercise-details.component';
import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
+import { ExerciseEntry, LearningPathHistoryStorageService, LectureUnitEntry } from 'app/course/learning-paths/participate/learning-path-history-storage.service';
@Component({
selector: 'jhi-learning-path-container',
@@ -29,7 +30,6 @@ export class LearningPathContainerComponent implements OnInit {
lecture: Lecture | undefined;
lectureUnit: LectureUnit | undefined;
exercise: Exercise | undefined;
- history: [number, number][] = [];
// icons
faChevronLeft = faChevronLeft;
@@ -42,6 +42,7 @@ export class LearningPathContainerComponent implements OnInit {
private learningPathService: LearningPathService,
private lectureService: LectureService,
private exerciseService: ExerciseService,
+ private learningPathHistoryStorageService: LearningPathHistoryStorageService,
) {}
ngOnInit() {
@@ -57,9 +58,9 @@ export class LearningPathContainerComponent implements OnInit {
onNextTask() {
if (this.lectureUnit?.id) {
- this.history.push([this.lectureUnit.id, this.lectureId!]);
+ this.learningPathHistoryStorageService.storeLectureUnit(this.learningPathId, this.lectureId!, this.lectureUnit.id);
} else if (this.exercise?.id) {
- this.history.push([this.exercise.id, -1]);
+ this.learningPathHistoryStorageService.storeExercise(this.learningPathId, this.exercise.id);
}
this.undefineAll();
this.learningPathService.getRecommendation(this.learningPathId).subscribe((recommendationResponse) => {
@@ -82,16 +83,15 @@ export class LearningPathContainerComponent implements OnInit {
onPrevTask() {
this.undefineAll();
- const task = this.history.pop();
- if (!task) {
- return;
- } else {
- this.learningObjectId = task[0];
- this.lectureId = task[1];
- if (task[1] == -1) {
- this.loadExercise();
- } else {
+ if (this.learningPathHistoryStorageService.hasPrevious(this.learningPathId)) {
+ const entry = this.learningPathHistoryStorageService.getPrevious(this.learningPathId);
+ if (entry instanceof LectureUnitEntry) {
+ this.learningObjectId = entry.lectureUnitId;
+ this.lectureId = entry.lectureId;
this.loadLectureUnit();
+ } else if (entry instanceof ExerciseEntry) {
+ this.learningObjectId = entry.exerciseId;
+ this.loadExercise();
}
}
}
@@ -145,4 +145,8 @@ export class LearningPathContainerComponent implements OnInit {
instance.exerciseId = this.learningObjectId;
}
}
+
+ hasPrevious() {
+ return this.learningPathHistoryStorageService.hasPrevious(this.learningPathId);
+ }
}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-history-storage.service.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-history-storage.service.ts
new file mode 100644
index 000000000000..cc3baab7cc6c
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-history-storage.service.ts
@@ -0,0 +1,85 @@
+import { Injectable } from '@angular/core';
+
+/**
+ * This service is used to store the histories of learning path participation for the currently logged-in user.
+ */
+@Injectable({ providedIn: 'root' })
+export class LearningPathHistoryStorageService {
+ private readonly learningPathHistories: Map = new Map();
+
+ /**
+ * Stores the lecture unit in the learning path's history.
+ *
+ * @param learningPathId the id of the learning path to which the new entry should be added
+ * @param lectureId the id of the lecture, the lecture unit belongs to
+ * @param lectureUnitId the id of the lecture unit
+ */
+ storeLectureUnit(learningPathId: number, lectureId: number, lectureUnitId: number) {
+ this.store(learningPathId, new LectureUnitEntry(lectureId, lectureUnitId));
+ }
+
+ /**
+ * Stores the exercise in the learning path's history.
+ *
+ * @param learningPathId the id of the learning path to which the new entry should be added
+ * @param exerciseId the id of the exercise
+ */
+ storeExercise(learningPathId: number, exerciseId: number) {
+ this.store(learningPathId, new ExerciseEntry(exerciseId));
+ }
+ private store(learningPathId: number, entry: HistoryEntry) {
+ if (!entry) {
+ return;
+ }
+ if (!this.learningPathHistories.has(learningPathId)) {
+ this.learningPathHistories.set(learningPathId, []);
+ }
+ this.learningPathHistories.get(learningPathId)!.push(entry);
+ }
+
+ /**
+ * Returns if the learning path's history stores at least one entry.
+ *
+ * @param learningPathId the id of the learning path for which the history should be checked
+ */
+ hasPrevious(learningPathId: number): boolean {
+ if (this.learningPathHistories.has(learningPathId)) {
+ return this.learningPathHistories.get(learningPathId)!.length !== 0;
+ }
+ return false;
+ }
+
+ /**
+ * Gets and removes the latest stored entry from the learning path's history.
+ *
+ * @param learningPathId
+ */
+ getPrevious(learningPathId: number) {
+ if (!this.hasPrevious(learningPathId)) {
+ return null;
+ }
+ return this.learningPathHistories.get(learningPathId)!.pop();
+ }
+}
+
+export abstract class HistoryEntry {}
+
+export class LectureUnitEntry extends HistoryEntry {
+ lectureUnitId: number;
+ lectureId: number;
+
+ constructor(lectureId: number, lectureUnitId: number) {
+ super();
+ this.lectureId = lectureId;
+ this.lectureUnitId = lectureUnitId;
+ }
+}
+
+export class ExerciseEntry extends HistoryEntry {
+ readonly exerciseId: number;
+
+ constructor(exerciseId: number) {
+ super();
+ this.exerciseId = exerciseId;
+ }
+}
diff --git a/src/main/webapp/i18n/de/competency.json b/src/main/webapp/i18n/de/competency.json
index 201f8fed2de4..cbffd183dd98 100644
--- a/src/main/webapp/i18n/de/competency.json
+++ b/src/main/webapp/i18n/de/competency.json
@@ -160,7 +160,7 @@
"isDisabled": "Lernpfade sind für diesen Kurs nicht aktiviert.",
"enable": "Lernpfade aktivieren",
"enableHint": "Die Erstellung von Lernpfaden für alle Studierende kann einen Moment dauern.",
- "search": "Suche nach Lernpfaden:",
+ "search": "Suche:",
"table": {
"name": "Name",
"login": "Login",
@@ -176,7 +176,9 @@
"participate": {
"noTaskSelected": "Aktuell hast du keine Vorlesungseinheit oder Aufgabe ausgewählt.",
"prev": "Zurück",
- "next": "Nächste"
+ "prevHint": "Zurück zur letzen Vorlesungseinheit oder Aufgabe",
+ "next": "Weiter",
+ "nextHint": "Weiter zur nächten Empfehlung"
}
}
}
diff --git a/src/main/webapp/i18n/en/competency.json b/src/main/webapp/i18n/en/competency.json
index 22a39cf4cc52..d8f323f68c5a 100644
--- a/src/main/webapp/i18n/en/competency.json
+++ b/src/main/webapp/i18n/en/competency.json
@@ -156,10 +156,10 @@
},
"manageLearningPaths": {
"title": "Learning Path Management",
- "isDisabled": "Learning Paths are currently disabled for this course.",
+ "isDisabled": "Learning paths are currently disabled for this course.",
"enable": "Enable Learning Paths",
- "enableHint": "The creation of Learning Paths for every student may take a moment.",
- "search": "Search for Learning Path:",
+ "enableHint": "The creation of learning paths for every student may take a moment.",
+ "search": "Search:",
"table": {
"name": "Name",
"login": "Login",
@@ -175,7 +175,9 @@
"participate": {
"noTaskSelected": "Currently you have no lecture unit or exercise selected.",
"prev": "Previous",
- "next": "Next"
+ "prevHint": "Return to your previous task",
+ "next": "Next",
+ "nextHint": "Go to next recommended task"
}
}
}
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
index bc952252b548..74c8cc280633 100644
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
@@ -18,6 +18,7 @@ import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model';
import { TextExercise } from 'app/entities/text-exercise.model';
import { LearningPathLectureUnitViewComponent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
import { CourseExerciseDetailsComponent } from 'app/overview/exercise-details/course-exercise-details.component';
+import { ExerciseEntry, LearningPathHistoryStorageService, LectureUnitEntry } from 'app/course/learning-paths/participate/learning-path-history-storage.service';
describe('LearningPathContainerComponent', () => {
let fixture: ComponentFixture;
@@ -33,6 +34,12 @@ describe('LearningPathContainerComponent', () => {
let exerciseService: ExerciseService;
let exercise: Exercise;
let getExerciseDetailsStub: jest.SpyInstance;
+ let historyService: LearningPathHistoryStorageService;
+ let storeLectureUnitStub: jest.SpyInstance;
+ let storeExerciseStub: jest.SpyInstance;
+ let hasPreviousStub: jest.SpyInstance;
+ let getPreviousStub: jest.SpyInstance;
+
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ArtemisTestModule, MockComponent(LearningPathGraphSidebarComponent), MockModule(RouterModule)],
@@ -73,6 +80,12 @@ describe('LearningPathContainerComponent', () => {
exerciseService = TestBed.inject(ExerciseService);
getExerciseDetailsStub = jest.spyOn(exerciseService, 'getExerciseDetails').mockReturnValue(of(new HttpResponse({ body: exercise })));
+ historyService = TestBed.inject(LearningPathHistoryStorageService);
+ storeLectureUnitStub = jest.spyOn(historyService, 'storeLectureUnit');
+ storeExerciseStub = jest.spyOn(historyService, 'storeExercise');
+ hasPreviousStub = jest.spyOn(historyService, 'hasPrevious');
+ getPreviousStub = jest.spyOn(historyService, 'getPrevious');
+
fixture.detectChanges();
});
});
@@ -124,7 +137,9 @@ describe('LearningPathContainerComponent', () => {
comp.lecture = lecture;
fixture.detectChanges();
comp.onNextTask();
- expect(comp.history).toEqual([[lectureUnit.id!, lecture.id!]]);
+ expect(storeLectureUnitStub).toHaveBeenCalledOnce();
+ expect(storeLectureUnitStub).toHaveBeenCalledWith(learningPathId, lecture.id, lectureUnit.id);
+ expect(storeExerciseStub).not.toHaveBeenCalled();
});
it('should store current exercise in history', () => {
@@ -132,17 +147,22 @@ describe('LearningPathContainerComponent', () => {
comp.exercise = exercise;
fixture.detectChanges();
comp.onNextTask();
- expect(comp.history).toEqual([[exercise.id!, -1]]);
+ expect(storeLectureUnitStub).not.toHaveBeenCalled();
+ expect(storeExerciseStub).toHaveBeenCalledOnce();
+ expect(storeExerciseStub).toHaveBeenCalledWith(learningPathId, exercise.id);
});
it('should load no previous task if history is empty', () => {
+ expect(historyService.hasPrevious(learningPathId)).toBeFalsy();
comp.onPrevTask();
+ expect(getPreviousStub).not.toHaveBeenCalled();
expect(findWithDetailsStub).not.toHaveBeenCalled();
expect(getExerciseDetailsStub).not.toHaveBeenCalled();
});
it('should load previous lecture unit', () => {
- comp.history = [[lectureUnit.id!, lecture.id!]];
+ hasPreviousStub.mockReturnValue(true);
+ getPreviousStub.mockReturnValue(new LectureUnitEntry(lecture.id, lectureUnit.id));
fixture.detectChanges();
comp.onPrevTask();
expect(findWithDetailsStub).toHaveBeenCalled();
@@ -151,7 +171,8 @@ describe('LearningPathContainerComponent', () => {
});
it('should load previous exercise', () => {
- comp.history = [[exercise.id!, -1]];
+ hasPreviousStub.mockReturnValue(true);
+ getPreviousStub.mockReturnValue(new ExerciseEntry(exercise.id));
fixture.detectChanges();
comp.onPrevTask();
expect(findWithDetailsStub).not.toHaveBeenCalled();
diff --git a/src/test/javascript/spec/service/learning-path-history-storage.service.spec.ts b/src/test/javascript/spec/service/learning-path-history-storage.service.spec.ts
new file mode 100644
index 000000000000..b82196bb1e1c
--- /dev/null
+++ b/src/test/javascript/spec/service/learning-path-history-storage.service.spec.ts
@@ -0,0 +1,127 @@
+import { TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../test.module';
+import { ExerciseEntry, LearningPathHistoryStorageService, LectureUnitEntry } from 'app/course/learning-paths/participate/learning-path-history-storage.service';
+
+describe('LearningPathHistoryStorageService', () => {
+ let historyStorageService: LearningPathHistoryStorageService;
+ let learningPathId: number;
+ let lectureId: number;
+ let lectureUnitId: number;
+ let exerciseId: number;
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule],
+ })
+ .compileComponents()
+ .then(() => {
+ historyStorageService = new LearningPathHistoryStorageService();
+ learningPathId = 1;
+ lectureId = 2;
+ lectureUnitId = 3;
+ exerciseId = 4;
+ });
+ });
+
+ it('should return null if no previous is present', () => {
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
+ const entry = historyStorageService.getPrevious(learningPathId);
+ expect(entry).toBeNull();
+ });
+
+ it('should handle single lecture unit', () => {
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
+ historyStorageService.storeLectureUnit(learningPathId, lectureId, lectureUnitId);
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
+ const entry = historyStorageService.getPrevious(learningPathId);
+ expect(entry).not.toBeNull();
+ expect(entry).toBeInstanceOf(LectureUnitEntry);
+ const lectureUnitEntry = entry;
+ expect(lectureUnitEntry.lectureId).toBe(lectureId);
+ expect(lectureUnitEntry.lectureUnitId).toBe(lectureUnitId);
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
+ });
+
+ it('should handle single exercise', () => {
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
+ historyStorageService.storeExercise(learningPathId, exerciseId);
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
+ const entry = historyStorageService.getPrevious(learningPathId);
+ expect(entry).not.toBeNull();
+ expect(entry).toBeInstanceOf(ExerciseEntry);
+ const exerciseEntry = entry;
+ expect(exerciseEntry.exerciseId).toBe(exerciseId);
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
+ });
+
+ it('should handle mixed sequence', () => {
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
+ historyStorageService.storeExercise(learningPathId, exerciseId);
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
+ historyStorageService.storeLectureUnit(learningPathId, lectureId, lectureUnitId);
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
+ historyStorageService.storeExercise(learningPathId, 11);
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
+
+ // exercise 2
+ let entry = historyStorageService.getPrevious(learningPathId);
+ expect(entry).not.toBeNull();
+ expect(entry).toBeInstanceOf(ExerciseEntry);
+ const exercise2Entry = entry;
+ expect(exercise2Entry.exerciseId).toBe(11);
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
+
+ // lecture unit
+ entry = historyStorageService.getPrevious(learningPathId);
+ expect(entry).not.toBeNull();
+ expect(entry).toBeInstanceOf(LectureUnitEntry);
+ const lectureUnitEntry = entry;
+ expect(lectureUnitEntry.lectureId).toBe(lectureId);
+ expect(lectureUnitEntry.lectureUnitId).toBe(lectureUnitId);
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
+
+ // exercise 1
+ entry = historyStorageService.getPrevious(learningPathId);
+ expect(entry).not.toBeNull();
+ expect(entry).toBeInstanceOf(ExerciseEntry);
+ const exerciseEntry = entry;
+ expect(exerciseEntry.exerciseId).toBe(exerciseId);
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
+ });
+
+ it('should handle multiple learning paths', () => {
+ const learningPath2Id = 11;
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
+ expect(historyStorageService.hasPrevious(learningPath2Id)).toBeFalsy();
+
+ // lecture unit in learningPath(1)
+ historyStorageService.storeLectureUnit(learningPathId, lectureId, lectureUnitId);
+
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
+ expect(historyStorageService.hasPrevious(learningPath2Id)).toBeFalsy();
+
+ // exercise in learningPath(2)
+ historyStorageService.storeExercise(learningPath2Id, exerciseId);
+
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
+ expect(historyStorageService.hasPrevious(learningPath2Id)).toBeTruthy();
+
+ let entry = historyStorageService.getPrevious(learningPathId);
+ expect(entry).not.toBeNull();
+ expect(entry).toBeInstanceOf(LectureUnitEntry);
+ const lectureUnitEntry = entry;
+ expect(lectureUnitEntry.lectureId).toBe(lectureId);
+ expect(lectureUnitEntry.lectureUnitId).toBe(lectureUnitId);
+
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
+ expect(historyStorageService.hasPrevious(learningPath2Id)).toBeTruthy();
+
+ entry = historyStorageService.getPrevious(learningPath2Id);
+ expect(entry).not.toBeNull();
+ expect(entry).toBeInstanceOf(ExerciseEntry);
+ const exerciseEntry = entry;
+ expect(exerciseEntry.exerciseId).toBe(exerciseId);
+
+ expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
+ expect(historyStorageService.hasPrevious(learningPath2Id)).toBeFalsy();
+ });
+});
From 30855807d38eaf98fb23cf66cebc5ffca631ae96 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Tue, 1 Aug 2023 11:39:33 +0200
Subject: [PATCH 110/215] fix build fail
---
.../participate/learning-path-container.component.spec.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
index 74c8cc280633..1bd89fef3e37 100644
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
@@ -162,7 +162,7 @@ describe('LearningPathContainerComponent', () => {
it('should load previous lecture unit', () => {
hasPreviousStub.mockReturnValue(true);
- getPreviousStub.mockReturnValue(new LectureUnitEntry(lecture.id, lectureUnit.id));
+ getPreviousStub.mockReturnValue(new LectureUnitEntry(lecture.id!, lectureUnit.id!));
fixture.detectChanges();
comp.onPrevTask();
expect(findWithDetailsStub).toHaveBeenCalled();
@@ -172,7 +172,7 @@ describe('LearningPathContainerComponent', () => {
it('should load previous exercise', () => {
hasPreviousStub.mockReturnValue(true);
- getPreviousStub.mockReturnValue(new ExerciseEntry(exercise.id));
+ getPreviousStub.mockReturnValue(new ExerciseEntry(exercise.id!));
fixture.detectChanges();
comp.onPrevTask();
expect(findWithDetailsStub).not.toHaveBeenCalled();
From 51265e693e377551ac13c5bd400ecc4fda5b8062 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Tue, 1 Aug 2023 12:47:25 +0200
Subject: [PATCH 111/215] fix wrong autocompletion
---
.../de/tum/in/www1/artemis/web/rest/CompetencyResource.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/CompetencyResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/CompetencyResource.java
index 78cdff7931b4..8cd08205736a 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/CompetencyResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/CompetencyResource.java
@@ -297,7 +297,7 @@ public ResponseEntity deleteCompetency(@PathVariable Long competencyId, @P
lectureUnitRepository.save(lectureUnit);
});
- if (course.getComplaintsEnabled()) {
+ if (course.getLearningPathsEnabled()) {
learningPathService.removeLinkedCompetencyFromLearningPathsOfCourse(competency, courseId);
}
From e64a32727f157aa273c24cbb4870d60b33ebdc04 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Tue, 1 Aug 2023 13:03:12 +0200
Subject: [PATCH 112/215] fix codestyle issues
---
.../learning-path-graph/learning-path-graph.component.scss | 1 +
.../webapp/app/entities/lecture-unit/lectureUnit.model.ts | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
index 9ee5c9bb5f03..de4453f8e8da 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
@@ -25,6 +25,7 @@ jhi-learning-path-graph-node:hover {
fa-icon {
width: 100%;
+
svg {
margin: 10%;
width: 80%;
diff --git a/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts b/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
index f525b0753661..b878b4554020 100644
--- a/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
+++ b/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
@@ -1,9 +1,9 @@
import { BaseEntity } from 'app/shared/model/base-entity';
import dayjs from 'dayjs/esm';
import { Lecture } from 'app/entities/lecture.model';
-import { Competency, CompetencyTaxonomy } from 'app/entities/competency.model';
+import { Competency } from 'app/entities/competency.model';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { faBrain, faComments, faDownload, faLink, faQuestion, faScroll, faVideo } from '@fortawesome/free-solid-svg-icons';
+import { faDownload, faLink, faQuestion, faScroll, faVideo } from '@fortawesome/free-solid-svg-icons';
// IMPORTANT NOTICE: The following strings have to be consistent with
// the ones defined in LectureUnit.java
From 386366de502f6858a01942664e53bce8e7a4d4f5 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Tue, 1 Aug 2023 13:05:56 +0200
Subject: [PATCH 113/215] fix type issue after merge
---
.../artemis/web/rest/dto/competency/NgxLearningPathDTO.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
index 68723c1fab52..f083b7eb4c52 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
@@ -10,7 +10,7 @@
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record NgxLearningPathDTO(Set nodes, Set edges, Set clusters) {
- public record Node(String id, NodeType type, long linkedResource, long linkedResourceParent, boolean completed, String label) {
+ public record Node(String id, NodeType type, Long linkedResource, Long linkedResourceParent, boolean completed, String label) {
public Node(String id, NodeType type, Long linkedResource, boolean completed, String label) {
this(id, type, linkedResource, null, completed, label);
From e4dfd81957bd9767e41ec347a5c1219ba6c6ff70 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 12:05:10 +0200
Subject: [PATCH 114/215] add feature toggle for learning paths
---
.../java/de/tum/in/www1/artemis/service/feature/Feature.java | 2 +-
.../webapp/app/shared/feature-toggle/feature-toggle.service.ts | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/feature/Feature.java b/src/main/java/de/tum/in/www1/artemis/service/feature/Feature.java
index 5c7164f209b3..b7546e269f99 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/feature/Feature.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/feature/Feature.java
@@ -1,5 +1,5 @@
package de.tum.in.www1.artemis.service.feature;
public enum Feature {
- ProgrammingExercises, PlagiarismChecks, Exports, TutorialGroups
+ ProgrammingExercises, PlagiarismChecks, Exports, TutorialGroups, LearningPaths
}
diff --git a/src/main/webapp/app/shared/feature-toggle/feature-toggle.service.ts b/src/main/webapp/app/shared/feature-toggle/feature-toggle.service.ts
index 8b279e87eaad..9c5d8f0b7c67 100644
--- a/src/main/webapp/app/shared/feature-toggle/feature-toggle.service.ts
+++ b/src/main/webapp/app/shared/feature-toggle/feature-toggle.service.ts
@@ -14,6 +14,7 @@ export enum FeatureToggle {
PlagiarismChecks = 'PlagiarismChecks',
Exports = 'Exports',
TutorialGroups = 'TutorialGroups',
+ LearningPaths = 'LearningPaths',
}
export type ActiveFeatureToggles = Array;
From cc6ac910922689a9d6fa9dbdc43c75a59fc94437 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 12:30:39 +0200
Subject: [PATCH 115/215] fix merge conflict
---
.../participate/learning-path-container.component.ts | 4 ++--
.../participate/learning-path-container.component.spec.ts | 8 ++++++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
index 309a72771e24..2e4277c408cb 100644
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
+++ b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
@@ -153,9 +153,9 @@ export class LearningPathContainerComponent implements OnInit {
onNodeClicked(node: NgxLearningPathNode) {
if (node.type === NodeType.LECTURE_UNIT || node.type === NodeType.EXERCISE) {
if (this.lectureUnit?.id) {
- this.history.push([this.lectureUnit.id, this.lectureId!]);
+ this.learningPathHistoryStorageService.storeLectureUnit(this.learningPathId, this.lectureId!, this.lectureUnit.id);
} else if (this.exercise?.id) {
- this.history.push([this.exercise.id, -1]);
+ this.learningPathHistoryStorageService.storeExercise(this.learningPathId, this.exercise.id);
}
this.undefineAll();
this.learningObjectId = node.linkedResource!;
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
index 9fc977f8707a..b2bb637d73a7 100644
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
@@ -225,7 +225,9 @@ describe('LearningPathContainerComponent', () => {
fixture.detectChanges();
const node = { id: 'some-id', type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
comp.onNodeClicked(node);
- expect(comp.history).toEqual([[lectureUnit.id!, lecture.id!]]);
+ expect(storeLectureUnitStub).toHaveBeenCalledOnce();
+ expect(storeLectureUnitStub).toHaveBeenCalledWith(learningPathId, lecture.id, lectureUnit.id!);
+ expect(storeExerciseStub).not.toHaveBeenCalled();
});
it('should handle store current exercise in history on node click', () => {
@@ -234,6 +236,8 @@ describe('LearningPathContainerComponent', () => {
fixture.detectChanges();
const node = { id: 'some-id', type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
comp.onNodeClicked(node);
- expect(comp.history).toEqual([[exercise.id!, -1]]);
+ expect(storeLectureUnitStub).not.toHaveBeenCalled();
+ expect(storeExerciseStub).toHaveBeenCalledOnce();
+ expect(storeExerciseStub).toHaveBeenCalledWith(learningPathId, exercise.id!);
});
});
From 10c3c0b38754f44865ad327851d4fa611cf972a2 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 13:09:12 +0200
Subject: [PATCH 116/215] reduce size of pr
---
.../artemis/service/LearningPathService.java | 253 ------------------
.../web/rest/LearningPathResource.java | 91 -------
.../LearningPathRecommendationDTO.java | 8 -
.../dto/competency/NgxLearningPathDTO.java | 47 ----
.../learning-path-graph-node.component.html | 41 ---
.../learning-path-graph-node.component.ts | 20 --
.../learning-path-graph.component.html | 41 ---
.../learning-path-graph.component.scss | 44 ---
.../learning-path-graph.component.ts | 60 -----
.../competency-node-details.component.html | 18 --
.../competency-node-details.component.ts | 58 ----
.../exercise-node-details.component.html | 14 -
.../exercise-node-details.component.ts | 38 ---
.../lecture-unit-node-details.component.html | 14 -
.../lecture-unit-node-details.component.ts | 45 ----
.../learning-path-management.component.ts | 11 +-
...earning-path-progress-modal.component.html | 19 --
...earning-path-progress-modal.component.scss | 26 --
.../learning-path-progress-modal.component.ts | 20 --
.../learning-path-progress-nav.component.html | 16 --
.../learning-path-progress-nav.component.ts | 19 --
.../learning-paths/learning-path.service.ts | 27 --
.../learning-paths/learning-paths.module.ts | 81 +-----
.../learning-path-container.component.html | 24 --
.../learning-path-container.component.scss | 13 -
.../learning-path-container.component.ts | 170 ------------
...learning-path-graph-sidebar.component.html | 36 ---
...learning-path-graph-sidebar.component.scss | 77 ------
.../learning-path-graph-sidebar.component.ts | 53 ----
.../learning-path-history-storage.service.ts | 85 ------
...ning-path-lecture-unit-view.component.html | 17 --
...arning-path-lecture-unit-view.component.ts | 55 ----
.../learning-path-lecture-unit-view.module.ts | 33 ---
.../course-management-tab-bar.component.html | 8 +-
.../overview/course-overview.component.html | 4 -
.../lecture/LearningPathIntegrationTest.java | 96 -------
.../service/LearningPathServiceTest.java | 245 -----------------
.../admin-feature-toggle.component.spec.ts | 2 +-
...learning-path-graph-node.component.spec.ts | 46 ----
.../learning-path-graph.component.spec.ts | 62 -----
.../competency-node-details.component.spec.ts | 69 -----
.../exercise-node-details.component.spec.ts | 50 ----
...ecture-unit-node-details.component.spec.ts | 57 ----
...ning-path-progress-modal.component.spec.ts | 48 ----
...arning-path-progress-nav.component.spec.ts | 73 -----
.../learning-path-container.component.spec.ts | 243 -----------------
...rning-path-graph-sidebar.component.spec.ts | 50 ----
...g-path-lecture-unit-view.component.spec.ts | 142 ----------
.../sticky-popover.directive.spec.ts | 60 -----
...rning-path-history-storage.service.spec.ts | 127 ---------
.../service/learning-path.service.spec.ts | 18 --
51 files changed, 12 insertions(+), 2962 deletions(-)
delete mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathRecommendationDTO.java
delete mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
delete mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.scss
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/learning-path-history-storage.service.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.html
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.ts
delete mode 100644 src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.module.ts
delete mode 100644 src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
delete mode 100644 src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
delete mode 100644 src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts
delete mode 100644 src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
delete mode 100644 src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts
delete mode 100644 src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
delete mode 100644 src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
delete mode 100644 src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
delete mode 100644 src/test/javascript/spec/component/learning-paths/participate/learning-path-graph-sidebar.component.spec.ts
delete mode 100644 src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
delete mode 100644 src/test/javascript/spec/directive/sticky-popover.directive.spec.ts
delete mode 100644 src/test/javascript/spec/service/learning-path-history-storage.service.spec.ts
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index 2b02972a4b76..fade615abc0d 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -1,31 +1,24 @@
package de.tum.in.www1.artemis.service;
import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
-import java.util.stream.LongStream;
-import java.util.stream.Stream;
import javax.validation.constraints.NotNull;
-import org.jgrapht.alg.util.UnionFind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import de.tum.in.www1.artemis.domain.Course;
-import de.tum.in.www1.artemis.domain.LearningObject;
import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.domain.competency.Competency;
-import de.tum.in.www1.artemis.domain.competency.CompetencyRelation;
import de.tum.in.www1.artemis.domain.competency.LearningPath;
import de.tum.in.www1.artemis.repository.*;
import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO;
import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathHealthDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathPageableSearchDTO;
-import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
import de.tum.in.www1.artemis.web.rest.util.PageUtil;
/**
@@ -179,252 +172,6 @@ private void updateLearningPathProgress(@NotNull LearningPath learningPath) {
log.debug("Updated LearningPath (id={}) for user (id={})", learningPath.getId(), userId);
}
- /**
- * Generates Ngx representation of the learning path.
- *
- * @param learningPath the learning path for which the Ngx representation should be created
- * @return Ngx representation of the learning path
- * @see NgxLearningPathDTO
- */
- public NgxLearningPathDTO generateNgxRepresentation(@NotNull LearningPath learningPath) {
- Set nodes = new HashSet<>();
- Set edges = new HashSet<>();
- Set clusters = new HashSet<>();
- learningPath.getCompetencies().forEach(competency -> generateNgxRepresentationForCompetency(learningPath, competency, nodes, edges, clusters));
- generateNgxRepresentationForRelations(learningPath, nodes, edges);
- return new NgxLearningPathDTO(nodes, edges, clusters);
- }
-
- /**
- * Generates Ngx representation for competency.
- *
- * A competency's representation consists of
- *
- * start node
- * end node
- * a node for each learning unit (exercises or lecture unit)
- * edges from start node to each learning unit
- * edges from each learning unit to end node
- * a cluster consisting of all created nodes
- *
- *
- * @param learningPath the learning path for which the representation should be created
- * @param competency the competency for which the representation will be created
- * @param nodes set of nodes to store the new nodes
- * @param edges set of edges to store the new edges
- * @param clusters set of clusters to store the new clusters
- */
- private void generateNgxRepresentationForCompetency(LearningPath learningPath, Competency competency, Set nodes, Set edges,
- Set clusters) {
- Set currentCluster = new HashSet<>();
- // generates start and end node
- final var startNodeId = getCompetencyStartNodeId(competency.getId());
- final var endNodeId = getCompetencyEndNodeId(competency.getId());
- currentCluster.add(new NgxLearningPathDTO.Node(startNodeId, NgxLearningPathDTO.NodeType.COMPETENCY_START, competency.getId()));
- currentCluster.add(new NgxLearningPathDTO.Node(endNodeId, NgxLearningPathDTO.NodeType.COMPETENCY_END, competency.getId()));
-
- // generate nodes and edges for lecture units
- competency.getLectureUnits().forEach(lectureUnit -> {
- currentCluster.add(new NgxLearningPathDTO.Node(getLectureUnitNodeId(competency.getId(), lectureUnit.getId()), NgxLearningPathDTO.NodeType.LECTURE_UNIT,
- lectureUnit.getId(), lectureUnit.getLecture().getId(), lectureUnit.isCompletedFor(learningPath.getUser()), lectureUnit.getName()));
- edges.add(new NgxLearningPathDTO.Edge(getLectureUnitInEdgeId(competency.getId(), lectureUnit.getId()), startNodeId,
- getLectureUnitNodeId(competency.getId(), lectureUnit.getId())));
- edges.add(new NgxLearningPathDTO.Edge(getLectureUnitOutEdgeId(competency.getId(), lectureUnit.getId()), getLectureUnitNodeId(competency.getId(), lectureUnit.getId()),
- endNodeId));
- });
- // generate nodes and edges for exercises
- competency.getExercises().forEach(exercise -> {
- currentCluster.add(new NgxLearningPathDTO.Node(getExerciseNodeId(competency.getId(), exercise.getId()), NgxLearningPathDTO.NodeType.EXERCISE, exercise.getId(),
- exercise.isCompletedFor(learningPath.getUser()), exercise.getTitle()));
- edges.add(new NgxLearningPathDTO.Edge(getExerciseInEdgeId(competency.getId(), exercise.getId()), startNodeId, getExerciseNodeId(competency.getId(), exercise.getId())));
- edges.add(new NgxLearningPathDTO.Edge(getExerciseOutEdgeId(competency.getId(), exercise.getId()), getExerciseNodeId(competency.getId(), exercise.getId()), endNodeId));
- });
- // if no linked learning units exist directly link start to end
- if (currentCluster.size() == 2) {
- edges.add(new NgxLearningPathDTO.Edge(getDirectEdgeId(competency.getId()), startNodeId, endNodeId));
- }
- // generate cluster for competency
- var childNodeIds = currentCluster.stream().map(NgxLearningPathDTO.Node::id).collect(Collectors.toSet());
- childNodeIds.add(startNodeId);
- childNodeIds.add(endNodeId);
- clusters.add(new NgxLearningPathDTO.Cluster(String.valueOf(competency.getId()), competency.getTitle(), childNodeIds));
-
- nodes.addAll(currentCluster);
- }
-
- /**
- * Generates Ngx representations for competency relations.
- *
- * The representation will contain:
- *
- *
- * For each matching cluster (transitive closure of competencies that are in a match relation):
- *
- * two nodes (start and end of cluster) will be created
- * edges from the start node of the cluster to each start node of the competencies
- * edges from each end node of the competency to the end node of the cluster
- *
- *
- *
- * For each other relation: edge from head competency end node to tail competency start node. If competency is part of a matching cluster, the edge will be linked to the
- * corresponding cluster start/end node.
- *
- *
- *
- * two nodes (start and end of cluster) will be created.
- *
- * @param learningPath the learning path for which the Ngx representation should be created
- * @param nodes set of nodes to store the new nodes
- * @param edges set of edges to store the new edges
- */
- private void generateNgxRepresentationForRelations(LearningPath learningPath, Set nodes, Set edges) {
- final var relations = competencyRelationRepository.findAllByCourseId(learningPath.getCourse().getId());
-
- // compute match clusters
- Map competencyToMatchCluster = new HashMap<>();
- final var competenciesInMatchRelation = relations.stream().filter(relation -> relation.getType().equals(CompetencyRelation.RelationType.MATCHES))
- .flatMap(relation -> Stream.of(relation.getHeadCompetency().getId(), relation.getTailCompetency().getId())).collect(Collectors.toSet());
- if (!competenciesInMatchRelation.isEmpty()) {
- UnionFind matchClusters = new UnionFind<>(competenciesInMatchRelation);
- relations.stream().filter(relation -> relation.getType().equals(CompetencyRelation.RelationType.MATCHES))
- .forEach(relation -> matchClusters.union(relation.getHeadCompetency().getId(), relation.getTailCompetency().getId()));
-
- // generate map between competencies and cluster node
- AtomicInteger matchClusterId = new AtomicInteger();
- relations.stream().filter(relation -> relation.getType().equals(CompetencyRelation.RelationType.MATCHES))
- .flatMapToLong(relation -> LongStream.of(relation.getHeadCompetency().getId(), relation.getTailCompetency().getId())).distinct().forEach(competencyId -> {
- var parentId = matchClusters.find(competencyId);
- var clusterId = competencyToMatchCluster.computeIfAbsent(parentId, (key) -> matchClusterId.getAndIncrement());
- competencyToMatchCluster.put(competencyId, clusterId);
- });
-
- // generate match cluster start and end nodes
- for (int i = 0; i < matchClusters.numberOfSets(); i++) {
- nodes.add(new NgxLearningPathDTO.Node(getMatchingClusterStartNodeId(i), NgxLearningPathDTO.NodeType.COMPETENCY_START));
- nodes.add(new NgxLearningPathDTO.Node(getMatchingClusterEndNodeId(i), NgxLearningPathDTO.NodeType.COMPETENCY_END));
- }
-
- // generate edges between match cluster nodes and corresponding competencies
- competencyToMatchCluster.forEach((competency, cluster) -> {
- edges.add(new NgxLearningPathDTO.Edge(getInEdgeId(competency), getMatchingClusterStartNodeId(cluster), getCompetencyStartNodeId(competency)));
- edges.add(new NgxLearningPathDTO.Edge(getOutEdgeId(competency), getCompetencyEndNodeId(competency), getMatchingClusterEndNodeId(cluster)));
- });
- }
-
- // generate edges for remaining relations
- final Set createdRelations = new HashSet<>();
- relations.stream().filter(relation -> !relation.getType().equals(CompetencyRelation.RelationType.MATCHES))
- .forEach(relation -> generateNgxRepresentationForRelation(relation, competencyToMatchCluster, createdRelations, edges));
- }
-
- /**
- * Generates Ngx representations for competency relation.
- *
- * @param relation the relation for which the Ngx representation should be created
- * @param competencyToMatchCluster map from competencies to corresponding cluster
- * @param createdRelations set of edge ids that have already been created
- * @param edges set of edges to store the new edges
- */
- private void generateNgxRepresentationForRelation(CompetencyRelation relation, Map competencyToMatchCluster, Set createdRelations,
- Set edges) {
- final var sourceId = relation.getHeadCompetency().getId();
- String sourceNodeId;
- if (competencyToMatchCluster.containsKey(sourceId)) {
- sourceNodeId = getMatchingClusterEndNodeId(competencyToMatchCluster.get(sourceId));
- }
- else {
- sourceNodeId = getCompetencyEndNodeId(sourceId);
- }
- final var targetId = relation.getTailCompetency().getId();
- String targetNodeId;
- if (competencyToMatchCluster.containsKey(targetId)) {
- targetNodeId = getMatchingClusterStartNodeId(competencyToMatchCluster.get(targetId));
- }
- else {
- targetNodeId = getCompetencyStartNodeId(targetId);
- }
- final String relationEdgeId = getRelationEdgeId(sourceNodeId, targetNodeId);
- // skip if relation has already been created (possible for edges linked to matching cluster start/end nodes)
- if (!createdRelations.contains(relationEdgeId)) {
- final var edge = new NgxLearningPathDTO.Edge(relationEdgeId, sourceNodeId, targetNodeId);
- edges.add(edge);
- createdRelations.add(relationEdgeId);
- }
- }
-
- public static String getCompetencyStartNodeId(long competencyId) {
- return "node-" + competencyId + "-start";
- }
-
- public static String getCompetencyEndNodeId(long competencyId) {
- return "node-" + competencyId + "-end";
- }
-
- public static String getLectureUnitNodeId(long competencyId, long lectureUnitId) {
- return "node-" + competencyId + "-lu-" + lectureUnitId;
- }
-
- public static String getExerciseNodeId(long competencyId, long exerciseId) {
- return "node-" + competencyId + "-ex-" + exerciseId;
- }
-
- public static String getMatchingClusterStartNodeId(long matchingClusterId) {
- return "matching-" + matchingClusterId + "-start";
- }
-
- public static String getMatchingClusterEndNodeId(long matchingClusterId) {
- return "matching-" + matchingClusterId + "-end";
- }
-
- public static String getLectureUnitInEdgeId(long competencyId, long lectureUnitId) {
- return "edge-" + competencyId + "-lu-" + getInEdgeId(lectureUnitId);
- }
-
- public static String getLectureUnitOutEdgeId(long competencyId, long lectureUnitId) {
- return "edge-" + competencyId + "-lu-" + getOutEdgeId(lectureUnitId);
- }
-
- public static String getExerciseInEdgeId(long competencyId, long exercise) {
- return "edge-" + competencyId + "-ex-" + getInEdgeId(exercise);
- }
-
- public static String getExerciseOutEdgeId(long competencyId, long exercise) {
- return "edge-" + competencyId + "-ex-" + getOutEdgeId(exercise);
- }
-
- public static String getInEdgeId(long id) {
- return "edge-" + id + "-in";
- }
-
- public static String getOutEdgeId(long id) {
- return "edge-" + id + "-out";
- }
-
- public static String getRelationEdgeId(String sourceNodeId, String targetNodeId) {
- return "edge-relation-" + sourceNodeId + "-" + targetNodeId;
- }
-
- public static String getDirectEdgeId(long competencyId) {
- return "edge-" + competencyId + "-direct";
- }
-
- /**
- * Gets a recommended learning object based on the current state of the given learning path.
- *
- * @param learningPath the learning path for which the recommendation should be computed
- * @return recommended learning object
- */
- public Optional getRecommendation(@NotNull LearningPath learningPath) {
- LearningObject learningObject = learningPath.getCompetencies().stream()
- .flatMap(competency -> Stream.concat(
- competency.getLectureUnits().stream()
- .filter(lectureUnit -> !lectureUnitRepository.findWithEagerCompletedUsersByIdElseThrow(lectureUnit.getId()).isCompletedFor(learningPath.getUser())),
- competency.getExercises().stream()
- .filter(exercise -> !exerciseRepository.findByIdWithStudentParticipationsElseThrow(exercise.getId()).isCompletedFor(learningPath.getUser()))))
- .findFirst().orElse(null);
- return Optional.ofNullable(learningObject);
- }
-
/**
* Gets the health status of learning paths for the given course.
*
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 191b842927bc..fca6892e86f5 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -8,15 +8,11 @@
import org.springframework.web.bind.annotation.*;
import de.tum.in.www1.artemis.domain.Course;
-import de.tum.in.www1.artemis.domain.User;
-import de.tum.in.www1.artemis.domain.competency.LearningPath;
-import de.tum.in.www1.artemis.domain.lecture.LectureUnit;
import de.tum.in.www1.artemis.repository.CourseRepository;
import de.tum.in.www1.artemis.repository.LearningPathRepository;
import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.security.Role;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastInstructor;
-import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.service.LearningPathService;
import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO;
@@ -111,93 +107,6 @@ public ResponseEntity> getLea
return ResponseEntity.ok(learningPathService.getAllOfCourseOnPageWithSize(search, course));
}
- /**
- * GET /courses/:courseId/learning-path-id : Gets the id of the learning path.
- * If the learning path has not been generated although the course has learning paths enabled, the corresponding learning path will be created.
- *
- * @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
- */
- @GetMapping("/courses/{courseId}/learning-path-id")
- @EnforceAtLeastStudent
- public ResponseEntity getLearningPathId(@PathVariable Long courseId) {
- log.debug("REST request to get learning path id for course with id: {}", courseId);
- Course course = courseRepository.findByIdElseThrow(courseId);
- authorizationCheckService.isStudentInCourse(course, null);
- if (!course.getLearningPathsEnabled()) {
- throw new BadRequestException("Learning paths are not enabled for this course.");
- }
-
- // generate learning path if missing
- User user = userRepository.getUser();
- final var learningPathOptional = learningPathRepository.findByCourseIdAndUserId(course.getId(), user.getId());
- LearningPath learningPath;
- if (learningPathOptional.isEmpty()) {
- course = courseRepository.findWithEagerCompetenciesByIdElseThrow(courseId);
- learningPath = learningPathService.generateLearningPathForUser(course, user);
- }
- else {
- learningPath = learningPathOptional.get();
- }
- return ResponseEntity.ok(learningPath.getId());
- }
-
- /**
- * GET /learning-path/:learningPathId : Gets the ngx representation of the learning path.
- *
- * @param learningPathId the id of the learning path that should be fetched
- * @return the ResponseEntity with status 200 (OK) and with body the ngx representation of the learning path
- */
- @GetMapping("/learning-path/{learningPathId}")
- @EnforceAtLeastStudent
- public ResponseEntity getNgxLearningPath(@PathVariable Long learningPathId) {
- log.debug("REST request to get ngx representation of learning path with id: {}", learningPathId);
- LearningPath learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersByIdElseThrow(learningPathId);
- Course course = courseRepository.findByIdElseThrow(learningPath.getCourse().getId());
- if (!course.getLearningPathsEnabled()) {
- throw new BadRequestException("Learning paths are not enabled for this course.");
- }
- if (authorizationCheckService.isStudentInCourse(course, null)) {
- final var user = userRepository.getUser();
- if (!user.getId().equals(learningPath.getUser().getId())) {
- throw new AccessForbiddenException("You are not allowed to access another users learning path.");
- }
- }
- else if (!authorizationCheckService.isAtLeastInstructorInCourse(course, null) && !authorizationCheckService.isAdmin()) {
- throw new AccessForbiddenException("You are not allowed to access another users learning path.");
- }
- NgxLearningPathDTO graph = learningPathService.generateNgxRepresentation(learningPath);
- return ResponseEntity.ok(graph);
- }
-
- /**
- * GET /learning-path/:learningPathId/recommendation : Gets the next recommended learning object for the learning path.
- *
- * @param learningPathId the id of the learning path from which the recommendation should be fetched
- * @return the ResponseEntity with status 200 (OK) and with body the recommended learning object
- */
- @GetMapping("/learning-path/{learningPathId}/recommendation")
- @EnforceAtLeastStudent
- public ResponseEntity getRecommendation(@PathVariable Long learningPathId) {
- log.debug("REST request to get recommendation for learning path with id: {}", learningPathId);
- LearningPath learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(learningPathId);
- final var user = userRepository.getUser();
- if (!user.getId().equals(learningPath.getUser().getId())) {
- throw new AccessForbiddenException("You are not allowed to access another users learning path.");
- }
- final var recommendation = learningPathService.getRecommendation(learningPath);
- if (recommendation.isEmpty()) {
- return ResponseEntity.ok(new LearningPathRecommendationDTO(-1, -1, LearningPathRecommendationDTO.RecommendationType.EMPTY));
- }
- else if (recommendation.get() instanceof LectureUnit lectureUnit) {
- return ResponseEntity.ok(new LearningPathRecommendationDTO(recommendation.get().getId(), lectureUnit.getLecture().getId(),
- LearningPathRecommendationDTO.RecommendationType.LECTURE_UNIT));
- }
- else {
- return ResponseEntity.ok(new LearningPathRecommendationDTO(recommendation.get().getId(), -1, LearningPathRecommendationDTO.RecommendationType.EXERCISE));
- }
- }
-
/**
* GET /courses/:courseId/learning-path-health : Gets the health status of learning paths for the course.
*
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathRecommendationDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathRecommendationDTO.java
deleted file mode 100644
index e86c41615620..000000000000
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathRecommendationDTO.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package de.tum.in.www1.artemis.web.rest.dto.competency;
-
-public record LearningPathRecommendationDTO(long learningObjectId, long lectureId, RecommendationType type) {
-
- public enum RecommendationType {
- EMPTY, LECTURE_UNIT, EXERCISE
- }
-}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
deleted file mode 100644
index f083b7eb4c52..000000000000
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package de.tum.in.www1.artemis.web.rest.dto.competency;
-
-import java.util.Set;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-
-/**
- * Represents simplified learning path optimized for Ngx representation
- */
-@JsonInclude(JsonInclude.Include.NON_EMPTY)
-public record NgxLearningPathDTO(Set nodes, Set edges, Set clusters) {
-
- public record Node(String id, NodeType type, Long linkedResource, Long linkedResourceParent, boolean completed, String label) {
-
- public Node(String id, NodeType type, Long linkedResource, boolean completed, String label) {
- this(id, type, linkedResource, null, completed, label);
- }
-
- public Node(String id, NodeType type, Long linkedResource, String label) {
- this(id, type, linkedResource, false, label);
- }
-
- public Node(String id, NodeType type, String label) {
- this(id, type, null, label);
- }
-
- public Node(String id, NodeType type, Long linkedResource) {
- this(id, type, linkedResource, "");
- }
-
- public Node(String id, NodeType type) {
- this(id, type, null, "");
- }
- }
-
- @JsonInclude(JsonInclude.Include.NON_NULL)
- public record Edge(String id, String source, String target) {
- }
-
- @JsonInclude(JsonInclude.Include.NON_NULL)
- public record Cluster(String id, String label, Set childNodeIds) {
- }
-
- public enum NodeType {
- COMPETENCY_START, COMPETENCY_END, MATCH_START, MATCH_END, EXERCISE, LECTURE_UNIT,
- }
-}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
deleted file mode 100644
index 5d537b33656e..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
deleted file mode 100644
index 9bd4b5584569..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Component, Input } from '@angular/core';
-import { faCheckCircle, faCircle, faPlayCircle, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
-import { NgxLearningPathNode, NodeType } from 'app/entities/competency/learning-path.model';
-
-@Component({
- selector: 'jhi-learning-path-graph-node',
- templateUrl: './learning-path-graph-node.component.html',
-})
-export class LearningPathGraphNodeComponent {
- @Input() courseId: number;
- @Input() node: NgxLearningPathNode;
-
- //icons
- faCheckCircle = faCheckCircle;
- faPlayCircle = faPlayCircle;
- faQuestionCircle = faQuestionCircle;
-
- faCircle = faCircle;
- protected readonly NodeType = NodeType;
-}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
deleted file mode 100644
index c72307fe6a29..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
deleted file mode 100644
index de4453f8e8da..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-.graph-container {
- display: block;
- width: 100%;
- height: 100%;
- overflow: hidden;
-
- .ngx-graph {
- width: auto;
- }
-
- .node {
- display: flex;
- width: 100%;
- height: 100%;
- }
-}
-
-jhi-learning-path-graph-node:hover {
- cursor: pointer;
-}
-
-.node-icon-container {
- width: 100%;
- display: flex;
-
- fa-icon {
- width: 100%;
-
- svg {
- margin: 10%;
- width: 80%;
- height: 80%;
- }
- }
-}
-
-.completed {
- color: var(--bs-success);
-}
-
-.node-details {
- display: block;
- max-width: 90vh;
-}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
deleted file mode 100644
index 5644c8db6e28..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
-import { ActivatedRoute } from '@angular/router';
-import { Layout } from '@swimlane/ngx-graph';
-import * as shape from 'd3-shape';
-import { Subject } from 'rxjs';
-import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
-import { NgxLearningPathDTO, NgxLearningPathNode } from 'app/entities/competency/learning-path.model';
-
-@Component({
- selector: 'jhi-learning-path-graph',
- styleUrls: ['./learning-path-graph.component.scss'],
- templateUrl: './learning-path-graph.component.html',
- encapsulation: ViewEncapsulation.None,
-})
-export class LearningPathGraphComponent implements OnInit {
- isLoading = false;
- @Input() learningPathId: number;
- @Input() courseId: number;
- @Output() nodeClicked: EventEmitter = new EventEmitter();
- ngxLearningPath: NgxLearningPathDTO;
-
- layout: string | Layout = 'dagreCluster';
- curve = shape.curveBundle;
-
- draggingEnabled = false;
- panningEnabled = true;
- zoomEnabled = true;
- panOnZoom = true;
-
- update$: Subject = new Subject();
- center$: Subject = new Subject();
- zoomToFit$: Subject = new Subject();
-
- constructor(private activatedRoute: ActivatedRoute, private learningPathService: LearningPathService) {}
-
- ngOnInit() {
- if (this.learningPathId) {
- this.loadData();
- }
- }
-
- loadData() {
- this.isLoading = true;
- this.learningPathService.getNgxLearningPath(this.learningPathId).subscribe((ngxLearningPathResponse) => {
- this.ngxLearningPath = ngxLearningPathResponse.body!;
- this.isLoading = false;
- });
- }
-
- onResize() {
- this.update$.next(true);
- this.center$.next(true);
- this.zoomToFit$.next(true);
- }
-
- onCenterView() {
- this.zoomToFit$.next(true);
- this.center$.next(true);
- }
-}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
deleted file mode 100644
index 78bc30b342fa..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
- {{ competency!.title }}
- = 100" class="badge text-white text-bg-success" jhiTranslate="artemisApp.competency.mastered">Mastered
- Optional
-
-
{{ competency.description }}
-
-
-
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts
deleted file mode 100644
index 24cb6455586b..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { Component, Input, OnInit } from '@angular/core';
-import { HttpErrorResponse } from '@angular/common/http';
-import { onError } from 'app/shared/util/global.utils';
-import { CompetencyService } from 'app/course/competencies/competency.service';
-import { Competency, CompetencyProgress, getIcon, getIconTooltip } from 'app/entities/competency.model';
-import { AlertService } from 'app/core/util/alert.service';
-
-@Component({
- selector: 'jhi-competency-node-details',
- templateUrl: './competency-node-details.component.html',
-})
-export class CompetencyNodeDetailsComponent implements OnInit {
- @Input() courseId: number;
- @Input() competencyId: number;
- competency: Competency;
- competencyProgress: CompetencyProgress;
-
- isLoading = false;
-
- constructor(private competencyService: CompetencyService, private alertService: AlertService) {}
-
- ngOnInit() {
- if (this.competencyId && this.courseId) {
- this.loadData();
- }
- }
- private loadData() {
- this.isLoading = true;
- this.competencyService.findById(this.competencyId!, this.courseId!).subscribe({
- next: (resp) => {
- this.competency = resp.body!;
- if (this.competency.userProgress?.length) {
- this.competencyProgress = this.competency.userProgress.first()!;
- } else {
- this.competencyProgress = { progress: 0, confidence: 0 } as CompetencyProgress;
- }
- this.isLoading = false;
- },
- error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
- });
- }
-
- get progress(): number {
- return Math.round(this.competencyProgress.progress ?? 0);
- }
-
- get confidence(): number {
- return Math.min(Math.round(((this.competencyProgress.confidence ?? 0) / (this.competency.masteryThreshold ?? 100)) * 100), 100);
- }
-
- get mastery(): number {
- const weight = 2 / 3;
- return Math.round((1 - weight) * this.progress + weight * this.confidence);
- }
-
- protected readonly getIcon = getIcon;
- protected readonly getIconTooltip = getIconTooltip;
-}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html
deleted file mode 100644
index d364b09a85b2..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
- {{ exercise.title }}
-
-
-
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
deleted file mode 100644
index 8f265a38fadf..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { Component, Input, OnInit } from '@angular/core';
-import { HttpErrorResponse } from '@angular/common/http';
-import { onError } from 'app/shared/util/global.utils';
-import { AlertService } from 'app/core/util/alert.service';
-import { Exercise, getIcon, getIconTooltip } from 'app/entities/exercise.model';
-import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
-
-@Component({
- selector: 'jhi-exercise-node-details',
- templateUrl: './exercise-node-details.component.html',
-})
-export class ExerciseNodeDetailsComponent implements OnInit {
- @Input() exerciseId: number;
-
- exercise: Exercise;
-
- isLoading = false;
-
- constructor(private exerciseService: ExerciseService, private alertService: AlertService) {}
-
- ngOnInit() {
- if (this.exerciseId) {
- this.loadData();
- }
- }
- private loadData() {
- this.isLoading = true;
- this.exerciseService.find(this.exerciseId).subscribe({
- next: (exerciseResponse) => {
- this.exercise = exerciseResponse.body!;
- },
- error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
- });
- }
-
- protected readonly getIcon = getIcon;
- protected readonly getIconTooltip = getIconTooltip;
-}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
deleted file mode 100644
index 4f6553e2ef8e..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
- {{ lectureUnit.name }}
-
-
-
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
deleted file mode 100644
index 97aa66e1c311..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Component, Input, OnInit } from '@angular/core';
-import { HttpErrorResponse } from '@angular/common/http';
-import { onError } from 'app/shared/util/global.utils';
-import { AlertService } from 'app/core/util/alert.service';
-import { LectureService } from 'app/lecture/lecture.service';
-import { Lecture } from 'app/entities/lecture.model';
-import { LectureUnit, getIcon, getIconTooltip } from 'app/entities/lecture-unit/lectureUnit.model';
-
-@Component({
- selector: 'jhi-lecture-unit-node-details',
- templateUrl: './lecture-unit-node-details.component.html',
-})
-export class LectureUnitNodeDetailsComponent implements OnInit {
- @Input() lectureId: number;
- @Input() lectureUnitId: number;
-
- lecture: Lecture;
- lectureUnit: LectureUnit;
-
- isLoading = false;
-
- constructor(private lectureService: LectureService, private alertService: AlertService) {}
-
- ngOnInit() {
- if (this.lectureId && this.lectureUnitId) {
- this.loadData();
- }
- }
- private loadData() {
- this.isLoading = true;
- this.lectureService.findWithDetails(this.lectureId!).subscribe({
- next: (findLectureResult) => {
- this.lecture = findLectureResult.body!;
- if (this.lecture?.lectureUnits) {
- this.lectureUnit = this.lecture.lectureUnits.find((lectureUnit) => lectureUnit.id === this.lectureUnitId)!;
- }
- this.isLoading = false;
- },
- error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
- });
- }
-
- protected readonly getIcon = getIcon;
- protected readonly getIconTooltip = getIconTooltip;
-}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index 523dc1321e6a..96441d25d553 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -11,8 +11,6 @@ import { LearningPathPagingService } from 'app/course/learning-paths/learning-pa
import { SortService } from 'app/shared/service/sort.service';
import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
import { faSort, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
-import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
-import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
import { HealthStatus, LearningPathHealthDTO } from 'app/entities/competency/learning-path-health.model';
export enum TableColumn {
@@ -57,7 +55,6 @@ export class LearningPathManagementComponent implements OnInit {
private alertService: AlertService,
private pagingService: LearningPathPagingService,
private sortService: SortService,
- private modalService: NgbModal,
) {}
get page(): number {
@@ -203,13 +200,7 @@ export class LearningPathManagementComponent implements OnInit {
}
}
viewLearningPath(learningPath: LearningPathPageableSearchDTO) {
- const modalRef = this.modalService.open(LearningPathProgressModalComponent, {
- size: 'xl',
- backdrop: 'static',
- windowClass: 'learning-path-modal',
- });
- modalRef.componentInstance.courseId = this.courseId;
- modalRef.componentInstance.learningPath = learningPath;
+ // todo: part of future pr
}
protected readonly HealthStatus = HealthStatus;
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
deleted file mode 100644
index 7477b90ced6c..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
deleted file mode 100644
index cc798cd46adb..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-.modal-container {
- display: flex;
- flex-wrap: wrap;
- height: 90vh;
- width: 100%;
- padding-top: 8px;
-
- .row {
- width: 100%;
- margin-left: 0;
- margin-right: 0;
- }
-
- .modal-nav {
- height: max-content;
- }
-
- .graph {
- width: 100%;
- overflow: hidden;
- }
-}
-
-.learning-path-modal .modal-dialog .modal-content {
- min-height: 500px;
-}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
deleted file mode 100644
index 090b7d5405f4..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Component, Input, ViewChild } from '@angular/core';
-import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
-import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
-import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
-@Component({
- selector: 'jhi-learning-path-progress-modal',
- styleUrls: ['./learning-path-progress-modal.component.scss'],
- templateUrl: './learning-path-progress-modal.component.html',
-})
-export class LearningPathProgressModalComponent {
- @Input() courseId: number;
- @Input() learningPath: LearningPathPageableSearchDTO;
- @ViewChild('learningPathGraphComponent') learningPathGraphComponent: LearningPathGraphComponent;
-
- constructor(private activeModal: NgbActiveModal) {}
-
- close() {
- this.activeModal.close();
- }
-}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
deleted file mode 100644
index 66f5c6778206..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
{{ learningPath.user?.name }} ({{ learningPath.user?.login }})
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
deleted file mode 100644
index c050632cc577..000000000000
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Component, EventEmitter, Input, Output } from '@angular/core';
-import { faArrowsRotate, faArrowsToEye, faXmark } from '@fortawesome/free-solid-svg-icons';
-import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
-
-@Component({
- selector: 'jhi-learning-path-progress-nav',
- templateUrl: './learning-path-progress-nav.component.html',
-})
-export class LearningPathProgressNavComponent {
- @Input() learningPath: LearningPathPageableSearchDTO;
- @Output() onRefresh: EventEmitter = new EventEmitter();
- @Output() onCenterView: EventEmitter = new EventEmitter();
- @Output() onClose: EventEmitter = new EventEmitter();
-
- // icons
- faXmark = faXmark;
- faArrowsToEye = faArrowsToEye;
- faArrowsRotate = faArrowsRotate;
-}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path.service.ts b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
index 7fc0e33a1a3c..ece1bc6b204a 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path.service.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
@@ -1,8 +1,6 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpResponse } from '@angular/common/http';
-import { LearningPathRecommendationDTO, NgxLearningPathDTO } from 'app/entities/competency/learning-path.model';
-import { map } from 'rxjs/operators';
import { LearningPathHealthDTO } from 'app/entities/competency/learning-path-health.model';
@Injectable({ providedIn: 'root' })
@@ -19,31 +17,6 @@ export class LearningPathService {
return this.httpClient.put(`${this.resourceURL}/courses/${courseId}/learning-paths/generate-missing`, null, { observe: 'response' });
}
- getLearningPathId(courseId: number) {
- return this.httpClient.get(`${this.resourceURL}/courses/${courseId}/learning-path-id`, { observe: 'response' });
- }
-
- getNgxLearningPath(learningPathId: number): Observable> {
- return this.httpClient.get(`${this.resourceURL}/learning-path/${learningPathId}`, { observe: 'response' }).pipe(
- map((ngxLearningPathResponse) => {
- if (!ngxLearningPathResponse.body!.nodes) {
- ngxLearningPathResponse.body!.nodes = [];
- }
- if (!ngxLearningPathResponse.body!.edges) {
- ngxLearningPathResponse.body!.edges = [];
- }
- if (!ngxLearningPathResponse.body!.clusters) {
- ngxLearningPathResponse.body!.clusters = [];
- }
- return ngxLearningPathResponse;
- }),
- );
- }
-
- getRecommendation(learningPathId: number) {
- return this.httpClient.get(`${this.resourceURL}/learning-path/${learningPathId}/recommendation`, { observe: 'response' });
- }
-
getHealthStatusForCourse(courseId: number) {
return this.httpClient.get(`${this.resourceURL}/courses/${courseId}/learning-path-health`, { observe: 'response' });
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
index f44050de7416..4d1a254a674f 100644
--- a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
@@ -1,87 +1,12 @@
import { NgModule } from '@angular/core';
-import { RouterModule, Routes } from '@angular/router';
import { ArtemisSharedModule } from 'app/shared/shared.module';
import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { LearningPathManagementComponent } from 'app/course/learning-paths/learning-path-management/learning-path-management.component';
-import { LearningPathContainerComponent } from 'app/course/learning-paths/participate/learning-path-container.component';
-import { Authority } from 'app/shared/constants/authority.constants';
-import { UserRouteAccessService } from 'app/core/auth/user-route-access-service';
-import { LearningPathGraphSidebarComponent } from 'app/course/learning-paths/participate/learning-path-graph-sidebar.component';
-import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
-import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph-node.component';
-import { NgxGraphModule } from '@swimlane/ngx-graph';
-import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
-import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
-import { LearningPathProgressNavComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-nav.component';
-import { CompetencyNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component';
-import { ArtemisCompetenciesModule } from 'app/course/competencies/competency.module';
-import { LectureUnitNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component';
-import { ExerciseNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component';
-
-const routes: Routes = [
- {
- path: '',
- component: LearningPathContainerComponent,
- data: {
- authorities: [Authority.USER],
- pageTitle: 'overview.learningPath',
- },
- canActivate: [UserRouteAccessService],
- children: [
- {
- path: 'lecture-unit',
- pathMatch: 'full',
- children: [
- {
- path: '',
- pathMatch: 'full',
- loadChildren: () =>
- import('app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.module').then(
- (m) => m.ArtemisLearningPathLectureUnitViewModule,
- ),
- },
- ],
- },
- {
- path: 'exercise',
- pathMatch: 'full',
- children: [
- {
- path: '',
- pathMatch: 'full',
- loadChildren: () => import('app/overview/exercise-details/course-exercise-details.module').then((m) => m.CourseExerciseDetailsModule),
- },
- ],
- },
- ],
- },
-];
@NgModule({
- imports: [
- ArtemisSharedModule,
- FormsModule,
- ReactiveFormsModule,
- ArtemisSharedComponentModule,
- NgxGraphModule,
- RouterModule.forChild(routes),
- ArtemisLectureUnitsModule,
- ArtemisCompetenciesModule,
- ],
- declarations: [
- LearningPathContainerComponent,
- LearningPathManagementComponent,
- LearningPathProgressModalComponent,
- LearningPathProgressNavComponent,
- LearningPathGraphSidebarComponent,
- LearningPathGraphComponent,
- LearningPathGraphNodeComponent,
- LearningPathProgressModalComponent,
- CompetencyNodeDetailsComponent,
- LectureUnitNodeDetailsComponent,
- ExerciseNodeDetailsComponent,
- ],
- exports: [LearningPathContainerComponent],
+ imports: [ArtemisSharedModule, FormsModule, ReactiveFormsModule, ArtemisSharedComponentModule],
+ declarations: [LearningPathManagementComponent],
+ exports: [],
})
export class ArtemisLearningPathsModule {}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
deleted file mode 100644
index 9c25931bcb99..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.html
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
- No task selected
-
-
-
-
-
-
- Previous
-
-
- Next
-
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.scss b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.scss
deleted file mode 100644
index 7b9a664c9b4b..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.main-container {
- width: 100%;
- margin-left: 0;
-}
-
-.course-info-bar {
- margin: 0 !important;
-}
-
-.tab-bar-exercise-details {
- margin-left: 0 !important;
- margin-right: 0 !important;
-}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
deleted file mode 100644
index 2e4277c408cb..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-container.component.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
-import { ActivatedRoute, Router } from '@angular/router';
-import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
-import { Exercise } from 'app/entities/exercise.model';
-import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
-import { Lecture } from 'app/entities/lecture.model';
-import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
-import { NgxLearningPathNode, NodeType, RecommendationType } from 'app/entities/competency/learning-path.model';
-import { LectureService } from 'app/lecture/lecture.service';
-import { onError } from 'app/shared/util/global.utils';
-import { HttpErrorResponse } from '@angular/common/http';
-import { AlertService } from 'app/core/util/alert.service';
-import { LearningPathLectureUnitViewComponent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
-import { CourseExerciseDetailsComponent } from 'app/overview/exercise-details/course-exercise-details.component';
-import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
-import { ExerciseEntry, LearningPathHistoryStorageService, LectureUnitEntry } from 'app/course/learning-paths/participate/learning-path-history-storage.service';
-
-@Component({
- selector: 'jhi-learning-path-container',
- styleUrls: ['./learning-path-container.component.scss'],
- templateUrl: './learning-path-container.component.html',
- encapsulation: ViewEncapsulation.None,
-})
-export class LearningPathContainerComponent implements OnInit {
- @Input() courseId: number;
- learningPathId: number;
-
- learningObjectId: number;
- lectureId?: number;
- lecture: Lecture | undefined;
- lectureUnit: LectureUnit | undefined;
- exercise: Exercise | undefined;
-
- // icons
- faChevronLeft = faChevronLeft;
- faChevronRight = faChevronRight;
-
- constructor(
- private router: Router,
- private activatedRoute: ActivatedRoute,
- private alertService: AlertService,
- private learningPathService: LearningPathService,
- private lectureService: LectureService,
- private exerciseService: ExerciseService,
- private learningPathHistoryStorageService: LearningPathHistoryStorageService,
- ) {}
-
- ngOnInit() {
- if (!this.courseId) {
- this.activatedRoute.parent!.parent!.params.subscribe((params) => {
- this.courseId = params['courseId'];
- });
- }
- this.learningPathService.getLearningPathId(this.courseId).subscribe((learningPathIdResponse) => {
- this.learningPathId = learningPathIdResponse.body!;
- });
- }
-
- onNextTask() {
- if (this.lectureUnit?.id) {
- this.learningPathHistoryStorageService.storeLectureUnit(this.learningPathId, this.lectureId!, this.lectureUnit.id);
- } else if (this.exercise?.id) {
- this.learningPathHistoryStorageService.storeExercise(this.learningPathId, this.exercise.id);
- }
- this.undefineAll();
- this.learningPathService.getRecommendation(this.learningPathId).subscribe((recommendationResponse) => {
- const recommendation = recommendationResponse.body!;
- this.learningObjectId = recommendation.learningObjectId;
- this.lectureId = recommendation.lectureId;
- if (recommendation.type == RecommendationType.LECTURE_UNIT) {
- this.loadLectureUnit();
- } else if (recommendation.type === RecommendationType.EXERCISE) {
- this.loadExercise();
- }
- });
- }
-
- undefineAll() {
- this.lecture = undefined;
- this.lectureUnit = undefined;
- this.exercise = undefined;
- }
-
- onPrevTask() {
- this.undefineAll();
- if (this.learningPathHistoryStorageService.hasPrevious(this.learningPathId)) {
- const entry = this.learningPathHistoryStorageService.getPrevious(this.learningPathId);
- if (entry instanceof LectureUnitEntry) {
- this.learningObjectId = entry.lectureUnitId;
- this.lectureId = entry.lectureId;
- this.loadLectureUnit();
- } else if (entry instanceof ExerciseEntry) {
- this.learningObjectId = entry.exerciseId;
- this.loadExercise();
- }
- }
- }
-
- loadLectureUnit() {
- this.lectureService.findWithDetails(this.lectureId!).subscribe({
- next: (findLectureResult) => {
- this.lecture = findLectureResult.body!;
- if (this.lecture?.lectureUnits) {
- this.lectureUnit = this.lecture.lectureUnits.find((lectureUnit) => lectureUnit.id === this.learningObjectId);
- }
- },
- error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
- });
- this.router.navigate(['lecture-unit'], { relativeTo: this.activatedRoute });
- }
-
- loadExercise() {
- this.exerciseService.getExerciseDetails(this.learningObjectId).subscribe({
- next: (exerciseResponse) => {
- this.exercise = exerciseResponse.body!;
- },
- error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
- });
- this.router.navigate(['exercise'], { relativeTo: this.activatedRoute });
- }
-
- /**
- * This function gets called if the router outlet gets activated. This is
- * used only for the LearningPathLectureUnitViewComponent
- * @param instance The component instance
- */
- onChildActivate(instance: LearningPathLectureUnitViewComponent | CourseExerciseDetailsComponent) {
- if (instance instanceof LearningPathLectureUnitViewComponent) {
- this.setupLectureUnitView(instance);
- } else {
- this.setupExerciseView(instance);
- }
- }
-
- setupLectureUnitView(instance: LearningPathLectureUnitViewComponent) {
- if (this.lecture) {
- instance.lecture = this.lecture;
- instance.lectureUnit = this.lectureUnit!;
- }
- }
-
- setupExerciseView(instance: CourseExerciseDetailsComponent) {
- if (this.exercise) {
- instance.courseId = this.courseId;
- instance.exerciseId = this.learningObjectId;
- }
- }
-
- hasPrevious() {
- return this.learningPathHistoryStorageService.hasPrevious(this.learningPathId);
- }
-
- onNodeClicked(node: NgxLearningPathNode) {
- if (node.type === NodeType.LECTURE_UNIT || node.type === NodeType.EXERCISE) {
- if (this.lectureUnit?.id) {
- this.learningPathHistoryStorageService.storeLectureUnit(this.learningPathId, this.lectureId!, this.lectureUnit.id);
- } else if (this.exercise?.id) {
- this.learningPathHistoryStorageService.storeExercise(this.learningPathId, this.exercise.id);
- }
- this.undefineAll();
- this.learningObjectId = node.linkedResource!;
- this.lectureId = node.linkedResourceParent;
- if (node.type === NodeType.LECTURE_UNIT) {
- this.loadLectureUnit();
- } else if (node.type === NodeType.EXERCISE) {
- this.loadExercise();
- }
- }
- }
-}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html
deleted file mode 100644
index 0c64d339f083..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss
deleted file mode 100644
index ee0763fe5218..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.scss
+++ /dev/null
@@ -1,77 +0,0 @@
-@import 'src/main/webapp/content/scss/artemis-variables';
-
-$draggable-width: 15px;
-$graph-min-width: 215px;
-
-.learning-path-sidebar {
- .expanded-graph {
- display: flex;
- width: calc(#{$draggable-width} + #{$graph-min-width});
- min-height: 500px;
- margin-left: auto;
-
- .draggable-right {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- min-width: $draggable-width;
- }
-
- .card {
- width: inherit;
- min-width: $graph-min-width;
-
- .card-header {
- display: inline-flex;
- justify-content: space-between;
- align-items: center;
- cursor: pointer;
-
- .card-title {
- display: flex;
- }
-
- .row > .col-auto:last-child {
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
- }
-
- .card-body {
- padding: 0;
- }
- }
- }
-
- .collapsed-graph {
- display: flex;
- width: 38px;
- justify-content: space-between;
- flex-flow: column;
- cursor: pointer;
-
- span {
- writing-mode: vertical-lr;
- transform: rotate(180deg);
- margin: auto;
- }
-
- .expand-graph-icon {
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
- place-self: center;
- }
- }
-
- @media screen and (max-width: 992px) {
- .expanded-graph {
- width: 94vw;
-
- .draggable-right {
- display: none;
- }
- }
- }
-}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
deleted file mode 100644
index 676656f48bca..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-graph-sidebar.component.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
-import interact from 'interactjs';
-import { faChevronLeft, faChevronRight, faGripLinesVertical, faNetworkWired } from '@fortawesome/free-solid-svg-icons';
-import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
-import { NgxLearningPathNode } from 'app/entities/competency/learning-path.model';
-
-@Component({
- selector: 'jhi-learning-path-graph-sidebar',
- styleUrls: ['./learning-path-graph-sidebar.component.scss'],
- templateUrl: './learning-path-graph-sidebar.component.html',
-})
-export class LearningPathGraphSidebarComponent implements AfterViewInit {
- @Input() courseId: number;
- @Input() learningPathId: number;
- collapsed: boolean;
- // Icons
- faChevronLeft = faChevronLeft;
- faChevronRight = faChevronRight;
- faGripLinesVertical = faGripLinesVertical;
- faNetworkWired = faNetworkWired;
-
- @ViewChild(`learningPathGraphComponent`, { static: false })
- learningPathGraphComponent: LearningPathGraphComponent;
-
- @Output() nodeClicked: EventEmitter = new EventEmitter();
-
- ngAfterViewInit(): void {
- // allows the sidebar to be resized towards the right-hand side
- interact('.expanded-graph')
- .resizable({
- edges: { left: false, right: '.draggable-right', bottom: false, top: false },
- modifiers: [
- // Set maximum width of the sidebar
- interact.modifiers!.restrictSize({
- min: { width: 230, height: 0 },
- max: { width: 500, height: 4000 },
- }),
- ],
- inertia: true,
- })
- .on('resizestart', (event: any) => {
- event.target.classList.add('card-resizable');
- })
- .on('resizeend', (event: any) => {
- event.target.classList.remove('card-resizable');
- this.learningPathGraphComponent.onResize();
- })
- .on('resizemove', (event: any) => {
- const target = event.target;
- target.style.width = event.rect.width + 'px';
- });
- }
-}
diff --git a/src/main/webapp/app/course/learning-paths/participate/learning-path-history-storage.service.ts b/src/main/webapp/app/course/learning-paths/participate/learning-path-history-storage.service.ts
deleted file mode 100644
index cc3baab7cc6c..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/learning-path-history-storage.service.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { Injectable } from '@angular/core';
-
-/**
- * This service is used to store the histories of learning path participation for the currently logged-in user.
- */
-@Injectable({ providedIn: 'root' })
-export class LearningPathHistoryStorageService {
- private readonly learningPathHistories: Map = new Map();
-
- /**
- * Stores the lecture unit in the learning path's history.
- *
- * @param learningPathId the id of the learning path to which the new entry should be added
- * @param lectureId the id of the lecture, the lecture unit belongs to
- * @param lectureUnitId the id of the lecture unit
- */
- storeLectureUnit(learningPathId: number, lectureId: number, lectureUnitId: number) {
- this.store(learningPathId, new LectureUnitEntry(lectureId, lectureUnitId));
- }
-
- /**
- * Stores the exercise in the learning path's history.
- *
- * @param learningPathId the id of the learning path to which the new entry should be added
- * @param exerciseId the id of the exercise
- */
- storeExercise(learningPathId: number, exerciseId: number) {
- this.store(learningPathId, new ExerciseEntry(exerciseId));
- }
- private store(learningPathId: number, entry: HistoryEntry) {
- if (!entry) {
- return;
- }
- if (!this.learningPathHistories.has(learningPathId)) {
- this.learningPathHistories.set(learningPathId, []);
- }
- this.learningPathHistories.get(learningPathId)!.push(entry);
- }
-
- /**
- * Returns if the learning path's history stores at least one entry.
- *
- * @param learningPathId the id of the learning path for which the history should be checked
- */
- hasPrevious(learningPathId: number): boolean {
- if (this.learningPathHistories.has(learningPathId)) {
- return this.learningPathHistories.get(learningPathId)!.length !== 0;
- }
- return false;
- }
-
- /**
- * Gets and removes the latest stored entry from the learning path's history.
- *
- * @param learningPathId
- */
- getPrevious(learningPathId: number) {
- if (!this.hasPrevious(learningPathId)) {
- return null;
- }
- return this.learningPathHistories.get(learningPathId)!.pop();
- }
-}
-
-export abstract class HistoryEntry {}
-
-export class LectureUnitEntry extends HistoryEntry {
- lectureUnitId: number;
- lectureId: number;
-
- constructor(lectureId: number, lectureUnitId: number) {
- super();
- this.lectureId = lectureId;
- this.lectureUnitId = lectureUnitId;
- }
-}
-
-export class ExerciseEntry extends HistoryEntry {
- readonly exerciseId: number;
-
- constructor(exerciseId: number) {
- super();
- this.exerciseId = exerciseId;
- }
-}
diff --git a/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.html b/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.html
deleted file mode 100644
index 8002222110b2..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
diff --git a/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.ts b/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.ts
deleted file mode 100644
index 5ad133a7b589..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Component, Input } from '@angular/core';
-import { HttpErrorResponse } from '@angular/common/http';
-import { LectureUnit, LectureUnitType } from 'app/entities/lecture-unit/lectureUnit.model';
-import { onError } from 'app/shared/util/global.utils';
-import { Lecture } from 'app/entities/lecture.model';
-import { LectureUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/lectureUnit.service';
-import { AlertService } from 'app/core/util/alert.service';
-import { isCommunicationEnabled, isMessagingEnabled } from 'app/entities/course.model';
-import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
-
-export interface LectureUnitCompletionEvent {
- lectureUnit: LectureUnit;
- completed: boolean;
-}
-
-@Component({
- selector: 'jhi-learning-path-lecture-unit-view',
- templateUrl: './learning-path-lecture-unit-view.component.html',
-})
-export class LearningPathLectureUnitViewComponent {
- @Input() lecture: Lecture;
- @Input() lectureUnit: LectureUnit;
- readonly LectureUnitType = LectureUnitType;
-
- discussionComponent?: DiscussionSectionComponent;
-
- constructor(private lectureUnitService: LectureUnitService, private alertService: AlertService) {}
-
- completeLectureUnit(event: LectureUnitCompletionEvent): void {
- if (this.lecture && event.lectureUnit.visibleToStudents && event.lectureUnit.completed !== event.completed) {
- this.lectureUnitService.setCompletion(event.lectureUnit.id!, this.lecture.id!, event.completed).subscribe({
- next: () => {
- event.lectureUnit.completed = event.completed;
- },
- error: (res: HttpErrorResponse) => onError(this.alertService, res),
- });
- }
- }
-
- protected readonly isMessagingEnabled = isMessagingEnabled;
- protected readonly isCommunicationEnabled = isCommunicationEnabled;
-
- /**
- * This function gets called if the router outlet gets activated. This is
- * used only for the DiscussionComponent
- * @param instance The component instance
- */
- onChildActivate(instance: DiscussionSectionComponent) {
- this.discussionComponent = instance; // save the reference to the component instance
- if (this.lecture) {
- instance.lecture = this.lecture;
- instance.isCommunicationPage = false;
- }
- }
-}
diff --git a/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.module.ts b/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.module.ts
deleted file mode 100644
index 33dc55941bda..000000000000
--- a/src/main/webapp/app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.module.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { NgModule } from '@angular/core';
-import { RouterModule, Routes } from '@angular/router';
-import { LearningPathLectureUnitViewComponent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
-import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
-import { ArtemisSharedModule } from 'app/shared/shared.module';
-import { Authority } from 'app/shared/constants/authority.constants';
-import { UserRouteAccessService } from 'app/core/auth/user-route-access-service';
-
-const routes: Routes = [
- {
- path: '',
- component: LearningPathLectureUnitViewComponent,
- data: {
- authorities: [Authority.USER],
- pageTitle: 'overview.learningPath',
- },
- canActivate: [UserRouteAccessService],
- children: [
- {
- path: '',
- pathMatch: 'full',
- loadChildren: () => import('app/overview/discussion-section/discussion-section.module').then((m) => m.DiscussionSectionModule),
- },
- ],
- },
-];
-
-@NgModule({
- imports: [ArtemisSharedModule, RouterModule.forChild(routes), ArtemisLectureUnitsModule],
- declarations: [LearningPathLectureUnitViewComponent],
- exports: [LearningPathLectureUnitViewComponent],
-})
-export class ArtemisLearningPathLectureUnitViewModule {}
diff --git a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html
index 22d9121ccc06..632188f51f4d 100644
--- a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html
+++ b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html
@@ -42,7 +42,13 @@
Competencies
-
+
Learning Paths
diff --git a/src/main/webapp/app/overview/course-overview.component.html b/src/main/webapp/app/overview/course-overview.component.html
index b1bbce889f7e..5b34697f6bc8 100644
--- a/src/main/webapp/app/overview/course-overview.component.html
+++ b/src/main/webapp/app/overview/course-overview.component.html
@@ -51,10 +51,6 @@
Competencies
-
-
- Learning Path
-
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 2d6089cec109..5cb2173e8cc3 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -304,28 +304,6 @@ void testGetLearningPathsOnPageForCourseExactlyStudent() throws Exception {
assertThat(result.getResultsOnPage()).hasSize(1);
}
- @Test
- @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
- void getLearningPathId() throws Exception {
- course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
- final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
- final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
- final var result = request.get("/api/courses/" + course.getId() + "/learning-path-id", HttpStatus.OK, Long.class);
- assertThat(result).isEqualTo(learningPath.getId());
- }
-
- @Test
- @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
- void getLearningPathIdNotExisting() throws Exception {
- course.setLearningPathsEnabled(true);
- course = courseRepository.save(course);
- var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
- student = userRepository.findWithLearningPathsByIdElseThrow(student.getId());
- learningPathRepository.deleteAll(student.getLearningPaths());
- final var result = request.get("/api/courses/" + course.getId() + "/learning-path-id", HttpStatus.OK, Long.class);
- assertThat(result).isNotNull();
- }
-
private static Stream addCompetencyToLearningPathsOnCreateAndImportCompetencyTestProvider() {
final Function createCall = (reference) -> {
try {
@@ -398,80 +376,6 @@ void testUpdateLearningPathProgress() throws Exception {
assertThat(learningPath.getProgress()).as("contains completed competency").isNotEqualTo(0);
}
- @Test
- @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
- void testGetNgxLearningPathForLearningPathsDisabled() throws Exception {
- course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
- final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
- final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
- course.setLearningPathsEnabled(false);
- courseRepository.save(course);
- request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.BAD_REQUEST, NgxLearningPathDTO.class);
- }
-
- @Test
- @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER")
- void testGetNgxLearningPathForOtherStudent() throws Exception {
- course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
- final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
- final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
- request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.FORBIDDEN, NgxLearningPathDTO.class);
- }
-
- /**
- * This only tests if the end point successfully retrieves the graph representation. The correctness of the response is tested in LearningPathServiceTest.
- *
- * @throws Exception the request failed
- * @see de.tum.in.www1.artemis.service.LearningPathServiceTest
- */
- @Test
- @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
- void testGetNgxLearningPathAsStudent() throws Exception {
- course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
- final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
- final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
- request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.OK, NgxLearningPathDTO.class);
- }
-
- /**
- * This only tests if the end point successfully retrieves the graph representation. The correctness of the response is tested in LearningPathServiceTest.
- *
- * @throws Exception the request failed
- * @see de.tum.in.www1.artemis.service.LearningPathServiceTest
- */
- @Test
- @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
- void testGetNgxLearningPathAsInstructor() throws Exception {
- course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
- final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
- final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
- request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.OK, NgxLearningPathDTO.class);
- }
-
- @Test
- @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER")
- void testGetRecommendationAsOtherUser() throws Exception {
- course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
- final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
- final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
- request.get("/api/learning-path/" + learningPath.getId() + "/recommendation", HttpStatus.FORBIDDEN, LearningPathRecommendationDTO.class);
- }
-
- /**
- * This only tests if the end point successfully retrieves the recommendation. The correctness of the response is tested in LearningPathServiceTest.
- *
- * @throws Exception the request failed
- * @see de.tum.in.www1.artemis.service.LearningPathServiceTest
- */
- @Test
- @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
- void testGetRecommendationAsOwner() throws Exception {
- course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
- final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
- final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
- request.get("/api/learning-path/" + learningPath.getId() + "/recommendation", HttpStatus.OK, LearningPathRecommendationDTO.class);
- }
-
/**
* This only tests if the end point successfully retrieves the health status. The correctness of the health status is tested in LearningPathServiceTest.
*
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 0a94115f6c8c..12cb456454e3 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -4,7 +4,6 @@
import java.time.ZonedDateTime;
import java.util.*;
-import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -12,24 +11,14 @@
import org.springframework.beans.factory.annotation.Autowired;
import de.tum.in.www1.artemis.AbstractSpringIntegrationBambooBitbucketJiraTest;
-import de.tum.in.www1.artemis.competency.CompetencyUtilService;
import de.tum.in.www1.artemis.competency.LearningPathUtilService;
import de.tum.in.www1.artemis.course.CourseFactory;
import de.tum.in.www1.artemis.course.CourseUtilService;
import de.tum.in.www1.artemis.domain.Course;
-import de.tum.in.www1.artemis.domain.Exercise;
-import de.tum.in.www1.artemis.domain.competency.Competency;
-import de.tum.in.www1.artemis.domain.competency.CompetencyRelation;
-import de.tum.in.www1.artemis.domain.competency.LearningPath;
-import de.tum.in.www1.artemis.domain.lecture.LectureUnit;
-import de.tum.in.www1.artemis.exercise.programmingexercise.ProgrammingExerciseUtilService;
-import de.tum.in.www1.artemis.lecture.LectureUtilService;
import de.tum.in.www1.artemis.repository.CourseRepository;
-import de.tum.in.www1.artemis.repository.LearningPathRepository;
import de.tum.in.www1.artemis.security.SecurityUtils;
import de.tum.in.www1.artemis.user.UserUtilService;
import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathHealthDTO;
-import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
class LearningPathServiceTest extends AbstractSpringIntegrationBambooBitbucketJiraTest {
@@ -44,18 +33,6 @@ class LearningPathServiceTest extends AbstractSpringIntegrationBambooBitbucketJi
@Autowired
private CourseUtilService courseUtilService;
- @Autowired
- private CompetencyUtilService competencyUtilService;
-
- @Autowired
- private LearningPathRepository learningPathRepository;
-
- @Autowired
- private LectureUtilService lectureUtilService;
-
- @Autowired
- private ProgrammingExerciseUtilService programmingExerciseUtilService;
-
@Autowired
UserUtilService userUtilService;
@@ -64,20 +41,6 @@ class LearningPathServiceTest extends AbstractSpringIntegrationBambooBitbucketJi
private Course course;
- private void generateAndAssert(NgxLearningPathDTO expected) {
- LearningPath learningPath = learningPathUtilService.createLearningPathInCourse(course);
- learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersByIdElseThrow(learningPath.getId());
- NgxLearningPathDTO actual = learningPathService.generateNgxRepresentation(learningPath);
- assertThat(actual).isNotNull();
- assertNgxRepEquals(actual, expected);
- }
-
- private void assertNgxRepEquals(NgxLearningPathDTO was, NgxLearningPathDTO expected) {
- assertThat(was.nodes()).as("correct nodes").containsExactlyInAnyOrderElementsOf(expected.nodes());
- assertThat(was.edges()).as("correct edges").containsExactlyInAnyOrderElementsOf(expected.edges());
- assertThat(was.clusters()).as("correct clusters").containsExactlyInAnyOrderElementsOf(expected.clusters());
- }
-
@BeforeEach
void setAuthorizationForRepositoryRequests() {
SecurityUtils.setAuthorizationObject();
@@ -88,214 +51,6 @@ void setup() {
course = courseUtilService.createCourse();
}
- @Nested
- class GenerateNgxRepresentationBaseTest {
-
- @Test
- void testEmptyLearningPath() {
- NgxLearningPathDTO expected = new NgxLearningPathDTO(Set.of(), Set.of(), Set.of());
- generateAndAssert(expected);
- }
-
- @Test
- void testEmptyCompetency() {
- final var competency = competencyUtilService.createCompetency(course);
- final var startNodeId = LearningPathService.getCompetencyStartNodeId(competency.getId());
- final var endNodeId = LearningPathService.getCompetencyEndNodeId(competency.getId());
- Set expectedNodes = getExpectedNodesOfEmptyCompetency(competency);
- Set expectedEdges = Set.of(new NgxLearningPathDTO.Edge(LearningPathService.getDirectEdgeId(competency.getId()), startNodeId, endNodeId));
- Set expectedClusters = Set
- .of(new NgxLearningPathDTO.Cluster(String.valueOf(competency.getId()), competency.getTitle(), Set.of(startNodeId, endNodeId)));
- NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
- generateAndAssert(expected);
- }
-
- @Test
- void testCompetencyWithLectureUnitAndExercise() {
- var competency = competencyUtilService.createCompetency(course);
- var lecture = lectureUtilService.createLecture(course, ZonedDateTime.now());
- final var lectureUnit = lectureUtilService.createTextUnit();
- lectureUtilService.addLectureUnitsToLecture(lecture, List.of(lectureUnit));
- competencyUtilService.linkLectureUnitToCompetency(competency, lectureUnit);
- final var exercise = programmingExerciseUtilService.addProgrammingExerciseToCourse(course, false);
- competencyUtilService.linkExerciseToCompetency(competency, exercise);
- final var startNodeId = LearningPathService.getCompetencyStartNodeId(competency.getId());
- final var endNodeId = LearningPathService.getCompetencyEndNodeId(competency.getId());
- Set expectedNodes = getExpectedNodesOfEmptyCompetency(competency);
- expectedNodes.add(getNodeForLectureUnit(competency, lectureUnit));
- expectedNodes.add(getNodeForExercise(competency, exercise));
- Set expectedEdges = Set.of(
- new NgxLearningPathDTO.Edge(LearningPathService.getLectureUnitInEdgeId(competency.getId(), lectureUnit.getId()), startNodeId,
- LearningPathService.getLectureUnitNodeId(competency.getId(), lectureUnit.getId())),
- new NgxLearningPathDTO.Edge(LearningPathService.getLectureUnitOutEdgeId(competency.getId(), lectureUnit.getId()),
- LearningPathService.getLectureUnitNodeId(competency.getId(), lectureUnit.getId()), endNodeId),
- new NgxLearningPathDTO.Edge(LearningPathService.getExerciseInEdgeId(competency.getId(), exercise.getId()), startNodeId,
- LearningPathService.getExerciseNodeId(competency.getId(), exercise.getId())),
- new NgxLearningPathDTO.Edge(LearningPathService.getExerciseOutEdgeId(competency.getId(), exercise.getId()),
- LearningPathService.getExerciseNodeId(competency.getId(), exercise.getId()), endNodeId));
- Set expectedClusters = Set.of(new NgxLearningPathDTO.Cluster(String.valueOf(competency.getId()), competency.getTitle(),
- expectedNodes.stream().map(NgxLearningPathDTO.Node::id).collect(Collectors.toSet())));
- NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
- generateAndAssert(expected);
- }
-
- @Test
- void testMultipleCompetencies() {
- Competency[] competencies = { competencyUtilService.createCompetency(course), competencyUtilService.createCompetency(course),
- competencyUtilService.createCompetency(course) };
- String[] startNodeIds = Arrays.stream(competencies).map(Competency::getId).map(LearningPathService::getCompetencyStartNodeId).toArray(String[]::new);
- String[] endNodeIds = Arrays.stream(competencies).map(Competency::getId).map(LearningPathService::getCompetencyEndNodeId).toArray(String[]::new);
- Set expectedNodes = new HashSet<>();
- Set expectedEdges = new HashSet<>();
- Set expectedClusters = new HashSet<>();
- for (int i = 0; i < competencies.length; i++) {
- expectedNodes.addAll(getExpectedNodesOfEmptyCompetency(competencies[i]));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getDirectEdgeId(competencies[i].getId()), startNodeIds[i], endNodeIds[i]));
- expectedClusters.add(new NgxLearningPathDTO.Cluster(String.valueOf(competencies[i].getId()), competencies[i].getTitle(), Set.of(startNodeIds[i], endNodeIds[i])));
- }
- NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
- generateAndAssert(expected);
- }
- }
-
- @Nested
- class GenerateNgxRepresentationRelationTest {
-
- private Competency competency1;
-
- private Competency competency2;
-
- private Set expectedNodes;
-
- Set expectedEdges;
-
- Set expectedClusters;
-
- @BeforeEach
- void setup() {
- competency1 = competencyUtilService.createCompetency(course);
- competency2 = competencyUtilService.createCompetency(course);
- expectedNodes = new HashSet<>();
- expectedEdges = new HashSet<>();
- expectedClusters = new HashSet<>();
- addExpectedComponentsForEmptyCompetencies(competency1, competency2);
- }
-
- void testSimpleRelation(CompetencyRelation.RelationType type) {
- competencyUtilService.addRelation(competency1, type, competency2);
- final var sourceNodeId = LearningPathService.getCompetencyEndNodeId(competency2.getId());
- final var targetNodeId = LearningPathService.getCompetencyStartNodeId(competency1.getId());
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getRelationEdgeId(sourceNodeId, targetNodeId), sourceNodeId, targetNodeId));
- NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
- generateAndAssert(expected);
- }
-
- @Test
- void testSingleRelates() {
- testSimpleRelation(CompetencyRelation.RelationType.RELATES);
- }
-
- @Test
- void testSingleAssumes() {
- testSimpleRelation(CompetencyRelation.RelationType.ASSUMES);
- }
-
- @Test
- void testSingleExtends() {
- testSimpleRelation(CompetencyRelation.RelationType.EXTENDS);
- }
-
- @Test
- void testSingleMatches() {
- competencyUtilService.addRelation(competency1, CompetencyRelation.RelationType.MATCHES, competency2);
- expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterStartNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_START, null, ""));
- expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterEndNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_END, null, ""));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency1.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
- LearningPathService.getCompetencyStartNodeId(competency1.getId())));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency1.getId()), LearningPathService.getCompetencyEndNodeId(competency1.getId()),
- LearningPathService.getMatchingClusterEndNodeId(0)));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency2.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
- LearningPathService.getCompetencyStartNodeId(competency2.getId())));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency2.getId()), LearningPathService.getCompetencyEndNodeId(competency2.getId()),
- LearningPathService.getMatchingClusterEndNodeId(0)));
- NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
- generateAndAssert(expected);
- }
-
- @Test
- void testMatchesTransitive() {
- var competency3 = competencyUtilService.createCompetency(course);
- addExpectedComponentsForEmptyCompetencies(competency3);
-
- competencyUtilService.addRelation(competency1, CompetencyRelation.RelationType.MATCHES, competency2);
- competencyUtilService.addRelation(competency2, CompetencyRelation.RelationType.MATCHES, competency3);
- expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterStartNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_START, null, ""));
- expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterEndNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_END, null, ""));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency1.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
- LearningPathService.getCompetencyStartNodeId(competency1.getId())));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency1.getId()), LearningPathService.getCompetencyEndNodeId(competency1.getId()),
- LearningPathService.getMatchingClusterEndNodeId(0)));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency2.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
- LearningPathService.getCompetencyStartNodeId(competency2.getId())));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency2.getId()), LearningPathService.getCompetencyEndNodeId(competency2.getId()),
- LearningPathService.getMatchingClusterEndNodeId(0)));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency3.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
- LearningPathService.getCompetencyStartNodeId(competency3.getId())));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency3.getId()), LearningPathService.getCompetencyEndNodeId(competency3.getId()),
- LearningPathService.getMatchingClusterEndNodeId(0)));
- NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
- generateAndAssert(expected);
- }
-
- private void addExpectedComponentsForEmptyCompetencies(Competency... competencies) {
- for (var competency : competencies) {
- expectedNodes.addAll(getExpectedNodesOfEmptyCompetency(competency));
- expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getDirectEdgeId(competency.getId()),
- LearningPathService.getCompetencyStartNodeId(competency.getId()), LearningPathService.getCompetencyEndNodeId(competency.getId())));
- expectedClusters.add(new NgxLearningPathDTO.Cluster(String.valueOf(competency.getId()), competency.getTitle(),
- Set.of(LearningPathService.getCompetencyStartNodeId(competency.getId()), LearningPathService.getCompetencyEndNodeId(competency.getId()))));
- }
- }
- }
-
- private static Set getExpectedNodesOfEmptyCompetency(Competency competency) {
- return new HashSet<>(Set.of(
- new NgxLearningPathDTO.Node(LearningPathService.getCompetencyStartNodeId(competency.getId()), NgxLearningPathDTO.NodeType.COMPETENCY_START, competency.getId(), ""),
- new NgxLearningPathDTO.Node(LearningPathService.getCompetencyEndNodeId(competency.getId()), NgxLearningPathDTO.NodeType.COMPETENCY_END, competency.getId(), "")));
- }
-
- private static NgxLearningPathDTO.Node getNodeForLectureUnit(Competency competency, LectureUnit lectureUnit) {
- return new NgxLearningPathDTO.Node(LearningPathService.getLectureUnitNodeId(competency.getId(), lectureUnit.getId()), NgxLearningPathDTO.NodeType.LECTURE_UNIT,
- lectureUnit.getId(), lectureUnit.getLecture().getId(), false, lectureUnit.getName());
- }
-
- private static NgxLearningPathDTO.Node getNodeForExercise(Competency competency, Exercise exercise) {
- return new NgxLearningPathDTO.Node(LearningPathService.getExerciseNodeId(competency.getId(), exercise.getId()), NgxLearningPathDTO.NodeType.EXERCISE, exercise.getId(),
- exercise.getTitle());
- }
-
- @Nested
- class RecommendationTest {
-
- @Test
- void testGetRecommendationEmpty() {
- competencyUtilService.createCompetency(course);
- LearningPath learningPath = learningPathUtilService.createLearningPathInCourse(course);
- learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(learningPath.getId());
- assertThat(learningPathService.getRecommendation(learningPath)).isEmpty();
- }
-
- @Test
- void testGetRecommendationNotEmpty() {
- var competency = competencyUtilService.createCompetency(course);
- final var lectureUnit = lectureUtilService.createTextUnit();
- competencyUtilService.linkLectureUnitToCompetency(competency, lectureUnit);
- LearningPath learningPath = learningPathUtilService.createLearningPathInCourse(course);
- learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(learningPath.getId());
- assertThat(learningPathService.getRecommendation(learningPath)).isPresent();
- }
- }
-
@Nested
class HeathCheckTest {
diff --git a/src/test/javascript/spec/component/admin/admin-feature-toggle.component.spec.ts b/src/test/javascript/spec/component/admin/admin-feature-toggle.component.spec.ts
index 877d96c57c75..941be287daff 100644
--- a/src/test/javascript/spec/component/admin/admin-feature-toggle.component.spec.ts
+++ b/src/test/javascript/spec/component/admin/admin-feature-toggle.component.spec.ts
@@ -31,7 +31,7 @@ describe('AdminFeatureToggleComponentTest', () => {
it('onInit test if features mapped successfully', () => {
expect(comp.availableToggles).toHaveLength(0);
comp.ngOnInit();
- expect(comp.availableToggles).toHaveLength(4);
+ expect(comp.availableToggles).toHaveLength(5);
});
it('onFeatureToggle test if feature disabled on toggle', () => {
diff --git a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
deleted file mode 100644
index 8371428d66ed..000000000000
--- a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ArtemisTestModule } from '../../../test.module';
-import { MockDirective } from 'ng-mocks';
-import { By } from '@angular/platform-browser';
-import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph-node.component';
-import { NgxLearningPathNode, NodeType } from 'app/entities/competency/learning-path.model';
-import { StickyPopoverDirective } from 'app/shared/sticky-popover/sticky-popover.directive';
-
-describe('LearningPathGraphNodeComponent', () => {
- let fixture: ComponentFixture;
- let comp: LearningPathGraphNodeComponent;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule],
- declarations: [LearningPathGraphNodeComponent, MockDirective(StickyPopoverDirective)],
- providers: [],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(LearningPathGraphNodeComponent);
- comp = fixture.componentInstance;
- });
- });
-
- it.each([NodeType.EXERCISE, NodeType.LECTURE_UNIT])('should display correct icon for completed learning object', (type: NodeType) => {
- comp.node = { id: '1', type: type, completed: true } as NgxLearningPathNode;
- fixture.detectChanges();
- expect(fixture.debugElement.query(By.css('#completed')).nativeElement).toBeTruthy();
- });
-
- it.each([NodeType.EXERCISE, NodeType.LECTURE_UNIT])('should display correct icon for not completed learning object', (type: NodeType) => {
- comp.node = { id: '1', type: type, completed: false } as NgxLearningPathNode;
- fixture.detectChanges();
- expect(fixture.debugElement.query(By.css('#not-completed')).nativeElement).toBeTruthy();
- });
-
- it.each([NodeType.COMPETENCY_START, NodeType.COMPETENCY_END, NodeType.COMPETENCY_START, NodeType.COMPETENCY_END])(
- 'should display correct icon for generic node',
- (type: NodeType) => {
- comp.node = { id: '1', type: type } as NgxLearningPathNode;
- fixture.detectChanges();
- expect(fixture.debugElement.query(By.css('#generic')).nativeElement).toBeTruthy();
- },
- );
-});
diff --git a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
deleted file mode 100644
index 32dcaacea7b2..000000000000
--- a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ArtemisTestModule } from '../../../test.module';
-import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
-import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
-
-describe('LearningPathGraphComponent', () => {
- let fixture: ComponentFixture;
- let comp: LearningPathGraphComponent;
- let learningPathService: LearningPathService;
- let getNgxLearningPathStub: jest.SpyInstance;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule],
- declarations: [LearningPathGraphComponent],
- providers: [],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(LearningPathGraphComponent);
- comp = fixture.componentInstance;
- learningPathService = TestBed.inject(LearningPathService);
- getNgxLearningPathStub = jest.spyOn(learningPathService, 'getNgxLearningPath');
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should load learning path from service', () => {
- comp.learningPathId = 1;
- fixture.detectChanges();
- expect(getNgxLearningPathStub).toHaveBeenCalledOnce();
- expect(getNgxLearningPathStub).toHaveBeenCalledWith(1);
- });
-
- it('should update, center, and zoom to fit on resize', () => {
- const updateStub = jest.spyOn(comp.update$, 'next');
- const centerStub = jest.spyOn(comp.center$, 'next');
- const zoomToFitStub = jest.spyOn(comp.zoomToFit$, 'next');
- fixture.detectChanges();
- comp.onResize();
- expect(updateStub).toHaveBeenCalledOnce();
- expect(updateStub).toHaveBeenCalledWith(true);
- expect(centerStub).toHaveBeenCalledOnce();
- expect(centerStub).toHaveBeenCalledWith(true);
- expect(zoomToFitStub).toHaveBeenCalledOnce();
- expect(zoomToFitStub).toHaveBeenCalledWith(true);
- });
-
- it('should zoom to fit and center on resize', () => {
- const zoomToFitStub = jest.spyOn(comp.zoomToFit$, 'next');
- const centerStub = jest.spyOn(comp.center$, 'next');
- fixture.detectChanges();
- comp.onCenterView();
- expect(zoomToFitStub).toHaveBeenCalledOnce();
- expect(zoomToFitStub).toHaveBeenCalledWith(true);
- expect(centerStub).toHaveBeenCalledOnce();
- expect(centerStub).toHaveBeenCalledWith(true);
- });
-});
diff --git a/src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts
deleted file mode 100644
index 848c9a2cefe9..000000000000
--- a/src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ArtemisTestModule } from '../../../../test.module';
-import { MockComponent, MockPipe } from 'ng-mocks';
-import { of } from 'rxjs';
-import { HttpResponse } from '@angular/common/http';
-import { CompetencyNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component';
-import { Competency, CompetencyProgress, CompetencyTaxonomy } from 'app/entities/competency.model';
-import { CompetencyService } from 'app/course/competencies/competency.service';
-import { CompetencyRingsComponent } from 'app/course/competencies/competency-rings/competency-rings.component';
-import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
-import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module';
-
-describe('CompetencyNodeDetailsComponent', () => {
- let fixture: ComponentFixture;
- let comp: CompetencyNodeDetailsComponent;
- let competencyService: CompetencyService;
- let findByIdStub: jest.SpyInstance;
- let competency: Competency;
- let competencyProgress: CompetencyProgress;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule, NgbTooltipMocksModule],
- declarations: [CompetencyNodeDetailsComponent, MockComponent(CompetencyRingsComponent), MockPipe(ArtemisTranslatePipe)],
- providers: [],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(CompetencyNodeDetailsComponent);
- comp = fixture.componentInstance;
- competency = new Competency();
- competency.id = 2;
- competency.title = 'Some arbitrary title';
- competency.description = 'Some description';
- competency.taxonomy = CompetencyTaxonomy.ANALYZE;
- competency.masteryThreshold = 50;
- competencyProgress = new CompetencyProgress();
- competencyProgress.progress = 80;
- competencyProgress.confidence = 70;
- competency.userProgress = [competencyProgress];
-
- competencyService = TestBed.inject(CompetencyService);
- findByIdStub = jest.spyOn(competencyService, 'findById').mockReturnValue(of(new HttpResponse({ body: competency })));
- comp.courseId = 1;
- comp.competencyId = competency.id;
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should load competency on init', () => {
- fixture.detectChanges();
- expect(findByIdStub).toHaveBeenCalledOnce();
- expect(findByIdStub).toHaveBeenCalledWith(competency.id, 1);
- expect(comp.competency).toEqual(competency);
- expect(comp.competencyProgress).toEqual(competencyProgress);
- });
-
- it('should default progress to zero if empty', () => {
- competency.userProgress = undefined;
- fixture.detectChanges();
- expect(findByIdStub).toHaveBeenCalledOnce();
- expect(findByIdStub).toHaveBeenCalledWith(competency.id, 1);
- expect(comp.competency).toEqual(competency);
- expect(comp.competencyProgress).toEqual({ confidence: 0, progress: 0 } as CompetencyProgress);
- });
-});
diff --git a/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
deleted file mode 100644
index 839aeccf50de..000000000000
--- a/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ArtemisTestModule } from '../../../../test.module';
-import { MockPipe } from 'ng-mocks';
-import { of } from 'rxjs';
-import { HttpResponse } from '@angular/common/http';
-import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
-import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module';
-import { ExerciseNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component';
-import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
-import { Exercise } from 'app/entities/exercise.model';
-import { TextExercise } from 'app/entities/text-exercise.model';
-
-describe('ExerciseNodeDetailsComponent', () => {
- let fixture: ComponentFixture;
- let comp: ExerciseNodeDetailsComponent;
- let exerciseService: ExerciseService;
- let findStub: jest.SpyInstance;
- let exercise: Exercise;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule, NgbTooltipMocksModule],
- declarations: [ExerciseNodeDetailsComponent, MockPipe(ArtemisTranslatePipe)],
- providers: [],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(ExerciseNodeDetailsComponent);
- comp = fixture.componentInstance;
- exercise = new TextExercise(undefined, undefined);
- exercise.id = 1;
- exercise.title = 'Some arbitrary title';
-
- exerciseService = TestBed.inject(ExerciseService);
- findStub = jest.spyOn(exerciseService, 'find').mockReturnValue(of(new HttpResponse({ body: exercise })));
- comp.exerciseId = exercise.id;
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should load exercise on init', () => {
- fixture.detectChanges();
- expect(findStub).toHaveBeenCalledOnce();
- expect(findStub).toHaveBeenCalledWith(exercise.id);
- expect(comp.exercise).toEqual(exercise);
- });
-});
diff --git a/src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts
deleted file mode 100644
index 0c7a44364c97..000000000000
--- a/src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ArtemisTestModule } from '../../../../test.module';
-import { MockPipe } from 'ng-mocks';
-import { of } from 'rxjs';
-import { HttpResponse } from '@angular/common/http';
-import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
-import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module';
-import { LectureUnitNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component';
-import { LectureService } from 'app/lecture/lecture.service';
-import { Lecture } from 'app/entities/lecture.model';
-import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
-import { TextUnit } from 'app/entities/lecture-unit/textUnit.model';
-
-describe('LectureUnitNodeDetailsComponent', () => {
- let fixture: ComponentFixture;
- let comp: LectureUnitNodeDetailsComponent;
- let lectureService: LectureService;
- let findWithDetailsStub: jest.SpyInstance;
- let lecture: Lecture;
- let lectureUnit: LectureUnit;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule, NgbTooltipMocksModule],
- declarations: [LectureUnitNodeDetailsComponent, MockPipe(ArtemisTranslatePipe)],
- providers: [],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(LectureUnitNodeDetailsComponent);
- comp = fixture.componentInstance;
- lecture = new Lecture();
- lecture.id = 1;
- lectureUnit = new TextUnit();
- lectureUnit.id = 2;
- lectureUnit.name = 'Some arbitrary name';
- lecture.lectureUnits = [lectureUnit];
-
- lectureService = TestBed.inject(LectureService);
- findWithDetailsStub = jest.spyOn(lectureService, 'findWithDetails').mockReturnValue(of(new HttpResponse({ body: lecture })));
- comp.lectureId = lecture.id;
- comp.lectureUnitId = lectureUnit.id;
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should load lecture unit on init', () => {
- fixture.detectChanges();
- expect(findWithDetailsStub).toHaveBeenCalledOnce();
- expect(findWithDetailsStub).toHaveBeenCalledWith(lecture.id);
- expect(comp.lecture).toEqual(lecture);
- expect(comp.lectureUnit).toEqual(lectureUnit);
- });
-});
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
deleted file mode 100644
index 1925146cac25..000000000000
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ArtemisTestModule } from '../../../test.module';
-import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
-import { MockComponent } from 'ng-mocks';
-import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
-import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
-import { By } from '@angular/platform-browser';
-import { LearningPathProgressNavComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-nav.component';
-import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
-
-describe('LearningPathProgressModalComponent', () => {
- let fixture: ComponentFixture;
- let comp: LearningPathProgressModalComponent;
- let activeModal: NgbActiveModal;
- let closeStub: jest.SpyInstance;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule, MockComponent(LearningPathGraphComponent), MockComponent(LearningPathProgressNavComponent)],
- declarations: [LearningPathProgressModalComponent],
- providers: [],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(LearningPathProgressModalComponent);
- comp = fixture.componentInstance;
- activeModal = TestBed.inject(NgbActiveModal);
- closeStub = jest.spyOn(activeModal, 'close');
- fixture.detectChanges();
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should display learning path graph if learning path is present', () => {
- comp.courseId = 2;
- comp.learningPath = { id: 1 } as LearningPathPageableSearchDTO;
- fixture.detectChanges();
- expect(fixture.debugElement.query(By.css('.graph')).nativeElement).toBeTruthy();
- });
-
- it('should correctly close modal', () => {
- comp.close();
- expect(closeStub).toHaveBeenCalledOnce();
- });
-});
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
deleted file mode 100644
index 437b2781c3ea..000000000000
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ArtemisTestModule } from '../../../test.module';
-import { By } from '@angular/platform-browser';
-import { LearningPathProgressNavComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-nav.component';
-import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
-import { UserNameAndLoginDTO } from 'app/core/user/user.model';
-import { MockPipe } from 'ng-mocks';
-import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
-import { NgbTooltipMocksModule } from '../../../helpers/mocks/directive/ngbTooltipMocks.module';
-
-describe('LearningPathProgressNavComponent', () => {
- let fixture: ComponentFixture;
- let comp: LearningPathProgressNavComponent;
- let onRefreshStub: jest.SpyInstance;
- let onCenterViewStub: jest.SpyInstance;
- let onCloseStub: jest.SpyInstance;
- let learningPath: LearningPathPageableSearchDTO;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule, NgbTooltipMocksModule],
- declarations: [LearningPathProgressNavComponent, MockPipe(ArtemisTranslatePipe)],
- providers: [],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(LearningPathProgressNavComponent);
- comp = fixture.componentInstance;
- onRefreshStub = jest.spyOn(comp.onRefresh, 'emit');
- onCenterViewStub = jest.spyOn(comp.onCenterView, 'emit');
- onCloseStub = jest.spyOn(comp.onClose, 'emit');
- learningPath = new LearningPathPageableSearchDTO();
- learningPath.user = new UserNameAndLoginDTO();
- learningPath.user.name = 'some arbitrary name';
- learningPath.user.login = 'somearbitrarylogin';
- comp.learningPath = learningPath;
- fixture.detectChanges();
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should create', () => {
- expect(fixture).toBeTruthy();
- expect(comp).toBeTruthy();
- });
-
- it('should emit refresh on click', () => {
- const button = fixture.debugElement.query(By.css('#refresh-button'));
- expect(button).not.toBeNull();
-
- button.nativeElement.click();
- expect(onRefreshStub).toHaveBeenCalledOnce();
- });
-
- it('should emit center view on click', () => {
- const button = fixture.debugElement.query(By.css('#center-button'));
- expect(button).not.toBeNull();
-
- button.nativeElement.click();
- expect(onCenterViewStub).toHaveBeenCalledOnce();
- });
-
- it('should emit close on click', () => {
- const button = fixture.debugElement.query(By.css('#close-button'));
- expect(button).not.toBeNull();
-
- button.nativeElement.click();
- expect(onCloseStub).toHaveBeenCalledOnce();
- });
-});
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
deleted file mode 100644
index b2bb637d73a7..000000000000
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-container.component.spec.ts
+++ /dev/null
@@ -1,243 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MockComponent, MockModule } from 'ng-mocks';
-import { By } from '@angular/platform-browser';
-import { ArtemisTestModule } from '../../../test.module';
-import { of } from 'rxjs';
-import { ActivatedRoute, RouterModule } from '@angular/router';
-import { HttpResponse } from '@angular/common/http';
-import { LearningPathContainerComponent } from 'app/course/learning-paths/participate/learning-path-container.component';
-import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
-import { LearningPathRecommendationDTO, NgxLearningPathNode, NodeType, RecommendationType } from 'app/entities/competency/learning-path.model';
-import { LectureService } from 'app/lecture/lecture.service';
-import { Lecture } from 'app/entities/lecture.model';
-import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
-import { Exercise } from 'app/entities/exercise.model';
-import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
-import { LearningPathGraphSidebarComponent } from 'app/course/learning-paths/participate/learning-path-graph-sidebar.component';
-import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model';
-import { TextExercise } from 'app/entities/text-exercise.model';
-import { LearningPathLectureUnitViewComponent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
-import { CourseExerciseDetailsComponent } from 'app/overview/exercise-details/course-exercise-details.component';
-import { ExerciseEntry, LearningPathHistoryStorageService, LectureUnitEntry } from 'app/course/learning-paths/participate/learning-path-history-storage.service';
-
-describe('LearningPathContainerComponent', () => {
- let fixture: ComponentFixture;
- let comp: LearningPathContainerComponent;
- let learningPathService: LearningPathService;
- let getLearningPathIdStub: jest.SpyInstance;
- const learningPathId = 1337;
- let getRecommendationStub: jest.SpyInstance;
- let lectureService: LectureService;
- let lecture: Lecture;
- let lectureUnit: LectureUnit;
- let findWithDetailsStub: jest.SpyInstance;
- let exerciseService: ExerciseService;
- let exercise: Exercise;
- let getExerciseDetailsStub: jest.SpyInstance;
- let historyService: LearningPathHistoryStorageService;
- let storeLectureUnitStub: jest.SpyInstance;
- let storeExerciseStub: jest.SpyInstance;
- let hasPreviousStub: jest.SpyInstance;
- let getPreviousStub: jest.SpyInstance;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule, MockComponent(LearningPathGraphSidebarComponent), MockModule(RouterModule)],
- declarations: [LearningPathContainerComponent],
- providers: [
- {
- provide: ActivatedRoute,
- useValue: {
- parent: {
- parent: {
- params: of({
- courseId: 1,
- }),
- },
- },
- },
- },
- ],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(LearningPathContainerComponent);
- comp = fixture.componentInstance;
- learningPathService = TestBed.inject(LearningPathService);
- getLearningPathIdStub = jest.spyOn(learningPathService, 'getLearningPathId').mockReturnValue(of(new HttpResponse({ body: learningPathId })));
- getRecommendationStub = jest.spyOn(learningPathService, 'getRecommendation');
-
- lectureUnit = new AttachmentUnit();
- lectureUnit.id = 3;
- lecture = new Lecture();
- lecture.id = 2;
- lecture.lectureUnits = [lectureUnit];
- lectureService = TestBed.inject(LectureService);
- findWithDetailsStub = jest.spyOn(lectureService, 'findWithDetails').mockReturnValue(of(new HttpResponse({ body: lecture })));
-
- exercise = new TextExercise(undefined, undefined);
- exercise.id = 4;
- exerciseService = TestBed.inject(ExerciseService);
- getExerciseDetailsStub = jest.spyOn(exerciseService, 'getExerciseDetails').mockReturnValue(of(new HttpResponse({ body: exercise })));
-
- historyService = TestBed.inject(LearningPathHistoryStorageService);
- storeLectureUnitStub = jest.spyOn(historyService, 'storeLectureUnit');
- storeExerciseStub = jest.spyOn(historyService, 'storeExercise');
- hasPreviousStub = jest.spyOn(historyService, 'hasPrevious');
- getPreviousStub = jest.spyOn(historyService, 'getPrevious');
-
- fixture.detectChanges();
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should initialize', () => {
- expect(comp.courseId).toBe(1);
- expect(getLearningPathIdStub).toHaveBeenCalled();
- expect(getLearningPathIdStub).toHaveBeenCalledWith(1);
- });
-
- it('should request recommendation on next button click', () => {
- const button = fixture.debugElement.query(By.css('.next-button'));
- expect(button).not.toBeNull();
- button.nativeElement.click();
- expect(getRecommendationStub).toHaveBeenCalledWith(learningPathId);
- });
-
- it('should load lecture unit on recommendation', () => {
- const recommendation = new LearningPathRecommendationDTO();
- recommendation.learningObjectId = lectureUnit.id!;
- recommendation.lectureId = lecture.id;
- recommendation.type = RecommendationType.LECTURE_UNIT;
- getRecommendationStub.mockReturnValue(of(new HttpResponse({ body: recommendation })));
- comp.onNextTask();
- expect(findWithDetailsStub).toHaveBeenCalled();
- expect(findWithDetailsStub).toHaveBeenCalledWith(lecture.id);
- expect(getExerciseDetailsStub).not.toHaveBeenCalled();
- });
-
- it('should load exercise on recommendation', () => {
- const recommendation = new LearningPathRecommendationDTO();
- recommendation.learningObjectId = exercise.id!;
- recommendation.type = RecommendationType.EXERCISE;
- getRecommendationStub.mockReturnValue(of(new HttpResponse({ body: recommendation })));
- comp.onNextTask();
- expect(findWithDetailsStub).not.toHaveBeenCalled();
- expect(getExerciseDetailsStub).toHaveBeenCalled();
- expect(getExerciseDetailsStub).toHaveBeenCalledWith(exercise.id);
- });
-
- it('should store current lecture unit in history', () => {
- comp.learningObjectId = lectureUnit.id!;
- comp.lectureUnit = lectureUnit;
- comp.lectureId = lecture.id;
- comp.lecture = lecture;
- fixture.detectChanges();
- comp.onNextTask();
- expect(storeLectureUnitStub).toHaveBeenCalledOnce();
- expect(storeLectureUnitStub).toHaveBeenCalledWith(learningPathId, lecture.id, lectureUnit.id);
- expect(storeExerciseStub).not.toHaveBeenCalled();
- });
-
- it('should store current exercise in history', () => {
- comp.learningObjectId = exercise.id!;
- comp.exercise = exercise;
- fixture.detectChanges();
- comp.onNextTask();
- expect(storeLectureUnitStub).not.toHaveBeenCalled();
- expect(storeExerciseStub).toHaveBeenCalledOnce();
- expect(storeExerciseStub).toHaveBeenCalledWith(learningPathId, exercise.id);
- });
-
- it('should load no previous task if history is empty', () => {
- expect(historyService.hasPrevious(learningPathId)).toBeFalsy();
- comp.onPrevTask();
- expect(getPreviousStub).not.toHaveBeenCalled();
- expect(findWithDetailsStub).not.toHaveBeenCalled();
- expect(getExerciseDetailsStub).not.toHaveBeenCalled();
- });
-
- it('should load previous lecture unit', () => {
- hasPreviousStub.mockReturnValue(true);
- getPreviousStub.mockReturnValue(new LectureUnitEntry(lecture.id!, lectureUnit.id!));
- fixture.detectChanges();
- comp.onPrevTask();
- expect(findWithDetailsStub).toHaveBeenCalled();
- expect(findWithDetailsStub).toHaveBeenCalledWith(lecture.id);
- expect(getExerciseDetailsStub).not.toHaveBeenCalled();
- });
-
- it('should load previous exercise', () => {
- hasPreviousStub.mockReturnValue(true);
- getPreviousStub.mockReturnValue(new ExerciseEntry(exercise.id!));
- fixture.detectChanges();
- comp.onPrevTask();
- expect(findWithDetailsStub).not.toHaveBeenCalled();
- expect(getExerciseDetailsStub).toHaveBeenCalled();
- expect(getExerciseDetailsStub).toHaveBeenCalledWith(exercise.id);
- });
-
- it('should set properties of lecture unit view on activate', () => {
- comp.learningObjectId = lectureUnit.id!;
- comp.lectureUnit = lectureUnit;
- comp.lectureId = lecture.id;
- comp.lecture = lecture;
- fixture.detectChanges();
- const instance = { lecture: undefined, lectureUnit: undefined } as unknown as LearningPathLectureUnitViewComponent;
- comp.setupLectureUnitView(instance);
- expect(instance.lecture).toEqual(lecture);
- expect(instance.lectureUnit).toEqual(lectureUnit);
- });
-
- it('should set properties of exercise view on activate', () => {
- comp.exercise = exercise;
- comp.learningObjectId = exercise.id!;
- fixture.detectChanges();
- const instance = { courseId: undefined, exerciseId: undefined } as unknown as CourseExerciseDetailsComponent;
- comp.setupExerciseView(instance);
- expect(instance.courseId).toBe(1);
- expect(instance.exerciseId).toEqual(exercise.id);
- });
-
- it('should handle lecture unit node click', () => {
- const node = { id: 'some-id', type: NodeType.LECTURE_UNIT, linkedResource: 2, linkedResourceParent: 3 } as NgxLearningPathNode;
- comp.onNodeClicked(node);
- expect(comp.learningObjectId).toBe(node.linkedResource);
- expect(comp.lectureId).toBe(node.linkedResourceParent);
- expect(findWithDetailsStub).toHaveBeenCalledWith(node.linkedResourceParent);
- });
-
- it('should handle exercise node click', () => {
- const node = { id: 'some-id', type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
- comp.onNodeClicked(node);
- expect(comp.learningObjectId).toBe(node.linkedResource);
- expect(getExerciseDetailsStub).toHaveBeenCalledWith(node.linkedResource);
- });
-
- it('should handle store current lecture unit in history on node click', () => {
- comp.learningObjectId = lectureUnit.id!;
- comp.lectureUnit = lectureUnit;
- comp.lectureId = lecture.id;
- comp.lecture = lecture;
- fixture.detectChanges();
- const node = { id: 'some-id', type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
- comp.onNodeClicked(node);
- expect(storeLectureUnitStub).toHaveBeenCalledOnce();
- expect(storeLectureUnitStub).toHaveBeenCalledWith(learningPathId, lecture.id, lectureUnit.id!);
- expect(storeExerciseStub).not.toHaveBeenCalled();
- });
-
- it('should handle store current exercise in history on node click', () => {
- comp.learningObjectId = exercise.id!;
- comp.exercise = exercise;
- fixture.detectChanges();
- const node = { id: 'some-id', type: NodeType.EXERCISE, linkedResource: 2 } as NgxLearningPathNode;
- comp.onNodeClicked(node);
- expect(storeLectureUnitStub).not.toHaveBeenCalled();
- expect(storeExerciseStub).toHaveBeenCalledOnce();
- expect(storeExerciseStub).toHaveBeenCalledWith(learningPathId, exercise.id!);
- });
-});
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-graph-sidebar.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-graph-sidebar.component.spec.ts
deleted file mode 100644
index f3be63c89f51..000000000000
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-graph-sidebar.component.spec.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MockComponent, MockPipe } from 'ng-mocks';
-import { By } from '@angular/platform-browser';
-import { ArtemisTestModule } from '../../../test.module';
-import { LearningPathGraphSidebarComponent } from 'app/course/learning-paths/participate/learning-path-graph-sidebar.component';
-import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
-import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
-import { NgbTooltipMocksModule } from '../../../helpers/mocks/directive/ngbTooltipMocks.module';
-
-describe('LearningPathGraphSidebarComponent', () => {
- let fixture: ComponentFixture;
- let comp: LearningPathGraphSidebarComponent;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule, MockComponent(LearningPathGraphComponent), MockPipe(ArtemisTranslatePipe), NgbTooltipMocksModule],
- declarations: [LearningPathGraphSidebarComponent],
- providers: [],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(LearningPathGraphSidebarComponent);
- comp = fixture.componentInstance;
- fixture.detectChanges();
- });
- });
-
- it('should create', () => {
- expect(fixture).toBeTruthy();
- expect(comp).toBeTruthy();
- });
-
- it('should show graph when not collapsed', () => {
- comp.collapsed = false;
- fixture.detectChanges();
- const expanded = fixture.debugElement.query(By.css('.expanded-graph'));
- const collapsed = fixture.debugElement.query(By.css('.collapsed-graph'));
- expect(expanded.nativeElement.hasAttribute('hidden')).toBeFalsy();
- expect(collapsed.nativeElement.hasAttribute('hidden')).toBeTruthy();
- });
-
- it('should not show graph when collapsed', () => {
- comp.collapsed = true;
- fixture.detectChanges();
- const expanded = fixture.debugElement.query(By.css('.expanded-graph'));
- const collapsed = fixture.debugElement.query(By.css('.collapsed-graph'));
- expect(expanded.nativeElement.hasAttribute('hidden')).toBeTruthy();
- expect(collapsed.nativeElement.hasAttribute('hidden')).toBeFalsy();
- });
-});
diff --git a/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts b/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
deleted file mode 100644
index 137578892fef..000000000000
--- a/src/test/javascript/spec/component/learning-paths/participate/learning-path-lecture-unit-view.component.spec.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MockModule } from 'ng-mocks';
-import { ArtemisTestModule } from '../../../test.module';
-import { RouterModule } from '@angular/router';
-import { Lecture } from 'app/entities/lecture.model';
-import { LearningPathLectureUnitViewComponent, LectureUnitCompletionEvent } from 'app/course/learning-paths/participate/lecture-unit/learning-path-lecture-unit-view.component';
-import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model';
-import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
-import { LectureUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/lectureUnit.service';
-import { VideoUnit } from 'app/entities/lecture-unit/videoUnit.model';
-import { TextUnit } from 'app/entities/lecture-unit/textUnit.model';
-import { OnlineUnit } from 'app/entities/lecture-unit/onlineUnit.model';
-import { Course, CourseInformationSharingConfiguration } from 'app/entities/course.model';
-import { DiscussionSectionComponent } from 'app/overview/discussion-section/discussion-section.component';
-
-describe('LearningPathLectureUnitViewComponent', () => {
- let fixture: ComponentFixture;
- let comp: LearningPathLectureUnitViewComponent;
- let lecture: Lecture;
- let lectureUnitService: LectureUnitService;
- let setCompletionStub: jest.SpyInstance;
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule, MockModule(RouterModule), MockModule(ArtemisLectureUnitsModule)],
- declarations: [LearningPathLectureUnitViewComponent],
- providers: [],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(LearningPathLectureUnitViewComponent);
- comp = fixture.componentInstance;
- lecture = new Lecture();
- lecture.id = 1;
- lectureUnitService = TestBed.inject(LectureUnitService);
- setCompletionStub = jest.spyOn(lectureUnitService, 'setCompletion');
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should display attachment unit correctly', () => {
- const attachment = new AttachmentUnit();
- attachment.id = 3;
- lecture.lectureUnits = [attachment];
- comp.lecture = lecture;
- comp.lectureUnit = attachment;
- fixture.detectChanges();
- const view = fixture.debugElement.nativeElement.querySelector('jhi-attachment-unit');
- expect(view).toBeTruthy();
- });
-
- it('should display video unit correctly', () => {
- const video = new VideoUnit();
- video.id = 3;
- lecture.lectureUnits = [video];
- comp.lecture = lecture;
- comp.lectureUnit = video;
- fixture.detectChanges();
- const view = fixture.debugElement.nativeElement.querySelector('jhi-video-unit');
- expect(view).toBeTruthy();
- });
-
- it('should display text unit correctly', () => {
- const text = new TextUnit();
- text.id = 3;
- lecture.lectureUnits = [text];
- comp.lecture = lecture;
- comp.lectureUnit = text;
- fixture.detectChanges();
- const view = fixture.debugElement.nativeElement.querySelector('jhi-text-unit');
- expect(view).toBeTruthy();
- });
-
- it('should display online unit correctly', () => {
- const online = new OnlineUnit();
- online.id = 3;
- lecture.lectureUnits = [online];
- comp.lecture = lecture;
- comp.lectureUnit = online;
- fixture.detectChanges();
- const view = fixture.debugElement.nativeElement.querySelector('jhi-online-unit');
- expect(view).toBeTruthy();
- });
-
- it('should display no discussions when disabled', () => {
- const attachment = new AttachmentUnit();
- attachment.id = 3;
- lecture.lectureUnits = [attachment];
- comp.lecture = lecture;
- comp.lectureUnit = attachment;
- lecture.course = new Course();
- lecture.course.courseInformationSharingConfiguration = CourseInformationSharingConfiguration.DISABLED;
- fixture.detectChanges();
- const outlet = fixture.debugElement.nativeElement.querySelector('router-outlet');
- expect(outlet).toBeFalsy();
- });
-
- it('should display discussions when enabled', () => {
- const attachment = new AttachmentUnit();
- attachment.id = 3;
- lecture.lectureUnits = [attachment];
- comp.lecture = lecture;
- comp.lectureUnit = attachment;
- lecture.course = new Course();
- lecture.course.courseInformationSharingConfiguration = CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING;
- fixture.detectChanges();
- const outlet = fixture.debugElement.nativeElement.querySelector('router-outlet');
- expect(outlet).toBeTruthy();
- });
-
- it('should set lecture unit completion', () => {
- const attachment = new AttachmentUnit();
- attachment.id = 3;
- attachment.visibleToStudents = true;
- attachment.completed = false;
- lecture.lectureUnits = [attachment];
- comp.lecture = lecture;
- comp.lectureUnit = attachment;
- lecture.course = new Course();
- fixture.detectChanges();
- const event = { lectureUnit: attachment, completed: true } as LectureUnitCompletionEvent;
- comp.completeLectureUnit(event);
- expect(setCompletionStub).toHaveBeenCalledOnce();
- expect(setCompletionStub).toHaveBeenCalledWith(attachment.id, lecture.id, event.completed);
- });
-
- it('should set properties of child on activate', () => {
- const attachment = new AttachmentUnit();
- attachment.id = 3;
- lecture.lectureUnits = [attachment];
- comp.lecture = lecture;
- comp.lectureUnit = attachment;
- lecture.course = new Course();
- fixture.detectChanges();
- const instance = { lecture: undefined, isCommunicationPage: undefined } as DiscussionSectionComponent;
- comp.onChildActivate(instance);
- expect(instance.lecture).toEqual(lecture);
- expect(instance.isCommunicationPage).toBeFalsy();
- });
-});
diff --git a/src/test/javascript/spec/directive/sticky-popover.directive.spec.ts b/src/test/javascript/spec/directive/sticky-popover.directive.spec.ts
deleted file mode 100644
index 13262c4fcf9a..000000000000
--- a/src/test/javascript/spec/directive/sticky-popover.directive.spec.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
-import { Component, DebugElement } from '@angular/core';
-import { ArtemisTestModule } from '../test.module';
-import { By } from '@angular/platform-browser';
-import { StickyPopoverDirective } from 'app/shared/sticky-popover/sticky-popover.directive';
-
-@Component({
- template: '
some content ',
-})
-class StickyPopoverComponent {
- pattern: string;
-}
-
-describe('StickyPopoverDirective', () => {
- let fixture: ComponentFixture;
- let debugDirective: DebugElement;
- let directive: StickyPopoverDirective;
- let openStub: jest.SpyInstance;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule],
- declarations: [StickyPopoverDirective, StickyPopoverComponent],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(StickyPopoverComponent);
- debugDirective = fixture.debugElement.query(By.directive(StickyPopoverDirective));
- directive = debugDirective.injector.get(StickyPopoverDirective);
- openStub = jest.spyOn(directive, 'open');
- fixture.detectChanges();
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should open on hover', fakeAsync(() => {
- fixture.whenStable();
- const div = fixture.debugElement.query(By.css('div'));
- expect(div).not.toBeNull();
- div.nativeElement.dispatchEvent(new MouseEvent('mouseenter'));
- tick(10);
- expect(openStub).toHaveBeenCalledOnce();
- expect(directive.isOpen()).toBeTruthy();
- const span = fixture.debugElement.query(By.css('span'));
- expect(span).not.toBeNull();
- }));
-
- it('should display content on hover', fakeAsync(() => {
- fixture.whenStable();
- const div = fixture.debugElement.query(By.css('div'));
- expect(div).not.toBeNull();
- div.nativeElement.dispatchEvent(new MouseEvent('mouseenter'));
- tick(10);
- const span = fixture.debugElement.query(By.css('span'));
- expect(span).not.toBeNull();
- }));
-});
diff --git a/src/test/javascript/spec/service/learning-path-history-storage.service.spec.ts b/src/test/javascript/spec/service/learning-path-history-storage.service.spec.ts
deleted file mode 100644
index b82196bb1e1c..000000000000
--- a/src/test/javascript/spec/service/learning-path-history-storage.service.spec.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-import { ArtemisTestModule } from '../test.module';
-import { ExerciseEntry, LearningPathHistoryStorageService, LectureUnitEntry } from 'app/course/learning-paths/participate/learning-path-history-storage.service';
-
-describe('LearningPathHistoryStorageService', () => {
- let historyStorageService: LearningPathHistoryStorageService;
- let learningPathId: number;
- let lectureId: number;
- let lectureUnitId: number;
- let exerciseId: number;
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule],
- })
- .compileComponents()
- .then(() => {
- historyStorageService = new LearningPathHistoryStorageService();
- learningPathId = 1;
- lectureId = 2;
- lectureUnitId = 3;
- exerciseId = 4;
- });
- });
-
- it('should return null if no previous is present', () => {
- expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
- const entry = historyStorageService.getPrevious(learningPathId);
- expect(entry).toBeNull();
- });
-
- it('should handle single lecture unit', () => {
- expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
- historyStorageService.storeLectureUnit(learningPathId, lectureId, lectureUnitId);
- expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
- const entry = historyStorageService.getPrevious(learningPathId);
- expect(entry).not.toBeNull();
- expect(entry).toBeInstanceOf(LectureUnitEntry);
- const lectureUnitEntry = entry;
- expect(lectureUnitEntry.lectureId).toBe(lectureId);
- expect(lectureUnitEntry.lectureUnitId).toBe(lectureUnitId);
- expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
- });
-
- it('should handle single exercise', () => {
- expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
- historyStorageService.storeExercise(learningPathId, exerciseId);
- expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
- const entry = historyStorageService.getPrevious(learningPathId);
- expect(entry).not.toBeNull();
- expect(entry).toBeInstanceOf(ExerciseEntry);
- const exerciseEntry = entry;
- expect(exerciseEntry.exerciseId).toBe(exerciseId);
- expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
- });
-
- it('should handle mixed sequence', () => {
- expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
- historyStorageService.storeExercise(learningPathId, exerciseId);
- expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
- historyStorageService.storeLectureUnit(learningPathId, lectureId, lectureUnitId);
- expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
- historyStorageService.storeExercise(learningPathId, 11);
- expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
-
- // exercise 2
- let entry = historyStorageService.getPrevious(learningPathId);
- expect(entry).not.toBeNull();
- expect(entry).toBeInstanceOf(ExerciseEntry);
- const exercise2Entry = entry;
- expect(exercise2Entry.exerciseId).toBe(11);
- expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
-
- // lecture unit
- entry = historyStorageService.getPrevious(learningPathId);
- expect(entry).not.toBeNull();
- expect(entry).toBeInstanceOf(LectureUnitEntry);
- const lectureUnitEntry = entry;
- expect(lectureUnitEntry.lectureId).toBe(lectureId);
- expect(lectureUnitEntry.lectureUnitId).toBe(lectureUnitId);
- expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
-
- // exercise 1
- entry = historyStorageService.getPrevious(learningPathId);
- expect(entry).not.toBeNull();
- expect(entry).toBeInstanceOf(ExerciseEntry);
- const exerciseEntry = entry;
- expect(exerciseEntry.exerciseId).toBe(exerciseId);
- expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
- });
-
- it('should handle multiple learning paths', () => {
- const learningPath2Id = 11;
- expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
- expect(historyStorageService.hasPrevious(learningPath2Id)).toBeFalsy();
-
- // lecture unit in learningPath(1)
- historyStorageService.storeLectureUnit(learningPathId, lectureId, lectureUnitId);
-
- expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
- expect(historyStorageService.hasPrevious(learningPath2Id)).toBeFalsy();
-
- // exercise in learningPath(2)
- historyStorageService.storeExercise(learningPath2Id, exerciseId);
-
- expect(historyStorageService.hasPrevious(learningPathId)).toBeTruthy();
- expect(historyStorageService.hasPrevious(learningPath2Id)).toBeTruthy();
-
- let entry = historyStorageService.getPrevious(learningPathId);
- expect(entry).not.toBeNull();
- expect(entry).toBeInstanceOf(LectureUnitEntry);
- const lectureUnitEntry = entry;
- expect(lectureUnitEntry.lectureId).toBe(lectureId);
- expect(lectureUnitEntry.lectureUnitId).toBe(lectureUnitId);
-
- expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
- expect(historyStorageService.hasPrevious(learningPath2Id)).toBeTruthy();
-
- entry = historyStorageService.getPrevious(learningPath2Id);
- expect(entry).not.toBeNull();
- expect(entry).toBeInstanceOf(ExerciseEntry);
- const exerciseEntry = entry;
- expect(exerciseEntry.exerciseId).toBe(exerciseId);
-
- expect(historyStorageService.hasPrevious(learningPathId)).toBeFalsy();
- expect(historyStorageService.hasPrevious(learningPath2Id)).toBeFalsy();
- });
-});
diff --git a/src/test/javascript/spec/service/learning-path.service.spec.ts b/src/test/javascript/spec/service/learning-path.service.spec.ts
index ca10b4b2ade4..cbd2601a8266 100644
--- a/src/test/javascript/spec/service/learning-path.service.spec.ts
+++ b/src/test/javascript/spec/service/learning-path.service.spec.ts
@@ -40,24 +40,6 @@ describe('LearningPathService', () => {
expect(putStub).toHaveBeenCalledWith('api/courses/1/learning-paths/generate-missing', null, { observe: 'response' });
});
- it('should send a request to the server to get learning path id of the current user in the course', () => {
- learningPathService.getLearningPathId(1).subscribe();
- expect(getStub).toHaveBeenCalledOnce();
- expect(getStub).toHaveBeenCalledWith('api/courses/1/learning-path-id', { observe: 'response' });
- });
-
- it('should send a request to the server to get ngx representation of learning path', () => {
- learningPathService.getNgxLearningPath(1).subscribe();
- expect(getStub).toHaveBeenCalledOnce();
- expect(getStub).toHaveBeenCalledWith('api/learning-path/1', { observe: 'response' });
- });
-
- it('should send a request to the server to get recommendation for learning path', () => {
- learningPathService.getRecommendation(1).subscribe();
- expect(getStub).toHaveBeenCalledOnce();
- expect(getStub).toHaveBeenCalledWith('api/learning-path/1/recommendation', { observe: 'response' });
- });
-
it('should send a request to the server to get health status of learning paths for course', () => {
learningPathService.getHealthStatusForCourse(1).subscribe();
expect(getStub).toHaveBeenCalledOnce();
From 74fb7cf753429b3e7447ba0285271b9f24fe8f26 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 13:12:46 +0200
Subject: [PATCH 117/215] undo dependency changes
---
build.gradle | 1 -
package.json | 4 +---
2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/build.gradle b/build.gradle
index 8c0f0c832233..cd60acf199f7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -340,7 +340,6 @@ dependencies {
implementation "de.tum.in.ase.athene:client:0.0.2"
implementation "commons-fileupload:commons-fileupload:1.5"
implementation "net.lingala.zip4j:zip4j:2.11.5"
- implementation "org.jgrapht:jgrapht-core:1.5.2"
annotationProcessor "org.hibernate:hibernate-jpamodelgen:${hibernate_version}"
diff --git a/package.json b/package.json
index f55e4b165d4e..35913127237d 100644
--- a/package.json
+++ b/package.json
@@ -85,9 +85,7 @@
"@swimlane/ngx-graph": {
"d3-color": "^3.1.0",
"d3-interpolate": "^3.0.1",
- "d3-transition": "^3.0.1",
- "d3-brush": "^3.0.0",
- "d3-selection": "^3.0.0"
+ "d3-brush": "^3.0.0"
},
"semver": "7.5.4",
"word-wrap": "1.2.3",
From a8c0f1062fe499fd7d6ac28d184212eaecea0124 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 13:32:34 +0200
Subject: [PATCH 118/215] remove unused methods from repository
---
package-lock.json | 10 +++++
.../repository/LearningPathRepository.java | 39 -------------------
2 files changed, 10 insertions(+), 39 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 346d4f062f3f..d9ba139fd300 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5941,6 +5941,11 @@
"d3-time-format": "2 - 3"
}
},
+ "node_modules/@swimlane/ngx-graph/node_modules/d3-selection": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz",
+ "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg=="
+ },
"node_modules/@swimlane/ngx-graph/node_modules/d3-shape": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
@@ -20109,6 +20114,11 @@
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
},
+ "node_modules/webcola/node_modules/d3-selection": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz",
+ "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg=="
+ },
"node_modules/webcola/node_modules/d3-shape": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
index 9e18387ce318..f48c5ee6b36c 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
@@ -3,9 +3,6 @@
import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD;
import java.util.Optional;
-import java.util.Set;
-
-import javax.validation.constraints.NotNull;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -23,10 +20,6 @@ public interface LearningPathRepository extends JpaRepository findByCourseIdAndUserId(long courseId, long userId);
- default LearningPath findByCourseIdAndUserIdElseThrow(long courseId, long userId) {
- return findByCourseIdAndUserId(courseId, userId).orElseThrow(() -> new EntityNotFoundException("LearningPath"));
- }
-
@EntityGraph(type = LOAD, attributePaths = { "competencies" })
Optional findWithEagerCompetenciesByCourseIdAndUserId(long courseId, long userId);
@@ -34,13 +27,6 @@ default LearningPath findWithEagerCompetenciesByCourseIdAndUserIdElseThrow(long
return findWithEagerCompetenciesByCourseIdAndUserId(courseId, userId).orElseThrow(() -> new EntityNotFoundException("LearningPath"));
}
- @Query("""
- SELECT lp
- FROM LearningPath lp
- WHERE lp.course.id = :courseId
- """)
- Set findAllForCourse(@Param("courseId") long courseId);
-
@Query("""
SELECT lp
FROM LearningPath lp
@@ -48,31 +34,6 @@ default LearningPath findWithEagerCompetenciesByCourseIdAndUserIdElseThrow(long
""")
Page findByLoginOrNameInCourse(@Param("searchTerm") String searchTerm, @Param("courseId") long courseId, Pageable pageable);
- @EntityGraph(type = LOAD, attributePaths = { "competencies" })
- Optional findWithEagerCompetenciesById(long learningPathId);
-
- @NotNull
- default LearningPath findWithEagerCompetenciesByIdElseThrow(long learningPathId) {
- return findWithEagerCompetenciesById(learningPathId).orElseThrow(() -> new EntityNotFoundException("LearningPath", learningPathId));
- }
-
- @EntityGraph(type = LOAD, attributePaths = { "competencies", "competencies.lectureUnits", "competencies.exercises" })
- Optional findWithEagerCompetenciesAndLearningObjectsById(long learningPathId);
-
- @NotNull
- default LearningPath findWithEagerCompetenciesAndLearningObjectsByIdElseThrow(long learningPathId) {
- return findWithEagerCompetenciesAndLearningObjectsById(learningPathId).orElseThrow(() -> new EntityNotFoundException("LearningPath", learningPathId));
- }
-
- @EntityGraph(type = LOAD, attributePaths = { "competencies", "competencies.lectureUnits", "competencies.lectureUnits.completedUsers", "competencies.exercises",
- "competencies.exercises.studentParticipations" })
- Optional findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersById(long learningPathId);
-
- @NotNull
- default LearningPath findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersByIdElseThrow(long learningPathId) {
- return findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersById(learningPathId).orElseThrow(() -> new EntityNotFoundException("LearningPath", learningPathId));
- }
-
@Query("""
SELECT COUNT (learningPath)
FROM LearningPath learningPath
From 01a12583d1ca904cd5239f74a89b4bafeb922b66 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 13:54:18 +0200
Subject: [PATCH 119/215] remove unused
---
.../www1/artemis/service/LearningPathService.java | 15 +++------------
1 file changed, 3 insertions(+), 12 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index fade615abc0d..dba822bdd7fa 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -45,22 +45,12 @@ public class LearningPathService {
private final CourseRepository courseRepository;
- private final CompetencyRelationRepository competencyRelationRepository;
-
- private final LectureUnitRepository lectureUnitRepository;
-
- private final ExerciseRepository exerciseRepository;
-
public LearningPathService(UserRepository userRepository, LearningPathRepository learningPathRepository, CompetencyProgressRepository competencyProgressRepository,
- CourseRepository courseRepository, CompetencyRelationRepository competencyRelationRepository, LectureUnitRepository lectureUnitRepository,
- ExerciseRepository exerciseRepository) {
+ CourseRepository courseRepository) {
this.userRepository = userRepository;
this.learningPathRepository = learningPathRepository;
this.competencyProgressRepository = competencyProgressRepository;
this.courseRepository = courseRepository;
- this.competencyRelationRepository = competencyRelationRepository;
- this.lectureUnitRepository = lectureUnitRepository;
- this.exerciseRepository = exerciseRepository;
}
/**
@@ -75,10 +65,11 @@ public void generateLearningPaths(@NotNull Course course) {
}
/**
- * Generate learning path for the user in the course
+ * Generate learning path for the user in the course if the learning path is not present
*
* @param course course that defines the learning path
* @param user student for which the learning path is generated
+ * @return the learning path of the user
*/
public LearningPath generateLearningPathForUser(@NotNull Course course, @NotNull User user) {
var existingLearningPath = learningPathRepository.findByCourseIdAndUserId(course.getId(), user.getId());
From 065757e9a5b8f763002e3b8f9a8b4c271e5d8591 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 15:14:17 +0200
Subject: [PATCH 120/215] fix learning path module
---
.../webapp/app/course/learning-paths/learning-paths.module.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
index 4d1a254a674f..0e762a712493 100644
--- a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
@@ -7,6 +7,6 @@ import { LearningPathManagementComponent } from 'app/course/learning-paths/learn
@NgModule({
imports: [ArtemisSharedModule, FormsModule, ReactiveFormsModule, ArtemisSharedComponentModule],
declarations: [LearningPathManagementComponent],
- exports: [],
+ exports: [LearningPathManagementComponent],
})
export class ArtemisLearningPathsModule {}
From c7f25fa65bb310bee5588460fbdeadf907f05154 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 15:17:44 +0200
Subject: [PATCH 121/215] Revert "fix learning path module"
This reverts commit 065757e9a5b8f763002e3b8f9a8b4c271e5d8591.
---
.../webapp/app/course/learning-paths/learning-paths.module.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
index 0e762a712493..4d1a254a674f 100644
--- a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
@@ -7,6 +7,6 @@ import { LearningPathManagementComponent } from 'app/course/learning-paths/learn
@NgModule({
imports: [ArtemisSharedModule, FormsModule, ReactiveFormsModule, ArtemisSharedComponentModule],
declarations: [LearningPathManagementComponent],
- exports: [LearningPathManagementComponent],
+ exports: [],
})
export class ArtemisLearningPathsModule {}
From d912b82588df6c9a26e8c8be65986f0bb8cdddf6 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 15:33:02 +0200
Subject: [PATCH 122/215] remove unused DTOs
---
.../competency/learning-path.model.ts | 48 -------------------
1 file changed, 48 deletions(-)
diff --git a/src/main/webapp/app/entities/competency/learning-path.model.ts b/src/main/webapp/app/entities/competency/learning-path.model.ts
index 7cee6e4ad2e4..aee01ef000cd 100644
--- a/src/main/webapp/app/entities/competency/learning-path.model.ts
+++ b/src/main/webapp/app/entities/competency/learning-path.model.ts
@@ -2,7 +2,6 @@ import { BaseEntity } from 'app/shared/model/base-entity';
import { Course } from 'app/entities/course.model';
import { User, UserNameAndLoginDTO } from 'app/core/user/user.model';
import { Competency } from 'app/entities/competency.model';
-import { ClusterNode, Edge, Node } from '@swimlane/ngx-graph';
export class LearningPath implements BaseEntity {
public id?: number;
@@ -19,50 +18,3 @@ export class LearningPathPageableSearchDTO {
public user?: UserNameAndLoginDTO;
public progress?: number;
}
-
-export class NgxLearningPathDTO {
- public nodes: NgxLearningPathNode[];
- public edges: NgxLearningPathEdge[];
- public clusters: NgxLearningPathCluster[];
-}
-
-export class NgxLearningPathNode implements Node {
- public id: string;
- public type?: NodeType;
- public linkedResource?: number;
- public linkedResourceParent?: number;
- public completed?: boolean;
- public label?: string;
-}
-
-export class NgxLearningPathEdge implements Edge {
- public id?: string;
- public source: string;
- public target: string;
-}
-
-export class NgxLearningPathCluster implements ClusterNode {
- public id: string;
- public label?: string;
- public childNodeIds?: string[];
-}
-export enum NodeType {
- COMPETENCY_START = 'COMPETENCY_START',
- COMPETENCY_END = 'COMPETENCY_END',
- MATCH_START = 'MATCH_START',
- MATCH_END = 'MATCH_END',
- EXERCISE = 'EXERCISE',
- LECTURE_UNIT = 'LECTURE_UNIT',
-}
-
-export class LearningPathRecommendationDTO {
- public learningObjectId: number;
- public lectureId?: number;
- public type: RecommendationType;
-}
-
-export enum RecommendationType {
- EMPTY = 'EMPTY',
- LECTURE_UNIT = 'LECTURE_UNIT',
- EXERCISE = 'EXERCISE',
-}
From 4236eadb82ae045cbcab7dc792761e8cede5a9b8 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 15:33:42 +0200
Subject: [PATCH 123/215] add basic components
---
.../learning-path-graph-node.component.html | 41 +++++++++++
.../learning-path-graph-node.component.ts | 20 +++++
.../learning-path-graph.component.html | 41 +++++++++++
.../learning-path-graph.component.scss | 44 +++++++++++
.../learning-path-graph.component.ts | 60 +++++++++++++++
.../competency-node-details.component.html | 18 +++++
.../competency-node-details.component.ts | 58 +++++++++++++++
.../exercise-node-details.component.html | 14 ++++
.../exercise-node-details.component.ts | 38 ++++++++++
.../lecture-unit-node-details.component.html | 14 ++++
.../lecture-unit-node-details.component.ts | 45 ++++++++++++
...earning-path-progress-modal.component.html | 19 +++++
...earning-path-progress-modal.component.scss | 26 +++++++
.../learning-path-progress-modal.component.ts | 20 +++++
.../learning-path-progress-nav.component.html | 16 ++++
.../learning-path-progress-nav.component.ts | 19 +++++
.../learning-paths/learning-path.service.ts | 19 +++++
.../learning-paths/learning-paths.module.ts | 23 +++++-
...learning-path-graph-node.component.spec.ts | 46 ++++++++++++
.../learning-path-graph.component.spec.ts | 62 ++++++++++++++++
.../competency-node-details.component.spec.ts | 69 ++++++++++++++++++
.../exercise-node-details.component.spec.ts | 50 +++++++++++++
...ecture-unit-node-details.component.spec.ts | 57 +++++++++++++++
...ning-path-progress-modal.component.spec.ts | 48 ++++++++++++
...arning-path-progress-nav.component.spec.ts | 73 +++++++++++++++++++
.../service/learning-path.service.spec.ts | 6 ++
26 files changed, 944 insertions(+), 2 deletions(-)
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
new file mode 100644
index 000000000000..5d537b33656e
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
new file mode 100644
index 000000000000..9bd4b5584569
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph-node.component.ts
@@ -0,0 +1,20 @@
+import { Component, Input } from '@angular/core';
+import { faCheckCircle, faCircle, faPlayCircle, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
+import { NgxLearningPathNode, NodeType } from 'app/entities/competency/learning-path.model';
+
+@Component({
+ selector: 'jhi-learning-path-graph-node',
+ templateUrl: './learning-path-graph-node.component.html',
+})
+export class LearningPathGraphNodeComponent {
+ @Input() courseId: number;
+ @Input() node: NgxLearningPathNode;
+
+ //icons
+ faCheckCircle = faCheckCircle;
+ faPlayCircle = faPlayCircle;
+ faQuestionCircle = faQuestionCircle;
+
+ faCircle = faCircle;
+ protected readonly NodeType = NodeType;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
new file mode 100644
index 000000000000..c72307fe6a29
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
new file mode 100644
index 000000000000..de4453f8e8da
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.scss
@@ -0,0 +1,44 @@
+.graph-container {
+ display: block;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+
+ .ngx-graph {
+ width: auto;
+ }
+
+ .node {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ }
+}
+
+jhi-learning-path-graph-node:hover {
+ cursor: pointer;
+}
+
+.node-icon-container {
+ width: 100%;
+ display: flex;
+
+ fa-icon {
+ width: 100%;
+
+ svg {
+ margin: 10%;
+ width: 80%;
+ height: 80%;
+ }
+ }
+}
+
+.completed {
+ color: var(--bs-success);
+}
+
+.node-details {
+ display: block;
+ max-width: 90vh;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
new file mode 100644
index 000000000000..5644c8db6e28
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/learning-path-graph.component.ts
@@ -0,0 +1,60 @@
+import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { Layout } from '@swimlane/ngx-graph';
+import * as shape from 'd3-shape';
+import { Subject } from 'rxjs';
+import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
+import { NgxLearningPathDTO, NgxLearningPathNode } from 'app/entities/competency/learning-path.model';
+
+@Component({
+ selector: 'jhi-learning-path-graph',
+ styleUrls: ['./learning-path-graph.component.scss'],
+ templateUrl: './learning-path-graph.component.html',
+ encapsulation: ViewEncapsulation.None,
+})
+export class LearningPathGraphComponent implements OnInit {
+ isLoading = false;
+ @Input() learningPathId: number;
+ @Input() courseId: number;
+ @Output() nodeClicked: EventEmitter = new EventEmitter();
+ ngxLearningPath: NgxLearningPathDTO;
+
+ layout: string | Layout = 'dagreCluster';
+ curve = shape.curveBundle;
+
+ draggingEnabled = false;
+ panningEnabled = true;
+ zoomEnabled = true;
+ panOnZoom = true;
+
+ update$: Subject = new Subject();
+ center$: Subject = new Subject();
+ zoomToFit$: Subject = new Subject();
+
+ constructor(private activatedRoute: ActivatedRoute, private learningPathService: LearningPathService) {}
+
+ ngOnInit() {
+ if (this.learningPathId) {
+ this.loadData();
+ }
+ }
+
+ loadData() {
+ this.isLoading = true;
+ this.learningPathService.getNgxLearningPath(this.learningPathId).subscribe((ngxLearningPathResponse) => {
+ this.ngxLearningPath = ngxLearningPathResponse.body!;
+ this.isLoading = false;
+ });
+ }
+
+ onResize() {
+ this.update$.next(true);
+ this.center$.next(true);
+ this.zoomToFit$.next(true);
+ }
+
+ onCenterView() {
+ this.zoomToFit$.next(true);
+ this.center$.next(true);
+ }
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
new file mode 100644
index 000000000000..78bc30b342fa
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.html
@@ -0,0 +1,18 @@
+
+
+
+
+ {{ competency!.title }}
+ = 100" class="badge text-white text-bg-success" jhiTranslate="artemisApp.competency.mastered">Mastered
+ Optional
+
+
{{ competency.description }}
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts
new file mode 100644
index 000000000000..24cb6455586b
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component.ts
@@ -0,0 +1,58 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { HttpErrorResponse } from '@angular/common/http';
+import { onError } from 'app/shared/util/global.utils';
+import { CompetencyService } from 'app/course/competencies/competency.service';
+import { Competency, CompetencyProgress, getIcon, getIconTooltip } from 'app/entities/competency.model';
+import { AlertService } from 'app/core/util/alert.service';
+
+@Component({
+ selector: 'jhi-competency-node-details',
+ templateUrl: './competency-node-details.component.html',
+})
+export class CompetencyNodeDetailsComponent implements OnInit {
+ @Input() courseId: number;
+ @Input() competencyId: number;
+ competency: Competency;
+ competencyProgress: CompetencyProgress;
+
+ isLoading = false;
+
+ constructor(private competencyService: CompetencyService, private alertService: AlertService) {}
+
+ ngOnInit() {
+ if (this.competencyId && this.courseId) {
+ this.loadData();
+ }
+ }
+ private loadData() {
+ this.isLoading = true;
+ this.competencyService.findById(this.competencyId!, this.courseId!).subscribe({
+ next: (resp) => {
+ this.competency = resp.body!;
+ if (this.competency.userProgress?.length) {
+ this.competencyProgress = this.competency.userProgress.first()!;
+ } else {
+ this.competencyProgress = { progress: 0, confidence: 0 } as CompetencyProgress;
+ }
+ this.isLoading = false;
+ },
+ error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
+ });
+ }
+
+ get progress(): number {
+ return Math.round(this.competencyProgress.progress ?? 0);
+ }
+
+ get confidence(): number {
+ return Math.min(Math.round(((this.competencyProgress.confidence ?? 0) / (this.competency.masteryThreshold ?? 100)) * 100), 100);
+ }
+
+ get mastery(): number {
+ const weight = 2 / 3;
+ return Math.round((1 - weight) * this.progress + weight * this.confidence);
+ }
+
+ protected readonly getIcon = getIcon;
+ protected readonly getIconTooltip = getIconTooltip;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html
new file mode 100644
index 000000000000..d364b09a85b2
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.html
@@ -0,0 +1,14 @@
+
+
+
+
+ {{ exercise.title }}
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
new file mode 100644
index 000000000000..8f265a38fadf
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component.ts
@@ -0,0 +1,38 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { HttpErrorResponse } from '@angular/common/http';
+import { onError } from 'app/shared/util/global.utils';
+import { AlertService } from 'app/core/util/alert.service';
+import { Exercise, getIcon, getIconTooltip } from 'app/entities/exercise.model';
+import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
+
+@Component({
+ selector: 'jhi-exercise-node-details',
+ templateUrl: './exercise-node-details.component.html',
+})
+export class ExerciseNodeDetailsComponent implements OnInit {
+ @Input() exerciseId: number;
+
+ exercise: Exercise;
+
+ isLoading = false;
+
+ constructor(private exerciseService: ExerciseService, private alertService: AlertService) {}
+
+ ngOnInit() {
+ if (this.exerciseId) {
+ this.loadData();
+ }
+ }
+ private loadData() {
+ this.isLoading = true;
+ this.exerciseService.find(this.exerciseId).subscribe({
+ next: (exerciseResponse) => {
+ this.exercise = exerciseResponse.body!;
+ },
+ error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
+ });
+ }
+
+ protected readonly getIcon = getIcon;
+ protected readonly getIconTooltip = getIconTooltip;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
new file mode 100644
index 000000000000..4f6553e2ef8e
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.html
@@ -0,0 +1,14 @@
+
+
+
+
+ {{ lectureUnit.name }}
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
new file mode 100644
index 000000000000..97aa66e1c311
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component.ts
@@ -0,0 +1,45 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { HttpErrorResponse } from '@angular/common/http';
+import { onError } from 'app/shared/util/global.utils';
+import { AlertService } from 'app/core/util/alert.service';
+import { LectureService } from 'app/lecture/lecture.service';
+import { Lecture } from 'app/entities/lecture.model';
+import { LectureUnit, getIcon, getIconTooltip } from 'app/entities/lecture-unit/lectureUnit.model';
+
+@Component({
+ selector: 'jhi-lecture-unit-node-details',
+ templateUrl: './lecture-unit-node-details.component.html',
+})
+export class LectureUnitNodeDetailsComponent implements OnInit {
+ @Input() lectureId: number;
+ @Input() lectureUnitId: number;
+
+ lecture: Lecture;
+ lectureUnit: LectureUnit;
+
+ isLoading = false;
+
+ constructor(private lectureService: LectureService, private alertService: AlertService) {}
+
+ ngOnInit() {
+ if (this.lectureId && this.lectureUnitId) {
+ this.loadData();
+ }
+ }
+ private loadData() {
+ this.isLoading = true;
+ this.lectureService.findWithDetails(this.lectureId!).subscribe({
+ next: (findLectureResult) => {
+ this.lecture = findLectureResult.body!;
+ if (this.lecture?.lectureUnits) {
+ this.lectureUnit = this.lecture.lectureUnits.find((lectureUnit) => lectureUnit.id === this.lectureUnitId)!;
+ }
+ this.isLoading = false;
+ },
+ error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse),
+ });
+ }
+
+ protected readonly getIcon = getIcon;
+ protected readonly getIconTooltip = getIconTooltip;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
new file mode 100644
index 000000000000..7477b90ced6c
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.html
@@ -0,0 +1,19 @@
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
new file mode 100644
index 000000000000..cc798cd46adb
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.scss
@@ -0,0 +1,26 @@
+.modal-container {
+ display: flex;
+ flex-wrap: wrap;
+ height: 90vh;
+ width: 100%;
+ padding-top: 8px;
+
+ .row {
+ width: 100%;
+ margin-left: 0;
+ margin-right: 0;
+ }
+
+ .modal-nav {
+ height: max-content;
+ }
+
+ .graph {
+ width: 100%;
+ overflow: hidden;
+ }
+}
+
+.learning-path-modal .modal-dialog .modal-content {
+ min-height: 500px;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
new file mode 100644
index 000000000000..090b7d5405f4
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-modal.component.ts
@@ -0,0 +1,20 @@
+import { Component, Input, ViewChild } from '@angular/core';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
+import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
+@Component({
+ selector: 'jhi-learning-path-progress-modal',
+ styleUrls: ['./learning-path-progress-modal.component.scss'],
+ templateUrl: './learning-path-progress-modal.component.html',
+})
+export class LearningPathProgressModalComponent {
+ @Input() courseId: number;
+ @Input() learningPath: LearningPathPageableSearchDTO;
+ @ViewChild('learningPathGraphComponent') learningPathGraphComponent: LearningPathGraphComponent;
+
+ constructor(private activeModal: NgbActiveModal) {}
+
+ close() {
+ this.activeModal.close();
+ }
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
new file mode 100644
index 000000000000..66f5c6778206
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.html
@@ -0,0 +1,16 @@
+
+
+
{{ learningPath.user?.name }} ({{ learningPath.user?.login }})
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
new file mode 100644
index 000000000000..c050632cc577
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-progress-nav.component.ts
@@ -0,0 +1,19 @@
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { faArrowsRotate, faArrowsToEye, faXmark } from '@fortawesome/free-solid-svg-icons';
+import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
+
+@Component({
+ selector: 'jhi-learning-path-progress-nav',
+ templateUrl: './learning-path-progress-nav.component.html',
+})
+export class LearningPathProgressNavComponent {
+ @Input() learningPath: LearningPathPageableSearchDTO;
+ @Output() onRefresh: EventEmitter = new EventEmitter();
+ @Output() onCenterView: EventEmitter = new EventEmitter();
+ @Output() onClose: EventEmitter = new EventEmitter();
+
+ // icons
+ faXmark = faXmark;
+ faArrowsToEye = faArrowsToEye;
+ faArrowsRotate = faArrowsRotate;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path.service.ts b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
index ece1bc6b204a..35bbb8d833ca 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path.service.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path.service.ts
@@ -2,6 +2,8 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { LearningPathHealthDTO } from 'app/entities/competency/learning-path-health.model';
+import { NgxLearningPathDTO } from 'app/entities/competency/learning-path.model';
+import { map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class LearningPathService {
@@ -20,4 +22,21 @@ export class LearningPathService {
getHealthStatusForCourse(courseId: number) {
return this.httpClient.get(`${this.resourceURL}/courses/${courseId}/learning-path-health`, { observe: 'response' });
}
+
+ getNgxLearningPath(learningPathId: number): Observable> {
+ return this.httpClient.get(`${this.resourceURL}/learning-path/${learningPathId}`, { observe: 'response' }).pipe(
+ map((ngxLearningPathResponse) => {
+ if (!ngxLearningPathResponse.body!.nodes) {
+ ngxLearningPathResponse.body!.nodes = [];
+ }
+ if (!ngxLearningPathResponse.body!.edges) {
+ ngxLearningPathResponse.body!.edges = [];
+ }
+ if (!ngxLearningPathResponse.body!.clusters) {
+ ngxLearningPathResponse.body!.clusters = [];
+ }
+ return ngxLearningPathResponse;
+ }),
+ );
+ }
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
index 4d1a254a674f..8c37905813e9 100644
--- a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
@@ -3,10 +3,29 @@ import { ArtemisSharedModule } from 'app/shared/shared.module';
import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { LearningPathManagementComponent } from 'app/course/learning-paths/learning-path-management/learning-path-management.component';
+import { NgxGraphModule } from '@swimlane/ngx-graph';
+import { ArtemisLectureUnitsModule } from 'app/overview/course-lectures/lecture-units.module';
+import { ArtemisCompetenciesModule } from 'app/course/competencies/competency.module';
+import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
+import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
+import { LearningPathProgressNavComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-nav.component';
+import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph-node.component';
+import { CompetencyNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component';
+import { LectureUnitNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component';
+import { ExerciseNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component';
@NgModule({
- imports: [ArtemisSharedModule, FormsModule, ReactiveFormsModule, ArtemisSharedComponentModule],
- declarations: [LearningPathManagementComponent],
+ imports: [ArtemisSharedModule, FormsModule, ReactiveFormsModule, ArtemisSharedComponentModule, NgxGraphModule, ArtemisLectureUnitsModule, ArtemisCompetenciesModule],
+ declarations: [
+ LearningPathManagementComponent,
+ LearningPathProgressModalComponent,
+ LearningPathProgressNavComponent,
+ LearningPathGraphComponent,
+ LearningPathGraphNodeComponent,
+ CompetencyNodeDetailsComponent,
+ LectureUnitNodeDetailsComponent,
+ ExerciseNodeDetailsComponent,
+ ],
exports: [],
})
export class ArtemisLearningPathsModule {}
diff --git a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
new file mode 100644
index 000000000000..8371428d66ed
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph-node.component.spec.ts
@@ -0,0 +1,46 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../test.module';
+import { MockDirective } from 'ng-mocks';
+import { By } from '@angular/platform-browser';
+import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph-node.component';
+import { NgxLearningPathNode, NodeType } from 'app/entities/competency/learning-path.model';
+import { StickyPopoverDirective } from 'app/shared/sticky-popover/sticky-popover.directive';
+
+describe('LearningPathGraphNodeComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LearningPathGraphNodeComponent;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule],
+ declarations: [LearningPathGraphNodeComponent, MockDirective(StickyPopoverDirective)],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathGraphNodeComponent);
+ comp = fixture.componentInstance;
+ });
+ });
+
+ it.each([NodeType.EXERCISE, NodeType.LECTURE_UNIT])('should display correct icon for completed learning object', (type: NodeType) => {
+ comp.node = { id: '1', type: type, completed: true } as NgxLearningPathNode;
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('#completed')).nativeElement).toBeTruthy();
+ });
+
+ it.each([NodeType.EXERCISE, NodeType.LECTURE_UNIT])('should display correct icon for not completed learning object', (type: NodeType) => {
+ comp.node = { id: '1', type: type, completed: false } as NgxLearningPathNode;
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('#not-completed')).nativeElement).toBeTruthy();
+ });
+
+ it.each([NodeType.COMPETENCY_START, NodeType.COMPETENCY_END, NodeType.COMPETENCY_START, NodeType.COMPETENCY_END])(
+ 'should display correct icon for generic node',
+ (type: NodeType) => {
+ comp.node = { id: '1', type: type } as NgxLearningPathNode;
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('#generic')).nativeElement).toBeTruthy();
+ },
+ );
+});
diff --git a/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
new file mode 100644
index 000000000000..32dcaacea7b2
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/graph/learning-path-graph.component.spec.ts
@@ -0,0 +1,62 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../test.module';
+import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
+import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
+
+describe('LearningPathGraphComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LearningPathGraphComponent;
+ let learningPathService: LearningPathService;
+ let getNgxLearningPathStub: jest.SpyInstance;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule],
+ declarations: [LearningPathGraphComponent],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathGraphComponent);
+ comp = fixture.componentInstance;
+ learningPathService = TestBed.inject(LearningPathService);
+ getNgxLearningPathStub = jest.spyOn(learningPathService, 'getNgxLearningPath');
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should load learning path from service', () => {
+ comp.learningPathId = 1;
+ fixture.detectChanges();
+ expect(getNgxLearningPathStub).toHaveBeenCalledOnce();
+ expect(getNgxLearningPathStub).toHaveBeenCalledWith(1);
+ });
+
+ it('should update, center, and zoom to fit on resize', () => {
+ const updateStub = jest.spyOn(comp.update$, 'next');
+ const centerStub = jest.spyOn(comp.center$, 'next');
+ const zoomToFitStub = jest.spyOn(comp.zoomToFit$, 'next');
+ fixture.detectChanges();
+ comp.onResize();
+ expect(updateStub).toHaveBeenCalledOnce();
+ expect(updateStub).toHaveBeenCalledWith(true);
+ expect(centerStub).toHaveBeenCalledOnce();
+ expect(centerStub).toHaveBeenCalledWith(true);
+ expect(zoomToFitStub).toHaveBeenCalledOnce();
+ expect(zoomToFitStub).toHaveBeenCalledWith(true);
+ });
+
+ it('should zoom to fit and center on resize', () => {
+ const zoomToFitStub = jest.spyOn(comp.zoomToFit$, 'next');
+ const centerStub = jest.spyOn(comp.center$, 'next');
+ fixture.detectChanges();
+ comp.onCenterView();
+ expect(zoomToFitStub).toHaveBeenCalledOnce();
+ expect(zoomToFitStub).toHaveBeenCalledWith(true);
+ expect(centerStub).toHaveBeenCalledOnce();
+ expect(centerStub).toHaveBeenCalledWith(true);
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts
new file mode 100644
index 000000000000..848c9a2cefe9
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/graph/node-details/competency-node-details.component.spec.ts
@@ -0,0 +1,69 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../../test.module';
+import { MockComponent, MockPipe } from 'ng-mocks';
+import { of } from 'rxjs';
+import { HttpResponse } from '@angular/common/http';
+import { CompetencyNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component';
+import { Competency, CompetencyProgress, CompetencyTaxonomy } from 'app/entities/competency.model';
+import { CompetencyService } from 'app/course/competencies/competency.service';
+import { CompetencyRingsComponent } from 'app/course/competencies/competency-rings/competency-rings.component';
+import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
+import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module';
+
+describe('CompetencyNodeDetailsComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: CompetencyNodeDetailsComponent;
+ let competencyService: CompetencyService;
+ let findByIdStub: jest.SpyInstance;
+ let competency: Competency;
+ let competencyProgress: CompetencyProgress;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, NgbTooltipMocksModule],
+ declarations: [CompetencyNodeDetailsComponent, MockComponent(CompetencyRingsComponent), MockPipe(ArtemisTranslatePipe)],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(CompetencyNodeDetailsComponent);
+ comp = fixture.componentInstance;
+ competency = new Competency();
+ competency.id = 2;
+ competency.title = 'Some arbitrary title';
+ competency.description = 'Some description';
+ competency.taxonomy = CompetencyTaxonomy.ANALYZE;
+ competency.masteryThreshold = 50;
+ competencyProgress = new CompetencyProgress();
+ competencyProgress.progress = 80;
+ competencyProgress.confidence = 70;
+ competency.userProgress = [competencyProgress];
+
+ competencyService = TestBed.inject(CompetencyService);
+ findByIdStub = jest.spyOn(competencyService, 'findById').mockReturnValue(of(new HttpResponse({ body: competency })));
+ comp.courseId = 1;
+ comp.competencyId = competency.id;
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should load competency on init', () => {
+ fixture.detectChanges();
+ expect(findByIdStub).toHaveBeenCalledOnce();
+ expect(findByIdStub).toHaveBeenCalledWith(competency.id, 1);
+ expect(comp.competency).toEqual(competency);
+ expect(comp.competencyProgress).toEqual(competencyProgress);
+ });
+
+ it('should default progress to zero if empty', () => {
+ competency.userProgress = undefined;
+ fixture.detectChanges();
+ expect(findByIdStub).toHaveBeenCalledOnce();
+ expect(findByIdStub).toHaveBeenCalledWith(competency.id, 1);
+ expect(comp.competency).toEqual(competency);
+ expect(comp.competencyProgress).toEqual({ confidence: 0, progress: 0 } as CompetencyProgress);
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
new file mode 100644
index 000000000000..839aeccf50de
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/graph/node-details/exercise-node-details.component.spec.ts
@@ -0,0 +1,50 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../../test.module';
+import { MockPipe } from 'ng-mocks';
+import { of } from 'rxjs';
+import { HttpResponse } from '@angular/common/http';
+import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
+import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module';
+import { ExerciseNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component';
+import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service';
+import { Exercise } from 'app/entities/exercise.model';
+import { TextExercise } from 'app/entities/text-exercise.model';
+
+describe('ExerciseNodeDetailsComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: ExerciseNodeDetailsComponent;
+ let exerciseService: ExerciseService;
+ let findStub: jest.SpyInstance;
+ let exercise: Exercise;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, NgbTooltipMocksModule],
+ declarations: [ExerciseNodeDetailsComponent, MockPipe(ArtemisTranslatePipe)],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(ExerciseNodeDetailsComponent);
+ comp = fixture.componentInstance;
+ exercise = new TextExercise(undefined, undefined);
+ exercise.id = 1;
+ exercise.title = 'Some arbitrary title';
+
+ exerciseService = TestBed.inject(ExerciseService);
+ findStub = jest.spyOn(exerciseService, 'find').mockReturnValue(of(new HttpResponse({ body: exercise })));
+ comp.exerciseId = exercise.id;
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should load exercise on init', () => {
+ fixture.detectChanges();
+ expect(findStub).toHaveBeenCalledOnce();
+ expect(findStub).toHaveBeenCalledWith(exercise.id);
+ expect(comp.exercise).toEqual(exercise);
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts b/src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts
new file mode 100644
index 000000000000..0c7a44364c97
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/graph/node-details/lecture-unit-node-details.component.spec.ts
@@ -0,0 +1,57 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../../test.module';
+import { MockPipe } from 'ng-mocks';
+import { of } from 'rxjs';
+import { HttpResponse } from '@angular/common/http';
+import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
+import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module';
+import { LectureUnitNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component';
+import { LectureService } from 'app/lecture/lecture.service';
+import { Lecture } from 'app/entities/lecture.model';
+import { LectureUnit } from 'app/entities/lecture-unit/lectureUnit.model';
+import { TextUnit } from 'app/entities/lecture-unit/textUnit.model';
+
+describe('LectureUnitNodeDetailsComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LectureUnitNodeDetailsComponent;
+ let lectureService: LectureService;
+ let findWithDetailsStub: jest.SpyInstance;
+ let lecture: Lecture;
+ let lectureUnit: LectureUnit;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, NgbTooltipMocksModule],
+ declarations: [LectureUnitNodeDetailsComponent, MockPipe(ArtemisTranslatePipe)],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LectureUnitNodeDetailsComponent);
+ comp = fixture.componentInstance;
+ lecture = new Lecture();
+ lecture.id = 1;
+ lectureUnit = new TextUnit();
+ lectureUnit.id = 2;
+ lectureUnit.name = 'Some arbitrary name';
+ lecture.lectureUnits = [lectureUnit];
+
+ lectureService = TestBed.inject(LectureService);
+ findWithDetailsStub = jest.spyOn(lectureService, 'findWithDetails').mockReturnValue(of(new HttpResponse({ body: lecture })));
+ comp.lectureId = lecture.id;
+ comp.lectureUnitId = lectureUnit.id;
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should load lecture unit on init', () => {
+ fixture.detectChanges();
+ expect(findWithDetailsStub).toHaveBeenCalledOnce();
+ expect(findWithDetailsStub).toHaveBeenCalledWith(lecture.id);
+ expect(comp.lecture).toEqual(lecture);
+ expect(comp.lectureUnit).toEqual(lectureUnit);
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
new file mode 100644
index 000000000000..1925146cac25
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-modal.component.spec.ts
@@ -0,0 +1,48 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../test.module';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { MockComponent } from 'ng-mocks';
+import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
+import { LearningPathGraphComponent } from 'app/course/learning-paths/learning-path-graph/learning-path-graph.component';
+import { By } from '@angular/platform-browser';
+import { LearningPathProgressNavComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-nav.component';
+import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
+
+describe('LearningPathProgressModalComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LearningPathProgressModalComponent;
+ let activeModal: NgbActiveModal;
+ let closeStub: jest.SpyInstance;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, MockComponent(LearningPathGraphComponent), MockComponent(LearningPathProgressNavComponent)],
+ declarations: [LearningPathProgressModalComponent],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathProgressModalComponent);
+ comp = fixture.componentInstance;
+ activeModal = TestBed.inject(NgbActiveModal);
+ closeStub = jest.spyOn(activeModal, 'close');
+ fixture.detectChanges();
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should display learning path graph if learning path is present', () => {
+ comp.courseId = 2;
+ comp.learningPath = { id: 1 } as LearningPathPageableSearchDTO;
+ fixture.detectChanges();
+ expect(fixture.debugElement.query(By.css('.graph')).nativeElement).toBeTruthy();
+ });
+
+ it('should correctly close modal', () => {
+ comp.close();
+ expect(closeStub).toHaveBeenCalledOnce();
+ });
+});
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
new file mode 100644
index 000000000000..437b2781c3ea
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-progress-nav.component.spec.ts
@@ -0,0 +1,73 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../test.module';
+import { By } from '@angular/platform-browser';
+import { LearningPathProgressNavComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-nav.component';
+import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
+import { UserNameAndLoginDTO } from 'app/core/user/user.model';
+import { MockPipe } from 'ng-mocks';
+import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
+import { NgbTooltipMocksModule } from '../../../helpers/mocks/directive/ngbTooltipMocks.module';
+
+describe('LearningPathProgressNavComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: LearningPathProgressNavComponent;
+ let onRefreshStub: jest.SpyInstance;
+ let onCenterViewStub: jest.SpyInstance;
+ let onCloseStub: jest.SpyInstance;
+ let learningPath: LearningPathPageableSearchDTO;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, NgbTooltipMocksModule],
+ declarations: [LearningPathProgressNavComponent, MockPipe(ArtemisTranslatePipe)],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathProgressNavComponent);
+ comp = fixture.componentInstance;
+ onRefreshStub = jest.spyOn(comp.onRefresh, 'emit');
+ onCenterViewStub = jest.spyOn(comp.onCenterView, 'emit');
+ onCloseStub = jest.spyOn(comp.onClose, 'emit');
+ learningPath = new LearningPathPageableSearchDTO();
+ learningPath.user = new UserNameAndLoginDTO();
+ learningPath.user.name = 'some arbitrary name';
+ learningPath.user.login = 'somearbitrarylogin';
+ comp.learningPath = learningPath;
+ fixture.detectChanges();
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should create', () => {
+ expect(fixture).toBeTruthy();
+ expect(comp).toBeTruthy();
+ });
+
+ it('should emit refresh on click', () => {
+ const button = fixture.debugElement.query(By.css('#refresh-button'));
+ expect(button).not.toBeNull();
+
+ button.nativeElement.click();
+ expect(onRefreshStub).toHaveBeenCalledOnce();
+ });
+
+ it('should emit center view on click', () => {
+ const button = fixture.debugElement.query(By.css('#center-button'));
+ expect(button).not.toBeNull();
+
+ button.nativeElement.click();
+ expect(onCenterViewStub).toHaveBeenCalledOnce();
+ });
+
+ it('should emit close on click', () => {
+ const button = fixture.debugElement.query(By.css('#close-button'));
+ expect(button).not.toBeNull();
+
+ button.nativeElement.click();
+ expect(onCloseStub).toHaveBeenCalledOnce();
+ });
+});
diff --git a/src/test/javascript/spec/service/learning-path.service.spec.ts b/src/test/javascript/spec/service/learning-path.service.spec.ts
index cbd2601a8266..0a85e60ff614 100644
--- a/src/test/javascript/spec/service/learning-path.service.spec.ts
+++ b/src/test/javascript/spec/service/learning-path.service.spec.ts
@@ -45,4 +45,10 @@ describe('LearningPathService', () => {
expect(getStub).toHaveBeenCalledOnce();
expect(getStub).toHaveBeenCalledWith('api/courses/1/learning-path-health', { observe: 'response' });
});
+
+ it('should send a request to the server to get ngx representation of learning path', () => {
+ learningPathService.getNgxLearningPath(1).subscribe();
+ expect(getStub).toHaveBeenCalledOnce();
+ expect(getStub).toHaveBeenCalledWith('api/learning-path/1', { observe: 'response' });
+ });
});
From 7be6cc0fbe02e96702de283fca71b97038e09c3e Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 15:39:50 +0200
Subject: [PATCH 124/215] add client DTOs
---
.../competency/learning-path.model.ts | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/src/main/webapp/app/entities/competency/learning-path.model.ts b/src/main/webapp/app/entities/competency/learning-path.model.ts
index aee01ef000cd..8c2978e39963 100644
--- a/src/main/webapp/app/entities/competency/learning-path.model.ts
+++ b/src/main/webapp/app/entities/competency/learning-path.model.ts
@@ -2,6 +2,7 @@ import { BaseEntity } from 'app/shared/model/base-entity';
import { Course } from 'app/entities/course.model';
import { User, UserNameAndLoginDTO } from 'app/core/user/user.model';
import { Competency } from 'app/entities/competency.model';
+import { ClusterNode, Edge, Node } from '@swimlane/ngx-graph';
export class LearningPath implements BaseEntity {
public id?: number;
@@ -18,3 +19,38 @@ export class LearningPathPageableSearchDTO {
public user?: UserNameAndLoginDTO;
public progress?: number;
}
+
+export class NgxLearningPathDTO {
+ public nodes: NgxLearningPathNode[];
+ public edges: NgxLearningPathEdge[];
+ public clusters: NgxLearningPathCluster[];
+}
+
+export class NgxLearningPathNode implements Node {
+ public id: string;
+ public type?: NodeType;
+ public linkedResource?: number;
+ public linkedResourceParent?: number;
+ public completed?: boolean;
+ public label?: string;
+}
+
+export class NgxLearningPathEdge implements Edge {
+ public id?: string;
+ public source: string;
+ public target: string;
+}
+
+export class NgxLearningPathCluster implements ClusterNode {
+ public id: string;
+ public label?: string;
+ public childNodeIds?: string[];
+}
+export enum NodeType {
+ COMPETENCY_START = 'COMPETENCY_START',
+ COMPETENCY_END = 'COMPETENCY_END',
+ MATCH_START = 'MATCH_START',
+ MATCH_END = 'MATCH_END',
+ EXERCISE = 'EXERCISE',
+ LECTURE_UNIT = 'LECTURE_UNIT',
+}
From ea276949540da320e4f74e89e33f56b4f883981a Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 15:40:15 +0200
Subject: [PATCH 125/215] add modal to learning path management
---
.../learning-path-management.component.ts | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index 96441d25d553..f8cafc4a67f0 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -12,6 +12,8 @@ import { SortService } from 'app/shared/service/sort.service';
import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
import { faSort, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
import { HealthStatus, LearningPathHealthDTO } from 'app/entities/competency/learning-path-health.model';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
export enum TableColumn {
ID = 'ID',
@@ -55,6 +57,7 @@ export class LearningPathManagementComponent implements OnInit {
private alertService: AlertService,
private pagingService: LearningPathPagingService,
private sortService: SortService,
+ private modalService: NgbModal,
) {}
get page(): number {
@@ -200,7 +203,13 @@ export class LearningPathManagementComponent implements OnInit {
}
}
viewLearningPath(learningPath: LearningPathPageableSearchDTO) {
- // todo: part of future pr
+ const modalRef = this.modalService.open(LearningPathProgressModalComponent, {
+ size: 'xl',
+ backdrop: 'static',
+ windowClass: 'learning-path-modal',
+ });
+ modalRef.componentInstance.courseId = this.courseId;
+ modalRef.componentInstance.learningPath = learningPath;
}
protected readonly HealthStatus = HealthStatus;
From cd36231a5ab70a43fe41f1155436a7eb7541a3f8 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 16:02:25 +0200
Subject: [PATCH 126/215] add server code and tests
---
build.gradle | 1 +
.../repository/LearningPathRepository.java | 15 ++
.../artemis/service/LearningPathService.java | 240 +++++++++++++++++-
.../web/rest/LearningPathResource.java | 30 +++
.../dto/competency/NgxLearningPathDTO.java | 47 ++++
.../lecture/LearningPathIntegrationTest.java | 50 ++++
.../service/LearningPathServiceTest.java | 223 ++++++++++++++++
7 files changed, 605 insertions(+), 1 deletion(-)
create mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
diff --git a/build.gradle b/build.gradle
index cd60acf199f7..8c0f0c832233 100644
--- a/build.gradle
+++ b/build.gradle
@@ -340,6 +340,7 @@ dependencies {
implementation "de.tum.in.ase.athene:client:0.0.2"
implementation "commons-fileupload:commons-fileupload:1.5"
implementation "net.lingala.zip4j:zip4j:2.11.5"
+ implementation "org.jgrapht:jgrapht-core:1.5.2"
annotationProcessor "org.hibernate:hibernate-jpamodelgen:${hibernate_version}"
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
index f48c5ee6b36c..5f5745217413 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/LearningPathRepository.java
@@ -4,6 +4,8 @@
import java.util.Optional;
+import javax.validation.constraints.NotNull;
+
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
@@ -20,6 +22,10 @@ public interface LearningPathRepository extends JpaRepository findByCourseIdAndUserId(long courseId, long userId);
+ default LearningPath findByCourseIdAndUserIdElseThrow(long courseId, long userId) {
+ return findByCourseIdAndUserId(courseId, userId).orElseThrow(() -> new EntityNotFoundException("LearningPath"));
+ }
+
@EntityGraph(type = LOAD, attributePaths = { "competencies" })
Optional findWithEagerCompetenciesByCourseIdAndUserId(long courseId, long userId);
@@ -40,4 +46,13 @@ SELECT COUNT (learningPath)
WHERE learningPath.course.id = :courseId AND learningPath.user.isDeleted = false AND learningPath.course.studentGroupName MEMBER OF learningPath.user.groups
""")
long countLearningPathsOfEnrolledStudentsInCourse(@Param("courseId") long courseId);
+
+ @EntityGraph(type = LOAD, attributePaths = { "competencies", "competencies.lectureUnits", "competencies.lectureUnits.completedUsers", "competencies.exercises",
+ "competencies.exercises.studentParticipations" })
+ Optional findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersById(long learningPathId);
+
+ @NotNull
+ default LearningPath findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersByIdElseThrow(long learningPathId) {
+ return findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersById(learningPathId).orElseThrow(() -> new EntityNotFoundException("LearningPath", learningPathId));
+ }
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index dba822bdd7fa..3917041d9798 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -1,10 +1,14 @@
package de.tum.in.www1.artemis.service;
import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
import javax.validation.constraints.NotNull;
+import org.jgrapht.alg.util.UnionFind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
@@ -13,12 +17,14 @@
import de.tum.in.www1.artemis.domain.Course;
import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.domain.competency.Competency;
+import de.tum.in.www1.artemis.domain.competency.CompetencyRelation;
import de.tum.in.www1.artemis.domain.competency.LearningPath;
import de.tum.in.www1.artemis.repository.*;
import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO;
import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathHealthDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathPageableSearchDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
import de.tum.in.www1.artemis.web.rest.util.PageUtil;
/**
@@ -45,12 +51,15 @@ public class LearningPathService {
private final CourseRepository courseRepository;
+ private final CompetencyRelationRepository competencyRelationRepository;
+
public LearningPathService(UserRepository userRepository, LearningPathRepository learningPathRepository, CompetencyProgressRepository competencyProgressRepository,
- CourseRepository courseRepository) {
+ CourseRepository courseRepository, CompetencyRelationRepository competencyRelationRepository) {
this.userRepository = userRepository;
this.learningPathRepository = learningPathRepository;
this.competencyProgressRepository = competencyProgressRepository;
this.courseRepository = courseRepository;
+ this.competencyRelationRepository = competencyRelationRepository;
}
/**
@@ -184,4 +193,233 @@ public LearningPathHealthDTO getHealthStatusForCourse(Course course) {
return new LearningPathHealthDTO(LearningPathHealthDTO.HealthStatus.MISSING, numberOfStudents - numberOfLearningPaths);
}
}
+
+ /**
+ * Generates Ngx representation of the learning path.
+ *
+ * @param learningPath the learning path for which the Ngx representation should be created
+ * @return Ngx representation of the learning path
+ * @see NgxLearningPathDTO
+ */
+ public NgxLearningPathDTO generateNgxRepresentation(@NotNull LearningPath learningPath) {
+ Set nodes = new HashSet<>();
+ Set edges = new HashSet<>();
+ Set clusters = new HashSet<>();
+ learningPath.getCompetencies().forEach(competency -> generateNgxRepresentationForCompetency(learningPath, competency, nodes, edges, clusters));
+ generateNgxRepresentationForRelations(learningPath, nodes, edges);
+ return new NgxLearningPathDTO(nodes, edges, clusters);
+ }
+
+ /**
+ * Generates Ngx representation for competency.
+ *
+ * A competency's representation consists of
+ *
+ * start node
+ * end node
+ * a node for each learning unit (exercises or lecture unit)
+ * edges from start node to each learning unit
+ * edges from each learning unit to end node
+ * a cluster consisting of all created nodes
+ *
+ *
+ * @param learningPath the learning path for which the representation should be created
+ * @param competency the competency for which the representation will be created
+ * @param nodes set of nodes to store the new nodes
+ * @param edges set of edges to store the new edges
+ * @param clusters set of clusters to store the new clusters
+ */
+ private void generateNgxRepresentationForCompetency(LearningPath learningPath, Competency competency, Set nodes, Set edges,
+ Set clusters) {
+ Set currentCluster = new HashSet<>();
+ // generates start and end node
+ final var startNodeId = getCompetencyStartNodeId(competency.getId());
+ final var endNodeId = getCompetencyEndNodeId(competency.getId());
+ currentCluster.add(new NgxLearningPathDTO.Node(startNodeId, NgxLearningPathDTO.NodeType.COMPETENCY_START, competency.getId()));
+ currentCluster.add(new NgxLearningPathDTO.Node(endNodeId, NgxLearningPathDTO.NodeType.COMPETENCY_END, competency.getId()));
+
+ // generate nodes and edges for lecture units
+ competency.getLectureUnits().forEach(lectureUnit -> {
+ currentCluster.add(new NgxLearningPathDTO.Node(getLectureUnitNodeId(competency.getId(), lectureUnit.getId()), NgxLearningPathDTO.NodeType.LECTURE_UNIT,
+ lectureUnit.getId(), lectureUnit.getLecture().getId(), lectureUnit.isCompletedFor(learningPath.getUser()), lectureUnit.getName()));
+ edges.add(new NgxLearningPathDTO.Edge(getLectureUnitInEdgeId(competency.getId(), lectureUnit.getId()), startNodeId,
+ getLectureUnitNodeId(competency.getId(), lectureUnit.getId())));
+ edges.add(new NgxLearningPathDTO.Edge(getLectureUnitOutEdgeId(competency.getId(), lectureUnit.getId()), getLectureUnitNodeId(competency.getId(), lectureUnit.getId()),
+ endNodeId));
+ });
+ // generate nodes and edges for exercises
+ competency.getExercises().forEach(exercise -> {
+ currentCluster.add(new NgxLearningPathDTO.Node(getExerciseNodeId(competency.getId(), exercise.getId()), NgxLearningPathDTO.NodeType.EXERCISE, exercise.getId(),
+ exercise.isCompletedFor(learningPath.getUser()), exercise.getTitle()));
+ edges.add(new NgxLearningPathDTO.Edge(getExerciseInEdgeId(competency.getId(), exercise.getId()), startNodeId, getExerciseNodeId(competency.getId(), exercise.getId())));
+ edges.add(new NgxLearningPathDTO.Edge(getExerciseOutEdgeId(competency.getId(), exercise.getId()), getExerciseNodeId(competency.getId(), exercise.getId()), endNodeId));
+ });
+ // if no linked learning units exist directly link start to end
+ if (currentCluster.size() == 2) {
+ edges.add(new NgxLearningPathDTO.Edge(getDirectEdgeId(competency.getId()), startNodeId, endNodeId));
+ }
+ // generate cluster for competency
+ var childNodeIds = currentCluster.stream().map(NgxLearningPathDTO.Node::id).collect(Collectors.toSet());
+ childNodeIds.add(startNodeId);
+ childNodeIds.add(endNodeId);
+ clusters.add(new NgxLearningPathDTO.Cluster(String.valueOf(competency.getId()), competency.getTitle(), childNodeIds));
+
+ nodes.addAll(currentCluster);
+ }
+
+ /**
+ * Generates Ngx representations for competency relations.
+ *
+ * The representation will contain:
+ *
+ *
+ * For each matching cluster (transitive closure of competencies that are in a match relation):
+ *
+ * two nodes (start and end of cluster) will be created
+ * edges from the start node of the cluster to each start node of the competencies
+ * edges from each end node of the competency to the end node of the cluster
+ *
+ *
+ *
+ * For each other relation: edge from head competency end node to tail competency start node. If competency is part of a matching cluster, the edge will be linked to the
+ * corresponding cluster start/end node.
+ *
+ *
+ *
+ * two nodes (start and end of cluster) will be created.
+ *
+ * @param learningPath the learning path for which the Ngx representation should be created
+ * @param nodes set of nodes to store the new nodes
+ * @param edges set of edges to store the new edges
+ */
+ private void generateNgxRepresentationForRelations(LearningPath learningPath, Set nodes, Set edges) {
+ final var relations = competencyRelationRepository.findAllByCourseId(learningPath.getCourse().getId());
+
+ // compute match clusters
+ Map competencyToMatchCluster = new HashMap<>();
+ final var competenciesInMatchRelation = relations.stream().filter(relation -> relation.getType().equals(CompetencyRelation.RelationType.MATCHES))
+ .flatMap(relation -> Stream.of(relation.getHeadCompetency().getId(), relation.getTailCompetency().getId())).collect(Collectors.toSet());
+ if (!competenciesInMatchRelation.isEmpty()) {
+ UnionFind matchClusters = new UnionFind<>(competenciesInMatchRelation);
+ relations.stream().filter(relation -> relation.getType().equals(CompetencyRelation.RelationType.MATCHES))
+ .forEach(relation -> matchClusters.union(relation.getHeadCompetency().getId(), relation.getTailCompetency().getId()));
+
+ // generate map between competencies and cluster node
+ AtomicInteger matchClusterId = new AtomicInteger();
+ relations.stream().filter(relation -> relation.getType().equals(CompetencyRelation.RelationType.MATCHES))
+ .flatMapToLong(relation -> LongStream.of(relation.getHeadCompetency().getId(), relation.getTailCompetency().getId())).distinct().forEach(competencyId -> {
+ var parentId = matchClusters.find(competencyId);
+ var clusterId = competencyToMatchCluster.computeIfAbsent(parentId, (key) -> matchClusterId.getAndIncrement());
+ competencyToMatchCluster.put(competencyId, clusterId);
+ });
+
+ // generate match cluster start and end nodes
+ for (int i = 0; i < matchClusters.numberOfSets(); i++) {
+ nodes.add(new NgxLearningPathDTO.Node(getMatchingClusterStartNodeId(i), NgxLearningPathDTO.NodeType.COMPETENCY_START));
+ nodes.add(new NgxLearningPathDTO.Node(getMatchingClusterEndNodeId(i), NgxLearningPathDTO.NodeType.COMPETENCY_END));
+ }
+
+ // generate edges between match cluster nodes and corresponding competencies
+ competencyToMatchCluster.forEach((competency, cluster) -> {
+ edges.add(new NgxLearningPathDTO.Edge(getInEdgeId(competency), getMatchingClusterStartNodeId(cluster), getCompetencyStartNodeId(competency)));
+ edges.add(new NgxLearningPathDTO.Edge(getOutEdgeId(competency), getCompetencyEndNodeId(competency), getMatchingClusterEndNodeId(cluster)));
+ });
+ }
+
+ // generate edges for remaining relations
+ final Set createdRelations = new HashSet<>();
+ relations.stream().filter(relation -> !relation.getType().equals(CompetencyRelation.RelationType.MATCHES))
+ .forEach(relation -> generateNgxRepresentationForRelation(relation, competencyToMatchCluster, createdRelations, edges));
+ }
+
+ /**
+ * Generates Ngx representations for competency relation.
+ *
+ * @param relation the relation for which the Ngx representation should be created
+ * @param competencyToMatchCluster map from competencies to corresponding cluster
+ * @param createdRelations set of edge ids that have already been created
+ * @param edges set of edges to store the new edges
+ */
+ private void generateNgxRepresentationForRelation(CompetencyRelation relation, Map competencyToMatchCluster, Set createdRelations,
+ Set edges) {
+ final var sourceId = relation.getHeadCompetency().getId();
+ String sourceNodeId;
+ if (competencyToMatchCluster.containsKey(sourceId)) {
+ sourceNodeId = getMatchingClusterEndNodeId(competencyToMatchCluster.get(sourceId));
+ }
+ else {
+ sourceNodeId = getCompetencyEndNodeId(sourceId);
+ }
+ final var targetId = relation.getTailCompetency().getId();
+ String targetNodeId;
+ if (competencyToMatchCluster.containsKey(targetId)) {
+ targetNodeId = getMatchingClusterStartNodeId(competencyToMatchCluster.get(targetId));
+ }
+ else {
+ targetNodeId = getCompetencyStartNodeId(targetId);
+ }
+ final String relationEdgeId = getRelationEdgeId(sourceNodeId, targetNodeId);
+ // skip if relation has already been created (possible for edges linked to matching cluster start/end nodes)
+ if (!createdRelations.contains(relationEdgeId)) {
+ final var edge = new NgxLearningPathDTO.Edge(relationEdgeId, sourceNodeId, targetNodeId);
+ edges.add(edge);
+ createdRelations.add(relationEdgeId);
+ }
+ }
+
+ public static String getCompetencyStartNodeId(long competencyId) {
+ return "node-" + competencyId + "-start";
+ }
+
+ public static String getCompetencyEndNodeId(long competencyId) {
+ return "node-" + competencyId + "-end";
+ }
+
+ public static String getLectureUnitNodeId(long competencyId, long lectureUnitId) {
+ return "node-" + competencyId + "-lu-" + lectureUnitId;
+ }
+
+ public static String getExerciseNodeId(long competencyId, long exerciseId) {
+ return "node-" + competencyId + "-ex-" + exerciseId;
+ }
+
+ public static String getMatchingClusterStartNodeId(long matchingClusterId) {
+ return "matching-" + matchingClusterId + "-start";
+ }
+
+ public static String getMatchingClusterEndNodeId(long matchingClusterId) {
+ return "matching-" + matchingClusterId + "-end";
+ }
+
+ public static String getLectureUnitInEdgeId(long competencyId, long lectureUnitId) {
+ return "edge-" + competencyId + "-lu-" + getInEdgeId(lectureUnitId);
+ }
+
+ public static String getLectureUnitOutEdgeId(long competencyId, long lectureUnitId) {
+ return "edge-" + competencyId + "-lu-" + getOutEdgeId(lectureUnitId);
+ }
+
+ public static String getExerciseInEdgeId(long competencyId, long exercise) {
+ return "edge-" + competencyId + "-ex-" + getInEdgeId(exercise);
+ }
+
+ public static String getExerciseOutEdgeId(long competencyId, long exercise) {
+ return "edge-" + competencyId + "-ex-" + getOutEdgeId(exercise);
+ }
+
+ public static String getInEdgeId(long id) {
+ return "edge-" + id + "-in";
+ }
+
+ public static String getOutEdgeId(long id) {
+ return "edge-" + id + "-out";
+ }
+
+ public static String getRelationEdgeId(String sourceNodeId, String targetNodeId) {
+ return "edge-relation-" + sourceNodeId + "-" + targetNodeId;
+ }
+
+ public static String getDirectEdgeId(long competencyId) {
+ return "edge-" + competencyId + "-direct";
+ }
}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index fca6892e86f5..e4c2e458a1b7 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -8,11 +8,13 @@
import org.springframework.web.bind.annotation.*;
import de.tum.in.www1.artemis.domain.Course;
+import de.tum.in.www1.artemis.domain.competency.LearningPath;
import de.tum.in.www1.artemis.repository.CourseRepository;
import de.tum.in.www1.artemis.repository.LearningPathRepository;
import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.security.Role;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastInstructor;
+import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.service.LearningPathService;
import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO;
@@ -125,4 +127,32 @@ public ResponseEntity getHealthStatusForCourse(@PathVaria
return ResponseEntity.ok(learningPathService.getHealthStatusForCourse(course));
}
+
+ /**
+ * GET /learning-path/:learningPathId : Gets the ngx representation of the learning path.
+ *
+ * @param learningPathId the id of the learning path that should be fetched
+ * @return the ResponseEntity with status 200 (OK) and with body the ngx representation of the learning path
+ */
+ @GetMapping("/learning-path/{learningPathId}")
+ @EnforceAtLeastStudent
+ public ResponseEntity getNgxLearningPath(@PathVariable Long learningPathId) {
+ log.debug("REST request to get ngx representation of learning path with id: {}", learningPathId);
+ LearningPath learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersByIdElseThrow(learningPathId);
+ Course course = courseRepository.findByIdElseThrow(learningPath.getCourse().getId());
+ if (!course.getLearningPathsEnabled()) {
+ throw new BadRequestException("Learning paths are not enabled for this course.");
+ }
+ if (authorizationCheckService.isStudentInCourse(course, null)) {
+ final var user = userRepository.getUser();
+ if (!user.getId().equals(learningPath.getUser().getId())) {
+ throw new AccessForbiddenException("You are not allowed to access another users learning path.");
+ }
+ }
+ else if (!authorizationCheckService.isAtLeastInstructorInCourse(course, null) && !authorizationCheckService.isAdmin()) {
+ throw new AccessForbiddenException("You are not allowed to access another users learning path.");
+ }
+ NgxLearningPathDTO graph = learningPathService.generateNgxRepresentation(learningPath);
+ return ResponseEntity.ok(graph);
+ }
}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
new file mode 100644
index 000000000000..f083b7eb4c52
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/NgxLearningPathDTO.java
@@ -0,0 +1,47 @@
+package de.tum.in.www1.artemis.web.rest.dto.competency;
+
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+/**
+ * Represents simplified learning path optimized for Ngx representation
+ */
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public record NgxLearningPathDTO(Set nodes, Set edges, Set clusters) {
+
+ public record Node(String id, NodeType type, Long linkedResource, Long linkedResourceParent, boolean completed, String label) {
+
+ public Node(String id, NodeType type, Long linkedResource, boolean completed, String label) {
+ this(id, type, linkedResource, null, completed, label);
+ }
+
+ public Node(String id, NodeType type, Long linkedResource, String label) {
+ this(id, type, linkedResource, false, label);
+ }
+
+ public Node(String id, NodeType type, String label) {
+ this(id, type, null, label);
+ }
+
+ public Node(String id, NodeType type, Long linkedResource) {
+ this(id, type, linkedResource, "");
+ }
+
+ public Node(String id, NodeType type) {
+ this(id, type, null, "");
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public record Edge(String id, String source, String target) {
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ public record Cluster(String id, String label, Set childNodeIds) {
+ }
+
+ public enum NodeType {
+ COMPETENCY_START, COMPETENCY_END, MATCH_START, MATCH_END, EXERCISE, LECTURE_UNIT,
+ }
+}
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 5cb2173e8cc3..a6e9085c3c21 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -387,4 +387,54 @@ void testUpdateLearningPathProgress() throws Exception {
void testGetHealthStatusForCourse() throws Exception {
request.get("/api/courses/" + course.getId() + "/learning-path-health", HttpStatus.OK, LearningPathHealthDTO.class);
}
+
+ @Test
+ @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
+ void testGetNgxLearningPathForLearningPathsDisabled() throws Exception {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ course.setLearningPathsEnabled(false);
+ courseRepository.save(course);
+ request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.BAD_REQUEST, NgxLearningPathDTO.class);
+ }
+
+ @Test
+ @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER")
+ void testGetNgxLearningPathForOtherStudent() throws Exception {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.FORBIDDEN, NgxLearningPathDTO.class);
+ }
+
+ /**
+ * This only tests if the end point successfully retrieves the graph representation. The correctness of the response is tested in LearningPathServiceTest.
+ *
+ * @throws Exception the request failed
+ * @see de.tum.in.www1.artemis.service.LearningPathServiceTest
+ */
+ @Test
+ @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER")
+ void testGetNgxLearningPathAsStudent() throws Exception {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.OK, NgxLearningPathDTO.class);
+ }
+
+ /**
+ * This only tests if the end point successfully retrieves the graph representation. The correctness of the response is tested in LearningPathServiceTest.
+ *
+ * @throws Exception the request failed
+ * @see de.tum.in.www1.artemis.service.LearningPathServiceTest
+ */
+ @Test
+ @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR")
+ void testGetNgxLearningPathAsInstructor() throws Exception {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow();
+ final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId());
+ request.get("/api/learning-path/" + learningPath.getId(), HttpStatus.OK, NgxLearningPathDTO.class);
+ }
}
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 12cb456454e3..a79cec2d0dd4 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -4,6 +4,7 @@
import java.time.ZonedDateTime;
import java.util.*;
+import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -11,14 +12,24 @@
import org.springframework.beans.factory.annotation.Autowired;
import de.tum.in.www1.artemis.AbstractSpringIntegrationBambooBitbucketJiraTest;
+import de.tum.in.www1.artemis.competency.CompetencyUtilService;
import de.tum.in.www1.artemis.competency.LearningPathUtilService;
import de.tum.in.www1.artemis.course.CourseFactory;
import de.tum.in.www1.artemis.course.CourseUtilService;
import de.tum.in.www1.artemis.domain.Course;
+import de.tum.in.www1.artemis.domain.Exercise;
+import de.tum.in.www1.artemis.domain.competency.Competency;
+import de.tum.in.www1.artemis.domain.competency.CompetencyRelation;
+import de.tum.in.www1.artemis.domain.competency.LearningPath;
+import de.tum.in.www1.artemis.domain.lecture.LectureUnit;
+import de.tum.in.www1.artemis.exercise.programmingexercise.ProgrammingExerciseUtilService;
+import de.tum.in.www1.artemis.lecture.LectureUtilService;
import de.tum.in.www1.artemis.repository.CourseRepository;
+import de.tum.in.www1.artemis.repository.LearningPathRepository;
import de.tum.in.www1.artemis.security.SecurityUtils;
import de.tum.in.www1.artemis.user.UserUtilService;
import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathHealthDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
class LearningPathServiceTest extends AbstractSpringIntegrationBambooBitbucketJiraTest {
@@ -39,6 +50,18 @@ class LearningPathServiceTest extends AbstractSpringIntegrationBambooBitbucketJi
@Autowired
CourseRepository courseRepository;
+ @Autowired
+ private CompetencyUtilService competencyUtilService;
+
+ @Autowired
+ private LectureUtilService lectureUtilService;
+
+ @Autowired
+ private ProgrammingExerciseUtilService programmingExerciseUtilService;
+
+ @Autowired
+ private LearningPathRepository learningPathRepository;
+
private Course course;
@BeforeEach
@@ -84,4 +107,204 @@ void testHealthStatusMissing() {
assertThat(healthStatus.missingLearningPaths()).isEqualTo(1);
}
}
+
+ @Nested
+ class GenerateNgxRepresentationBaseTest {
+
+ @Test
+ void testEmptyLearningPath() {
+ NgxLearningPathDTO expected = new NgxLearningPathDTO(Set.of(), Set.of(), Set.of());
+ generateAndAssert(expected);
+ }
+
+ @Test
+ void testEmptyCompetency() {
+ final var competency = competencyUtilService.createCompetency(course);
+ final var startNodeId = LearningPathService.getCompetencyStartNodeId(competency.getId());
+ final var endNodeId = LearningPathService.getCompetencyEndNodeId(competency.getId());
+ Set expectedNodes = getExpectedNodesOfEmptyCompetency(competency);
+ Set expectedEdges = Set.of(new NgxLearningPathDTO.Edge(LearningPathService.getDirectEdgeId(competency.getId()), startNodeId, endNodeId));
+ Set expectedClusters = Set
+ .of(new NgxLearningPathDTO.Cluster(String.valueOf(competency.getId()), competency.getTitle(), Set.of(startNodeId, endNodeId)));
+ NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
+ generateAndAssert(expected);
+ }
+
+ @Test
+ void testCompetencyWithLectureUnitAndExercise() {
+ var competency = competencyUtilService.createCompetency(course);
+ var lecture = lectureUtilService.createLecture(course, ZonedDateTime.now());
+ final var lectureUnit = lectureUtilService.createTextUnit();
+ lectureUtilService.addLectureUnitsToLecture(lecture, List.of(lectureUnit));
+ competencyUtilService.linkLectureUnitToCompetency(competency, lectureUnit);
+ final var exercise = programmingExerciseUtilService.addProgrammingExerciseToCourse(course, false);
+ competencyUtilService.linkExerciseToCompetency(competency, exercise);
+ final var startNodeId = LearningPathService.getCompetencyStartNodeId(competency.getId());
+ final var endNodeId = LearningPathService.getCompetencyEndNodeId(competency.getId());
+ Set expectedNodes = getExpectedNodesOfEmptyCompetency(competency);
+ expectedNodes.add(getNodeForLectureUnit(competency, lectureUnit));
+ expectedNodes.add(getNodeForExercise(competency, exercise));
+ Set expectedEdges = Set.of(
+ new NgxLearningPathDTO.Edge(LearningPathService.getLectureUnitInEdgeId(competency.getId(), lectureUnit.getId()), startNodeId,
+ LearningPathService.getLectureUnitNodeId(competency.getId(), lectureUnit.getId())),
+ new NgxLearningPathDTO.Edge(LearningPathService.getLectureUnitOutEdgeId(competency.getId(), lectureUnit.getId()),
+ LearningPathService.getLectureUnitNodeId(competency.getId(), lectureUnit.getId()), endNodeId),
+ new NgxLearningPathDTO.Edge(LearningPathService.getExerciseInEdgeId(competency.getId(), exercise.getId()), startNodeId,
+ LearningPathService.getExerciseNodeId(competency.getId(), exercise.getId())),
+ new NgxLearningPathDTO.Edge(LearningPathService.getExerciseOutEdgeId(competency.getId(), exercise.getId()),
+ LearningPathService.getExerciseNodeId(competency.getId(), exercise.getId()), endNodeId));
+ Set expectedClusters = Set.of(new NgxLearningPathDTO.Cluster(String.valueOf(competency.getId()), competency.getTitle(),
+ expectedNodes.stream().map(NgxLearningPathDTO.Node::id).collect(Collectors.toSet())));
+ NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
+ generateAndAssert(expected);
+ }
+
+ @Test
+ void testMultipleCompetencies() {
+ Competency[] competencies = { competencyUtilService.createCompetency(course), competencyUtilService.createCompetency(course),
+ competencyUtilService.createCompetency(course) };
+ String[] startNodeIds = Arrays.stream(competencies).map(Competency::getId).map(LearningPathService::getCompetencyStartNodeId).toArray(String[]::new);
+ String[] endNodeIds = Arrays.stream(competencies).map(Competency::getId).map(LearningPathService::getCompetencyEndNodeId).toArray(String[]::new);
+ Set expectedNodes = new HashSet<>();
+ Set expectedEdges = new HashSet<>();
+ Set expectedClusters = new HashSet<>();
+ for (int i = 0; i < competencies.length; i++) {
+ expectedNodes.addAll(getExpectedNodesOfEmptyCompetency(competencies[i]));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getDirectEdgeId(competencies[i].getId()), startNodeIds[i], endNodeIds[i]));
+ expectedClusters.add(new NgxLearningPathDTO.Cluster(String.valueOf(competencies[i].getId()), competencies[i].getTitle(), Set.of(startNodeIds[i], endNodeIds[i])));
+ }
+ NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
+ generateAndAssert(expected);
+ }
+ }
+
+ @Nested
+ class GenerateNgxRepresentationRelationTest {
+
+ private Competency competency1;
+
+ private Competency competency2;
+
+ private Set expectedNodes;
+
+ Set expectedEdges;
+
+ Set expectedClusters;
+
+ @BeforeEach
+ void setup() {
+ competency1 = competencyUtilService.createCompetency(course);
+ competency2 = competencyUtilService.createCompetency(course);
+ expectedNodes = new HashSet<>();
+ expectedEdges = new HashSet<>();
+ expectedClusters = new HashSet<>();
+ addExpectedComponentsForEmptyCompetencies(competency1, competency2);
+ }
+
+ void testSimpleRelation(CompetencyRelation.RelationType type) {
+ competencyUtilService.addRelation(competency1, type, competency2);
+ final var sourceNodeId = LearningPathService.getCompetencyEndNodeId(competency2.getId());
+ final var targetNodeId = LearningPathService.getCompetencyStartNodeId(competency1.getId());
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getRelationEdgeId(sourceNodeId, targetNodeId), sourceNodeId, targetNodeId));
+ NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
+ generateAndAssert(expected);
+ }
+
+ @Test
+ void testSingleRelates() {
+ testSimpleRelation(CompetencyRelation.RelationType.RELATES);
+ }
+
+ @Test
+ void testSingleAssumes() {
+ testSimpleRelation(CompetencyRelation.RelationType.ASSUMES);
+ }
+
+ @Test
+ void testSingleExtends() {
+ testSimpleRelation(CompetencyRelation.RelationType.EXTENDS);
+ }
+
+ @Test
+ void testSingleMatches() {
+ competencyUtilService.addRelation(competency1, CompetencyRelation.RelationType.MATCHES, competency2);
+ expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterStartNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_START, null, ""));
+ expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterEndNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_END, null, ""));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency1.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
+ LearningPathService.getCompetencyStartNodeId(competency1.getId())));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency1.getId()), LearningPathService.getCompetencyEndNodeId(competency1.getId()),
+ LearningPathService.getMatchingClusterEndNodeId(0)));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency2.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
+ LearningPathService.getCompetencyStartNodeId(competency2.getId())));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency2.getId()), LearningPathService.getCompetencyEndNodeId(competency2.getId()),
+ LearningPathService.getMatchingClusterEndNodeId(0)));
+ NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
+ generateAndAssert(expected);
+ }
+
+ @Test
+ void testMatchesTransitive() {
+ var competency3 = competencyUtilService.createCompetency(course);
+ addExpectedComponentsForEmptyCompetencies(competency3);
+
+ competencyUtilService.addRelation(competency1, CompetencyRelation.RelationType.MATCHES, competency2);
+ competencyUtilService.addRelation(competency2, CompetencyRelation.RelationType.MATCHES, competency3);
+ expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterStartNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_START, null, ""));
+ expectedNodes.add(new NgxLearningPathDTO.Node(LearningPathService.getMatchingClusterEndNodeId(0), NgxLearningPathDTO.NodeType.COMPETENCY_END, null, ""));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency1.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
+ LearningPathService.getCompetencyStartNodeId(competency1.getId())));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency1.getId()), LearningPathService.getCompetencyEndNodeId(competency1.getId()),
+ LearningPathService.getMatchingClusterEndNodeId(0)));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency2.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
+ LearningPathService.getCompetencyStartNodeId(competency2.getId())));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency2.getId()), LearningPathService.getCompetencyEndNodeId(competency2.getId()),
+ LearningPathService.getMatchingClusterEndNodeId(0)));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getInEdgeId(competency3.getId()), LearningPathService.getMatchingClusterStartNodeId(0),
+ LearningPathService.getCompetencyStartNodeId(competency3.getId())));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getOutEdgeId(competency3.getId()), LearningPathService.getCompetencyEndNodeId(competency3.getId()),
+ LearningPathService.getMatchingClusterEndNodeId(0)));
+ NgxLearningPathDTO expected = new NgxLearningPathDTO(expectedNodes, expectedEdges, expectedClusters);
+ generateAndAssert(expected);
+ }
+
+ private void addExpectedComponentsForEmptyCompetencies(Competency... competencies) {
+ for (var competency : competencies) {
+ expectedNodes.addAll(getExpectedNodesOfEmptyCompetency(competency));
+ expectedEdges.add(new NgxLearningPathDTO.Edge(LearningPathService.getDirectEdgeId(competency.getId()),
+ LearningPathService.getCompetencyStartNodeId(competency.getId()), LearningPathService.getCompetencyEndNodeId(competency.getId())));
+ expectedClusters.add(new NgxLearningPathDTO.Cluster(String.valueOf(competency.getId()), competency.getTitle(),
+ Set.of(LearningPathService.getCompetencyStartNodeId(competency.getId()), LearningPathService.getCompetencyEndNodeId(competency.getId()))));
+ }
+ }
+ }
+
+ private void generateAndAssert(NgxLearningPathDTO expected) {
+ LearningPath learningPath = learningPathUtilService.createLearningPathInCourse(course);
+ learningPath = learningPathRepository.findWithEagerCompetenciesAndLearningObjectsAndCompletedUsersByIdElseThrow(learningPath.getId());
+ NgxLearningPathDTO actual = learningPathService.generateNgxRepresentation(learningPath);
+ assertThat(actual).isNotNull();
+ assertNgxRepEquals(actual, expected);
+ }
+
+ private void assertNgxRepEquals(NgxLearningPathDTO was, NgxLearningPathDTO expected) {
+ assertThat(was.nodes()).as("correct nodes").containsExactlyInAnyOrderElementsOf(expected.nodes());
+ assertThat(was.edges()).as("correct edges").containsExactlyInAnyOrderElementsOf(expected.edges());
+ assertThat(was.clusters()).as("correct clusters").containsExactlyInAnyOrderElementsOf(expected.clusters());
+ }
+
+ private static Set getExpectedNodesOfEmptyCompetency(Competency competency) {
+ return new HashSet<>(Set.of(
+ new NgxLearningPathDTO.Node(LearningPathService.getCompetencyStartNodeId(competency.getId()), NgxLearningPathDTO.NodeType.COMPETENCY_START, competency.getId(), ""),
+ new NgxLearningPathDTO.Node(LearningPathService.getCompetencyEndNodeId(competency.getId()), NgxLearningPathDTO.NodeType.COMPETENCY_END, competency.getId(), "")));
+ }
+
+ private static NgxLearningPathDTO.Node getNodeForLectureUnit(Competency competency, LectureUnit lectureUnit) {
+ return new NgxLearningPathDTO.Node(LearningPathService.getLectureUnitNodeId(competency.getId(), lectureUnit.getId()), NgxLearningPathDTO.NodeType.LECTURE_UNIT,
+ lectureUnit.getId(), lectureUnit.getLecture().getId(), false, lectureUnit.getName());
+ }
+
+ private static NgxLearningPathDTO.Node getNodeForExercise(Competency competency, Exercise exercise) {
+ return new NgxLearningPathDTO.Node(LearningPathService.getExerciseNodeId(competency.getId(), exercise.getId()), NgxLearningPathDTO.NodeType.EXERCISE, exercise.getId(),
+ exercise.getTitle());
+ }
}
From 5f347286af91bddd7b940633404f83a8c277a424 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 16:03:27 +0200
Subject: [PATCH 127/215] fix code style
---
.../tum/in/www1/artemis/service/LearningPathServiceTest.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 12cb456454e3..6f3b240fe457 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -34,10 +34,10 @@ class LearningPathServiceTest extends AbstractSpringIntegrationBambooBitbucketJi
private CourseUtilService courseUtilService;
@Autowired
- UserUtilService userUtilService;
+ private UserUtilService userUtilService;
@Autowired
- CourseRepository courseRepository;
+ private CourseRepository courseRepository;
private Course course;
From b86beb216d34108e99ea250d5e34d29b70a25faa Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 16:07:04 +0200
Subject: [PATCH 128/215] add feature toggle annotation to endpoints
---
.../artemis/web/rest/LearningPathResource.java | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index fca6892e86f5..05008a418ea9 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -9,12 +9,12 @@
import de.tum.in.www1.artemis.domain.Course;
import de.tum.in.www1.artemis.repository.CourseRepository;
-import de.tum.in.www1.artemis.repository.LearningPathRepository;
-import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.security.Role;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastInstructor;
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.service.LearningPathService;
+import de.tum.in.www1.artemis.service.feature.Feature;
+import de.tum.in.www1.artemis.service.feature.FeatureToggle;
import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO;
import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.*;
@@ -30,19 +30,12 @@ public class LearningPathResource {
private final AuthorizationCheckService authorizationCheckService;
- private final UserRepository userRepository;
-
private final LearningPathService learningPathService;
- private final LearningPathRepository learningPathRepository;
-
- public LearningPathResource(CourseRepository courseRepository, AuthorizationCheckService authorizationCheckService, UserRepository userRepository,
- LearningPathService learningPathService, LearningPathRepository learningPathRepository) {
+ public LearningPathResource(CourseRepository courseRepository, AuthorizationCheckService authorizationCheckService, LearningPathService learningPathService) {
this.courseRepository = courseRepository;
this.authorizationCheckService = authorizationCheckService;
- this.userRepository = userRepository;
this.learningPathService = learningPathService;
- this.learningPathRepository = learningPathRepository;
}
/**
@@ -52,6 +45,7 @@ public LearningPathResource(CourseRepository courseRepository, AuthorizationChec
* @return the ResponseEntity with status 200 (OK)
*/
@PutMapping("/courses/{courseId}/learning-paths/enable")
+ @FeatureToggle(Feature.LearningPaths)
@EnforceAtLeastInstructor
public ResponseEntity enableLearningPathsForCourse(@PathVariable Long courseId) {
log.debug("REST request to enable learning paths for course with id: {}", courseId);
@@ -75,6 +69,7 @@ public ResponseEntity enableLearningPathsForCourse(@PathVariable Long cour
* @return the ResponseEntity with status 200 (OK)
*/
@PutMapping("/courses/{courseId}/learning-paths/generate-missing")
+ @FeatureToggle(Feature.LearningPaths)
@EnforceAtLeastInstructor
public ResponseEntity generateMissingLearningPathsForCourse(@PathVariable Long courseId) {
log.debug("REST request to generate missing learning paths for course with id: {}", courseId);
@@ -95,6 +90,7 @@ public ResponseEntity generateMissingLearningPathsForCourse(@PathVariable
* @return the ResponseEntity with status 200 (OK) and with body the desired page, sorted and matching the given query
*/
@GetMapping("/courses/{courseId}/learning-paths")
+ @FeatureToggle(Feature.LearningPaths)
@EnforceAtLeastInstructor
public ResponseEntity> getLearningPathsOnPage(@PathVariable Long courseId, PageableSearchDTO search) {
log.debug("REST request to get learning paths for course with id: {}", courseId);
@@ -114,6 +110,7 @@ public ResponseEntity> getLea
* @return the ResponseEntity with status 200 (OK) and with body the health status
*/
@GetMapping("/courses/{courseId}/learning-path-health")
+ @FeatureToggle(Feature.LearningPaths)
@EnforceAtLeastInstructor
public ResponseEntity getHealthStatusForCourse(@PathVariable long courseId) {
log.debug("REST request to get health status of learning paths in course with id: {}", courseId);
From ea54c74aeeced4accd799bf19f86d459513a68cf Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Wed, 2 Aug 2023 16:09:50 +0200
Subject: [PATCH 129/215] add feature toggle annotation to endpoint
---
.../www1/artemis/web/rest/LearningPathResource.java | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index b8ab67dd4b71..309301101d3d 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -10,6 +10,8 @@
import de.tum.in.www1.artemis.domain.Course;
import de.tum.in.www1.artemis.domain.competency.LearningPath;
import de.tum.in.www1.artemis.repository.CourseRepository;
+import de.tum.in.www1.artemis.repository.LearningPathRepository;
+import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.security.Role;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastInstructor;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
@@ -34,10 +36,17 @@ public class LearningPathResource {
private final LearningPathService learningPathService;
- public LearningPathResource(CourseRepository courseRepository, AuthorizationCheckService authorizationCheckService, LearningPathService learningPathService) {
+ private final LearningPathRepository learningPathRepository;
+
+ private final UserRepository userRepository;
+
+ public LearningPathResource(CourseRepository courseRepository, AuthorizationCheckService authorizationCheckService, LearningPathService learningPathService,
+ LearningPathRepository learningPathRepository, UserRepository userRepository) {
this.courseRepository = courseRepository;
this.authorizationCheckService = authorizationCheckService;
this.learningPathService = learningPathService;
+ this.learningPathRepository = learningPathRepository;
+ this.userRepository = userRepository;
}
/**
@@ -132,6 +141,7 @@ public ResponseEntity getHealthStatusForCourse(@PathVaria
* @return the ResponseEntity with status 200 (OK) and with body the ngx representation of the learning path
*/
@GetMapping("/learning-path/{learningPathId}")
+ @FeatureToggle(Feature.LearningPaths)
@EnforceAtLeastStudent
public ResponseEntity getNgxLearningPath(@PathVariable Long learningPathId) {
log.debug("REST request to get ngx representation of learning path with id: {}", learningPathId);
From 47610965934eba6079c159ce1233edd4c2bc7c32 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 3 Aug 2023 11:21:13 +0200
Subject: [PATCH 130/215] remove unused and fix code style
---
.../artemis/repository/UserRepository.java | 2 +-
.../artemis/service/LearningPathService.java | 6 +-
.../www1/artemis/web/rest/CourseResource.java | 15 ---
.../web/rest/LearningPathResource.java | 3 +-
.../manage/course-management.service.ts | 8 --
.../lecture-unit/lectureUnit.model.ts | 32 ------
.../overview/course-overview.component.html | 1 -
.../app/overview/course-overview.component.ts | 2 -
.../app/overview/courses-routing.module.ts | 4 -
.../discussion-section.component.ts | 6 +-
.../course-exercise-details.component.ts | 10 +-
.../shared/layouts/navbar/navbar.component.ts | 3 -
src/main/webapp/app/shared/shared.module.ts | 3 -
.../sticky-popover.directive.ts | 105 ------------------
src/main/webapp/i18n/de/competency.json | 21 ----
.../webapp/i18n/de/student-dashboard.json | 1 -
src/main/webapp/i18n/en/competency.json | 21 ----
.../webapp/i18n/en/student-dashboard.json | 1 -
.../artemis/course/CourseTestService.java | 22 ----
...rseBitbucketBambooJiraIntegrationTest.java | 6 -
.../CourseGitlabJenkinsIntegrationTest.java | 6 -
.../service/LearningPathServiceTest.java | 2 +-
22 files changed, 10 insertions(+), 270 deletions(-)
delete mode 100644 src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java
index 91ec0c29f870..4f9cd9a77b77 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java
@@ -98,7 +98,7 @@ public interface UserRepository extends JpaRepository, JpaSpecificat
Optional findOneWithGroupsAuthoritiesAndGuidedTourSettingsByLogin(String login);
@EntityGraph(type = LOAD, attributePaths = { "learningPaths" })
- Optional findWithLearningPathsById(long id);
+ Optional findWithLearningPathsById(long userId);
@Query("SELECT count(*) FROM User user WHERE user.isDeleted = false AND :#{#groupName} MEMBER OF user.groups")
Long countByGroupsIsContaining(@Param("groupName") String groupName);
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index dba822bdd7fa..d7c1d572fe3d 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -1,6 +1,6 @@
package de.tum.in.www1.artemis.service;
-import java.util.*;
+import java.util.List;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
@@ -27,9 +27,7 @@
* This includes
*
* the generation of learning paths in courses,
- * performing pageable searches for learning paths,
- * generation of the Ngx representation of learning paths,
- * and the computation of recommended learning objects for a specific learning path.
+ * and performing pageable searches for learning paths.
*
*/
@Service
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
index feaf469135a8..f6b073ef86c2 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/CourseResource.java
@@ -1212,19 +1212,4 @@ public ResponseEntity> addUsersToCourseGroup(@PathVariable Long
List notFoundStudentsDtos = courseService.registerUsersForCourseGroup(courseId, studentDtos, courseGroup);
return ResponseEntity.ok().body(notFoundStudentsDtos);
}
-
- /**
- * GET /courses/:courseId/learning-paths-enabled Get a course by id with eagerly loaded learning paths
- *
- * @param courseId the id of the course
- * @return the course with eagerly loaded learning paths
- */
- @GetMapping("courses/{courseId}/learning-paths-enabled")
- @EnforceAtLeastInstructor
- public ResponseEntity getCourseLearningPathsEnabled(@PathVariable Long courseId) {
- log.debug("REST request to get if course has learning paths enabled : {}", courseId);
- Course course = courseRepository.findByIdElseThrow(courseId);
- authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
- return ResponseEntity.ok(course.getLearningPathsEnabled());
- }
}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 05008a418ea9..8aedc991981f 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -17,7 +17,8 @@
import de.tum.in.www1.artemis.service.feature.FeatureToggle;
import de.tum.in.www1.artemis.web.rest.dto.PageableSearchDTO;
import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO;
-import de.tum.in.www1.artemis.web.rest.dto.competency.*;
+import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathHealthDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathPageableSearchDTO;
import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;
@RestController
diff --git a/src/main/webapp/app/course/manage/course-management.service.ts b/src/main/webapp/app/course/manage/course-management.service.ts
index 5ff5ce30f36d..68cd769eaca9 100644
--- a/src/main/webapp/app/course/manage/course-management.service.ts
+++ b/src/main/webapp/app/course/manage/course-management.service.ts
@@ -685,12 +685,4 @@ export class CourseManagementService {
course?.exams?.forEach((exam) => this.entityTitleService.setTitle(EntityType.EXAM, [exam.id], exam.title));
course?.organizations?.forEach((org) => this.entityTitleService.setTitle(EntityType.ORGANIZATION, [org.id], org.name));
}
-
- /**
- * retrieves if the course with the given id has enabled learning paths
- * @param courseId the id of the course
- */
- getCourseLearningPathsEnabled(courseId: number): Observable> {
- return this.http.get(`${this.resourceUrl}/${courseId}/learning-paths-enabled`, { observe: 'response' });
- }
}
diff --git a/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts b/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
index b878b4554020..647d462e0cb7 100644
--- a/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
+++ b/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
@@ -2,8 +2,6 @@ import { BaseEntity } from 'app/shared/model/base-entity';
import dayjs from 'dayjs/esm';
import { Lecture } from 'app/entities/lecture.model';
import { Competency } from 'app/entities/competency.model';
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { faDownload, faLink, faQuestion, faScroll, faVideo } from '@fortawesome/free-solid-svg-icons';
// IMPORTANT NOTICE: The following strings have to be consistent with
// the ones defined in LectureUnit.java
@@ -15,22 +13,6 @@ export enum LectureUnitType {
ONLINE = 'online',
}
-export const lectureUnitIcons = {
- [LectureUnitType.ATTACHMENT]: faDownload,
- [LectureUnitType.EXERCISE]: faQuestion,
- [LectureUnitType.TEXT]: faScroll,
- [LectureUnitType.VIDEO]: faVideo,
- [LectureUnitType.ONLINE]: faLink,
-};
-
-export const lectureUnitTooltips = {
- [LectureUnitType.ATTACHMENT]: 'artemisApp.attachmentUnit.tooltip',
- [LectureUnitType.EXERCISE]: '',
- [LectureUnitType.TEXT]: 'artemisApp.textUnit.tooltip',
- [LectureUnitType.VIDEO]: 'artemisApp.videoUnit.tooltip',
- [LectureUnitType.ONLINE]: 'artemisApp.onlineUnit.tooltip',
-};
-
export abstract class LectureUnit implements BaseEntity {
public id?: number;
public name?: string;
@@ -46,17 +28,3 @@ export abstract class LectureUnit implements BaseEntity {
this.type = type;
}
}
-
-export function getIcon(lectureUnitType: LectureUnitType): IconProp {
- if (!lectureUnitType) {
- return faQuestion as IconProp;
- }
- return lectureUnitIcons[lectureUnitType] as IconProp;
-}
-
-export function getIconTooltip(lectureUnitType: LectureUnitType) {
- if (!lectureUnitType) {
- return '';
- }
- return lectureUnitTooltips[lectureUnitType];
-}
diff --git a/src/main/webapp/app/overview/course-overview.component.html b/src/main/webapp/app/overview/course-overview.component.html
index 5b34697f6bc8..937df274a472 100644
--- a/src/main/webapp/app/overview/course-overview.component.html
+++ b/src/main/webapp/app/overview/course-overview.component.html
@@ -1,7 +1,6 @@
-
diff --git a/src/main/webapp/app/overview/course-overview.component.ts b/src/main/webapp/app/overview/course-overview.component.ts
index b1a8a54fb56c..835ac782f5b4 100644
--- a/src/main/webapp/app/overview/course-overview.component.ts
+++ b/src/main/webapp/app/overview/course-overview.component.ts
@@ -23,7 +23,6 @@ import {
faFlag,
faGraduationCap,
faListAlt,
- faNetworkWired,
faPersonChalkboard,
faSync,
faTable,
@@ -83,7 +82,6 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit
faWrench = faWrench;
faTable = faTable;
faFlag = faFlag;
- faNetworkWired = faNetworkWired;
faListAlt = faListAlt;
faChartBar = faChartBar;
faFilePdf = faFilePdf;
diff --git a/src/main/webapp/app/overview/courses-routing.module.ts b/src/main/webapp/app/overview/courses-routing.module.ts
index 41cb383835a2..3272d689f660 100644
--- a/src/main/webapp/app/overview/courses-routing.module.ts
+++ b/src/main/webapp/app/overview/courses-routing.module.ts
@@ -65,10 +65,6 @@ const routes: Routes = [
path: 'competencies',
loadChildren: () => import('./course-competencies/course-competencies.module').then((m) => m.CourseCompetenciesModule),
},
- {
- path: 'learning-path',
- loadChildren: () => import('app/course/learning-paths/learning-paths.module').then((m) => m.ArtemisLearningPathsModule),
- },
{
path: 'discussion',
loadChildren: () => import('./course-discussion/course-discussion.module').then((m) => m.CourseDiscussionModule),
diff --git a/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts b/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
index edc9ed17430f..c4cc4e431a3f 100644
--- a/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
+++ b/src/main/webapp/app/overview/discussion-section/discussion-section.component.ts
@@ -70,11 +70,7 @@ export class DiscussionSectionComponent extends CourseDiscussionDirective implem
this.course = this.exercise?.course ?? this.lecture?.course;
this.metisService.setCourse(this.course);
this.metisService.setPageType(this.pageType);
- if (routeParams.params.courseId) {
- this.setChannel(routeParams.params.courseId);
- } else if (this.course?.id) {
- this.setChannel(this.course.id);
- }
+ this.setChannel(routeParams.params.courseId);
this.createEmptyPost();
this.resetFormGroup();
});
diff --git a/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts b/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts
index e98a4222b6d9..ffb40ecdc9f9 100644
--- a/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts
+++ b/src/main/webapp/app/overview/exercise-details/course-exercise-details.component.ts
@@ -69,7 +69,7 @@ export class CourseExerciseDetailsComponent implements OnInit, OnDestroy {
readonly isMessagingEnabled = isMessagingEnabled;
private currentUser: User;
- public exerciseId: number;
+ private exerciseId: number;
public courseId: number;
public course: Course;
public exercise?: Exercise;
@@ -142,12 +142,8 @@ export class CourseExerciseDetailsComponent implements OnInit, OnDestroy {
this.route.params.subscribe((params) => {
const didExerciseChange = this.exerciseId !== parseInt(params['exerciseId'], 10);
const didCourseChange = this.courseId !== parseInt(params['courseId'], 10);
- if (params['exerciseId']) {
- this.exerciseId = parseInt(params['exerciseId'], 10);
- }
- if (params['courseId']) {
- this.courseId = parseInt(params['courseId'], 10);
- }
+ this.exerciseId = parseInt(params['exerciseId'], 10);
+ this.courseId = parseInt(params['courseId'], 10);
this.courseService.find(this.courseId).subscribe((courseResponse) => (this.course = courseResponse.body!));
this.accountService.identity().then((user: User) => {
this.currentUser = user;
diff --git a/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts b/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts
index b0bb13855a86..44f093dba011 100644
--- a/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts
+++ b/src/main/webapp/app/shared/layouts/navbar/navbar.component.ts
@@ -336,9 +336,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
exercises: 'artemisApp.courseOverview.menu.exercises',
lectures: 'artemisApp.courseOverview.menu.lectures',
competencies: 'artemisApp.courseOverview.menu.competencies',
- learning_path: 'artemisApp.courseOverview.menu.learningPath',
- lecture_unit: 'artemisApp.learningPath.breadcrumbs.lectureUnit',
- exercise: 'artemisApp.learningPath.breadcrumbs.exercise',
statistics: 'artemisApp.courseOverview.menu.statistics',
discussion: 'artemisApp.metis.communication.label',
messages: 'artemisApp.conversationsLayout.breadCrumbLabel',
diff --git a/src/main/webapp/app/shared/shared.module.ts b/src/main/webapp/app/shared/shared.module.ts
index 774efdf960a3..108a76462502 100644
--- a/src/main/webapp/app/shared/shared.module.ts
+++ b/src/main/webapp/app/shared/shared.module.ts
@@ -23,7 +23,6 @@ import { AssessmentWarningComponent } from 'app/assessment/assessment-warning/as
import { JhiConnectionWarningComponent } from 'app/shared/connection-warning/connection-warning.component';
import { LoadingIndicatorContainerComponent } from 'app/shared/loading-indicator-container/loading-indicator-container.component';
import { CompetencySelectionComponent } from 'app/shared/competency-selection/competency-selection.component';
-import { StickyPopoverDirective } from 'app/shared/sticky-popover/sticky-popover.directive';
@NgModule({
imports: [ArtemisSharedLibsModule, ArtemisSharedCommonModule, ArtemisSharedPipesModule, RouterModule],
@@ -48,7 +47,6 @@ import { StickyPopoverDirective } from 'app/shared/sticky-popover/sticky-popover
ItemCountComponent,
ConsistencyCheckComponent,
AssessmentWarningComponent,
- StickyPopoverDirective,
],
exports: [
ArtemisSharedLibsModule,
@@ -74,7 +72,6 @@ import { StickyPopoverDirective } from 'app/shared/sticky-popover/sticky-popover
ConsistencyCheckComponent,
AssessmentWarningComponent,
CompetencySelectionComponent,
- StickyPopoverDirective,
],
})
export class ArtemisSharedModule {}
diff --git a/src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts b/src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts
deleted file mode 100644
index 4950c06f7937..000000000000
--- a/src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import {
- ApplicationRef,
- ChangeDetectorRef,
- Directive,
- ElementRef,
- Inject,
- Injector,
- Input,
- NgZone,
- OnDestroy,
- OnInit,
- Renderer2,
- TemplateRef,
- ViewContainerRef,
-} from '@angular/core';
-
-import { DOCUMENT } from '@angular/common';
-import { NgbPopover, NgbPopoverConfig } from '@ng-bootstrap/ng-bootstrap';
-@Directive({
- selector: '[jhiStickyPopover]',
-})
-export class StickyPopoverDirective extends NgbPopover implements OnInit, OnDestroy {
- @Input() jhiStickyPopover: TemplateRef;
-
- popoverTitle: string;
-
- triggers: string;
- container: string;
- ngpPopover: TemplateRef;
- canClosePopover: boolean;
-
- toggle(): void {
- super.toggle();
- }
-
- isOpen(): boolean {
- return super.isOpen();
- }
-
- constructor(
- private _elRef: ElementRef,
- private _render: Renderer2,
- injector: Injector,
- private viewContainerRef: ViewContainerRef,
- config: NgbPopoverConfig,
- ngZone: NgZone,
- private changeRef: ChangeDetectorRef,
- private applicationRef: ApplicationRef,
- @Inject(DOCUMENT) _document: any,
- ) {
- super(_elRef, _render, injector, viewContainerRef, config, ngZone, _document, changeRef, applicationRef);
- this.triggers = 'manual';
- this.popoverTitle = '';
- this.container = 'body';
- }
-
- ngOnInit(): void {
- super.ngOnInit();
- this.ngbPopover = this.jhiStickyPopover;
-
- this._render.listen(this._elRef.nativeElement, 'mouseenter', () => {
- this.canClosePopover = true;
- this.open();
- });
-
- this._render.listen(this._elRef.nativeElement, 'mouseleave', () => {
- setTimeout(() => {
- if (this.canClosePopover) {
- this.close();
- }
- }, 100);
- });
-
- this._render.listen(this._elRef.nativeElement, 'click', () => {
- this.close();
- });
- }
-
- ngOnDestroy(): void {
- super.ngOnDestroy();
- }
-
- open() {
- super.open();
- setTimeout(() => {
- const popover = window.document.querySelector('.popover');
- this._render.listen(popover, 'mouseover', () => {
- this.canClosePopover = false;
- });
-
- this._render.listen(popover, 'mouseout', () => {
- this.canClosePopover = true;
- setTimeout(() => {
- if (this.canClosePopover) {
- this.close();
- }
- }, 0);
- });
- }, 0);
- }
-
- close() {
- super.close();
- }
-}
diff --git a/src/main/webapp/i18n/de/competency.json b/src/main/webapp/i18n/de/competency.json
index 4f4bc84ecdf9..62d59e2cd035 100644
--- a/src/main/webapp/i18n/de/competency.json
+++ b/src/main/webapp/i18n/de/competency.json
@@ -151,10 +151,6 @@
},
"learningPath": {
"learningPathButton": "Lernpfade",
- "breadcrumbs": {
- "lectureUnit": "Vorlesungseinheit",
- "exercise": "Aufgabe"
- },
"manageLearningPaths": {
"title": "Lernpfadmanagement",
"health": {
@@ -173,24 +169,7 @@
"name": "Name",
"login": "Login",
"progress": "Fortschritt"
- },
- "progressNav": {
- "header": "Lernpfad",
- "refresh": "Aktualisieren",
- "center": "Zentrieren"
}
- },
- "sideBar": {
- "hide": "Lernpfad ausblenden",
- "show": "Lernpfad einblenden",
- "header": "Lernpfad"
- },
- "participate": {
- "noTaskSelected": "Aktuell hast du keine Vorlesungseinheit oder Aufgabe ausgewählt.",
- "prev": "Zurück",
- "prevHint": "Zurück zur letzen Vorlesungseinheit oder Aufgabe",
- "next": "Weiter",
- "nextHint": "Weiter zur nächten Empfehlung"
}
}
}
diff --git a/src/main/webapp/i18n/de/student-dashboard.json b/src/main/webapp/i18n/de/student-dashboard.json
index bc34950a6fe6..d2d0d5d7afd6 100644
--- a/src/main/webapp/i18n/de/student-dashboard.json
+++ b/src/main/webapp/i18n/de/student-dashboard.json
@@ -33,7 +33,6 @@
"statistics": "Statistiken",
"lectures": "Vorlesungen",
"competencies": "Kompetenzen",
- "learningPath": "Lernpfad",
"tutorialGroups": "Tutorien",
"exams": "Klausuren",
"testExam": "Testklausur",
diff --git a/src/main/webapp/i18n/en/competency.json b/src/main/webapp/i18n/en/competency.json
index 73d444187ab1..fbc7c96a12aa 100644
--- a/src/main/webapp/i18n/en/competency.json
+++ b/src/main/webapp/i18n/en/competency.json
@@ -150,10 +150,6 @@
},
"learningPath": {
"learningPathButton": "Learning Paths",
- "breadcrumbs": {
- "lectureUnit": "Lecture Unit",
- "exercise": "Exercise"
- },
"manageLearningPaths": {
"title": "Learning Path Management",
"health": {
@@ -172,24 +168,7 @@
"name": "Name",
"login": "Login",
"progress": "Progress"
- },
- "progressNav": {
- "header": "Learning Path",
- "refresh": "Refresh",
- "center": "Center view"
}
- },
- "sideBar": {
- "hide": "Hide Learning Path",
- "show": "Show Learning Path",
- "header": "Learning Path"
- },
- "participate": {
- "noTaskSelected": "Currently you have no lecture unit or exercise selected.",
- "prev": "Previous",
- "prevHint": "Return to your previous task",
- "next": "Next",
- "nextHint": "Go to next recommended task"
}
}
}
diff --git a/src/main/webapp/i18n/en/student-dashboard.json b/src/main/webapp/i18n/en/student-dashboard.json
index b0212682ffba..bd38b3313712 100644
--- a/src/main/webapp/i18n/en/student-dashboard.json
+++ b/src/main/webapp/i18n/en/student-dashboard.json
@@ -34,7 +34,6 @@
"lectures": "Lectures",
"tutorialGroups": "Tutorials",
"competencies": "Competencies",
- "learningPath": "Learning Path",
"exams": "Exams",
"testExam": "Test Exam",
"communication": "Communication",
diff --git a/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java b/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java
index 6f85679b4ae3..1e5eae1968d1 100644
--- a/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java
+++ b/src/test/java/de/tum/in/www1/artemis/course/CourseTestService.java
@@ -3129,26 +3129,4 @@ public void testEditCourseRemoveExistingIcon() throws Exception {
private String getUpdateOnlineCourseConfigurationPath(String courseId) {
return "/api/courses/" + courseId + "/onlineCourseConfiguration";
}
-
- // Test
- public void testGetCourseLearningPathsEnabled_AsInstructor() throws Exception {
- String testSuffix = "getlearningpathsenabled";
- adjustUserGroupsToCustomGroups(testSuffix);
-
- var course1 = courseUtilService.createCourse();
- adjustCourseGroups(course1, testSuffix);
- course1.setLearningPathsEnabled(true);
- course1 = courseRepo.save(course1);
-
- var course2 = courseUtilService.createCourse();
- adjustCourseGroups(course2, testSuffix);
- course2.setLearningPathsEnabled(false);
- course2 = courseRepo.save(course2);
-
- final var result1 = request.get("/api/courses/" + course1.getId() + "/learning-paths-enabled", HttpStatus.OK, Boolean.class);
- assertThat(result1).isTrue();
-
- final var result2 = request.get("/api/courses/" + course2.getId() + "/learning-paths-enabled", HttpStatus.OK, Boolean.class);
- assertThat(result2).isFalse();
- }
}
diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseBitbucketBambooJiraIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseBitbucketBambooJiraIntegrationTest.java
index 1c24d7cc0243..cf94dabc272d 100644
--- a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseBitbucketBambooJiraIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseBitbucketBambooJiraIntegrationTest.java
@@ -918,10 +918,4 @@ void testUpdateValidOnlineCourseConfiguration() throws Exception {
void testEditCourseRemoveExistingIcon() throws Exception {
courseTestService.testEditCourseRemoveExistingIcon();
}
-
- @Test
- @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
- void testGetCourseLearningPathsEnabled_AsInstructor() throws Exception {
- courseTestService.testGetCourseLearningPathsEnabled_AsInstructor();
- }
}
diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseGitlabJenkinsIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseGitlabJenkinsIntegrationTest.java
index 690edef3a80c..f859fca4b856 100644
--- a/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseGitlabJenkinsIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/exercise/programmingexercise/CourseGitlabJenkinsIntegrationTest.java
@@ -1036,10 +1036,4 @@ void testUpdateValidOnlineCourseConfiguration() throws Exception {
void testEditCourseRemoveExistingIcon() throws Exception {
courseTestService.testEditCourseRemoveExistingIcon();
}
-
- @Test
- @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
- void testGetCourseLearningPathsEnabled_AsInstructor() throws Exception {
- courseTestService.testGetCourseLearningPathsEnabled_AsInstructor();
- }
}
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 6f3b240fe457..f33149e252c9 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -3,7 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import java.time.ZonedDateTime;
-import java.util.*;
+import java.util.HashSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
From 8bbc0c2751f1685ec63154bb19c46d4a0f0e1cdc Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 3 Aug 2023 12:06:50 +0200
Subject: [PATCH 131/215] add sticky popover directive
---
src/main/webapp/app/shared/shared.module.ts | 3 +
.../sticky-popover.directive.ts | 105 ++++++++++++++++++
.../sticky-popover.directive.spec.ts | 60 ++++++++++
3 files changed, 168 insertions(+)
create mode 100644 src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts
create mode 100644 src/test/javascript/spec/directive/sticky-popover.directive.spec.ts
diff --git a/src/main/webapp/app/shared/shared.module.ts b/src/main/webapp/app/shared/shared.module.ts
index 108a76462502..774efdf960a3 100644
--- a/src/main/webapp/app/shared/shared.module.ts
+++ b/src/main/webapp/app/shared/shared.module.ts
@@ -23,6 +23,7 @@ import { AssessmentWarningComponent } from 'app/assessment/assessment-warning/as
import { JhiConnectionWarningComponent } from 'app/shared/connection-warning/connection-warning.component';
import { LoadingIndicatorContainerComponent } from 'app/shared/loading-indicator-container/loading-indicator-container.component';
import { CompetencySelectionComponent } from 'app/shared/competency-selection/competency-selection.component';
+import { StickyPopoverDirective } from 'app/shared/sticky-popover/sticky-popover.directive';
@NgModule({
imports: [ArtemisSharedLibsModule, ArtemisSharedCommonModule, ArtemisSharedPipesModule, RouterModule],
@@ -47,6 +48,7 @@ import { CompetencySelectionComponent } from 'app/shared/competency-selection/co
ItemCountComponent,
ConsistencyCheckComponent,
AssessmentWarningComponent,
+ StickyPopoverDirective,
],
exports: [
ArtemisSharedLibsModule,
@@ -72,6 +74,7 @@ import { CompetencySelectionComponent } from 'app/shared/competency-selection/co
ConsistencyCheckComponent,
AssessmentWarningComponent,
CompetencySelectionComponent,
+ StickyPopoverDirective,
],
})
export class ArtemisSharedModule {}
diff --git a/src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts b/src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts
new file mode 100644
index 000000000000..4950c06f7937
--- /dev/null
+++ b/src/main/webapp/app/shared/sticky-popover/sticky-popover.directive.ts
@@ -0,0 +1,105 @@
+import {
+ ApplicationRef,
+ ChangeDetectorRef,
+ Directive,
+ ElementRef,
+ Inject,
+ Injector,
+ Input,
+ NgZone,
+ OnDestroy,
+ OnInit,
+ Renderer2,
+ TemplateRef,
+ ViewContainerRef,
+} from '@angular/core';
+
+import { DOCUMENT } from '@angular/common';
+import { NgbPopover, NgbPopoverConfig } from '@ng-bootstrap/ng-bootstrap';
+@Directive({
+ selector: '[jhiStickyPopover]',
+})
+export class StickyPopoverDirective extends NgbPopover implements OnInit, OnDestroy {
+ @Input() jhiStickyPopover: TemplateRef;
+
+ popoverTitle: string;
+
+ triggers: string;
+ container: string;
+ ngpPopover: TemplateRef;
+ canClosePopover: boolean;
+
+ toggle(): void {
+ super.toggle();
+ }
+
+ isOpen(): boolean {
+ return super.isOpen();
+ }
+
+ constructor(
+ private _elRef: ElementRef,
+ private _render: Renderer2,
+ injector: Injector,
+ private viewContainerRef: ViewContainerRef,
+ config: NgbPopoverConfig,
+ ngZone: NgZone,
+ private changeRef: ChangeDetectorRef,
+ private applicationRef: ApplicationRef,
+ @Inject(DOCUMENT) _document: any,
+ ) {
+ super(_elRef, _render, injector, viewContainerRef, config, ngZone, _document, changeRef, applicationRef);
+ this.triggers = 'manual';
+ this.popoverTitle = '';
+ this.container = 'body';
+ }
+
+ ngOnInit(): void {
+ super.ngOnInit();
+ this.ngbPopover = this.jhiStickyPopover;
+
+ this._render.listen(this._elRef.nativeElement, 'mouseenter', () => {
+ this.canClosePopover = true;
+ this.open();
+ });
+
+ this._render.listen(this._elRef.nativeElement, 'mouseleave', () => {
+ setTimeout(() => {
+ if (this.canClosePopover) {
+ this.close();
+ }
+ }, 100);
+ });
+
+ this._render.listen(this._elRef.nativeElement, 'click', () => {
+ this.close();
+ });
+ }
+
+ ngOnDestroy(): void {
+ super.ngOnDestroy();
+ }
+
+ open() {
+ super.open();
+ setTimeout(() => {
+ const popover = window.document.querySelector('.popover');
+ this._render.listen(popover, 'mouseover', () => {
+ this.canClosePopover = false;
+ });
+
+ this._render.listen(popover, 'mouseout', () => {
+ this.canClosePopover = true;
+ setTimeout(() => {
+ if (this.canClosePopover) {
+ this.close();
+ }
+ }, 0);
+ });
+ }, 0);
+ }
+
+ close() {
+ super.close();
+ }
+}
diff --git a/src/test/javascript/spec/directive/sticky-popover.directive.spec.ts b/src/test/javascript/spec/directive/sticky-popover.directive.spec.ts
new file mode 100644
index 000000000000..13262c4fcf9a
--- /dev/null
+++ b/src/test/javascript/spec/directive/sticky-popover.directive.spec.ts
@@ -0,0 +1,60 @@
+import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { Component, DebugElement } from '@angular/core';
+import { ArtemisTestModule } from '../test.module';
+import { By } from '@angular/platform-browser';
+import { StickyPopoverDirective } from 'app/shared/sticky-popover/sticky-popover.directive';
+
+@Component({
+ template: '
some content ',
+})
+class StickyPopoverComponent {
+ pattern: string;
+}
+
+describe('StickyPopoverDirective', () => {
+ let fixture: ComponentFixture;
+ let debugDirective: DebugElement;
+ let directive: StickyPopoverDirective;
+ let openStub: jest.SpyInstance;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule],
+ declarations: [StickyPopoverDirective, StickyPopoverComponent],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(StickyPopoverComponent);
+ debugDirective = fixture.debugElement.query(By.directive(StickyPopoverDirective));
+ directive = debugDirective.injector.get(StickyPopoverDirective);
+ openStub = jest.spyOn(directive, 'open');
+ fixture.detectChanges();
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should open on hover', fakeAsync(() => {
+ fixture.whenStable();
+ const div = fixture.debugElement.query(By.css('div'));
+ expect(div).not.toBeNull();
+ div.nativeElement.dispatchEvent(new MouseEvent('mouseenter'));
+ tick(10);
+ expect(openStub).toHaveBeenCalledOnce();
+ expect(directive.isOpen()).toBeTruthy();
+ const span = fixture.debugElement.query(By.css('span'));
+ expect(span).not.toBeNull();
+ }));
+
+ it('should display content on hover', fakeAsync(() => {
+ fixture.whenStable();
+ const div = fixture.debugElement.query(By.css('div'));
+ expect(div).not.toBeNull();
+ div.nativeElement.dispatchEvent(new MouseEvent('mouseenter'));
+ tick(10);
+ const span = fixture.debugElement.query(By.css('span'));
+ expect(span).not.toBeNull();
+ }));
+});
From abf8eac4df5850aeb3fa20a5735316a9e1de5dd5 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 3 Aug 2023 12:13:21 +0200
Subject: [PATCH 132/215] add icon and hint retrieval to lecture unit model
---
.../lecture-unit/lectureUnit.model.ts | 32 +++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts b/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
index 647d462e0cb7..b878b4554020 100644
--- a/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
+++ b/src/main/webapp/app/entities/lecture-unit/lectureUnit.model.ts
@@ -2,6 +2,8 @@ import { BaseEntity } from 'app/shared/model/base-entity';
import dayjs from 'dayjs/esm';
import { Lecture } from 'app/entities/lecture.model';
import { Competency } from 'app/entities/competency.model';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { faDownload, faLink, faQuestion, faScroll, faVideo } from '@fortawesome/free-solid-svg-icons';
// IMPORTANT NOTICE: The following strings have to be consistent with
// the ones defined in LectureUnit.java
@@ -13,6 +15,22 @@ export enum LectureUnitType {
ONLINE = 'online',
}
+export const lectureUnitIcons = {
+ [LectureUnitType.ATTACHMENT]: faDownload,
+ [LectureUnitType.EXERCISE]: faQuestion,
+ [LectureUnitType.TEXT]: faScroll,
+ [LectureUnitType.VIDEO]: faVideo,
+ [LectureUnitType.ONLINE]: faLink,
+};
+
+export const lectureUnitTooltips = {
+ [LectureUnitType.ATTACHMENT]: 'artemisApp.attachmentUnit.tooltip',
+ [LectureUnitType.EXERCISE]: '',
+ [LectureUnitType.TEXT]: 'artemisApp.textUnit.tooltip',
+ [LectureUnitType.VIDEO]: 'artemisApp.videoUnit.tooltip',
+ [LectureUnitType.ONLINE]: 'artemisApp.onlineUnit.tooltip',
+};
+
export abstract class LectureUnit implements BaseEntity {
public id?: number;
public name?: string;
@@ -28,3 +46,17 @@ export abstract class LectureUnit implements BaseEntity {
this.type = type;
}
}
+
+export function getIcon(lectureUnitType: LectureUnitType): IconProp {
+ if (!lectureUnitType) {
+ return faQuestion as IconProp;
+ }
+ return lectureUnitIcons[lectureUnitType] as IconProp;
+}
+
+export function getIconTooltip(lectureUnitType: LectureUnitType) {
+ if (!lectureUnitType) {
+ return '';
+ }
+ return lectureUnitTooltips[lectureUnitType];
+}
From e2ed5b7d22da8d4b75eeda6031506bc5025712e3 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 3 Aug 2023 12:15:41 +0200
Subject: [PATCH 133/215] add translations
---
src/main/webapp/i18n/de/competency.json | 5 +++++
src/main/webapp/i18n/en/competency.json | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/src/main/webapp/i18n/de/competency.json b/src/main/webapp/i18n/de/competency.json
index 62d59e2cd035..6543d4a798a8 100644
--- a/src/main/webapp/i18n/de/competency.json
+++ b/src/main/webapp/i18n/de/competency.json
@@ -169,6 +169,11 @@
"name": "Name",
"login": "Login",
"progress": "Fortschritt"
+ },
+ "progressNav": {
+ "header": "Lernpfad",
+ "refresh": "Aktualisieren",
+ "center": "Zentrieren"
}
}
}
diff --git a/src/main/webapp/i18n/en/competency.json b/src/main/webapp/i18n/en/competency.json
index fbc7c96a12aa..ba6dc5f20b2e 100644
--- a/src/main/webapp/i18n/en/competency.json
+++ b/src/main/webapp/i18n/en/competency.json
@@ -168,6 +168,11 @@
"name": "Name",
"login": "Login",
"progress": "Progress"
+ },
+ "progressNav": {
+ "header": "Learning Path",
+ "refresh": "Refresh",
+ "center": "Center view"
}
}
}
From 3b15b298470bb45de24037e2fcdfa5170c21f66d Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 3 Aug 2023 12:19:14 +0200
Subject: [PATCH 134/215] add missing import
---
.../de/tum/in/www1/artemis/web/rest/LearningPathResource.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 3f59d4131896..a48f9454f5ed 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -23,6 +23,7 @@
import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathHealthDTO;
import de.tum.in.www1.artemis.web.rest.dto.competency.LearningPathPageableSearchDTO;
+import de.tum.in.www1.artemis.web.rest.dto.competency.NgxLearningPathDTO;
import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;
@RestController
From 06254639cd2778512769b1569e0938804d3dd375 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Thu, 3 Aug 2023 12:26:03 +0200
Subject: [PATCH 135/215] add dependencies for ngx graph
---
package-lock.json | 10 ----------
package.json | 4 +++-
2 files changed, 3 insertions(+), 11 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index d9ba139fd300..346d4f062f3f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5941,11 +5941,6 @@
"d3-time-format": "2 - 3"
}
},
- "node_modules/@swimlane/ngx-graph/node_modules/d3-selection": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz",
- "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg=="
- },
"node_modules/@swimlane/ngx-graph/node_modules/d3-shape": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
@@ -20114,11 +20109,6 @@
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
},
- "node_modules/webcola/node_modules/d3-selection": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz",
- "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg=="
- },
"node_modules/webcola/node_modules/d3-shape": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
diff --git a/package.json b/package.json
index 35913127237d..f55e4b165d4e 100644
--- a/package.json
+++ b/package.json
@@ -85,7 +85,9 @@
"@swimlane/ngx-graph": {
"d3-color": "^3.1.0",
"d3-interpolate": "^3.0.1",
- "d3-brush": "^3.0.0"
+ "d3-transition": "^3.0.1",
+ "d3-brush": "^3.0.0",
+ "d3-selection": "^3.0.0"
},
"semver": "7.5.4",
"word-wrap": "1.2.3",
From 0c315a96ac93c630d5671ed6f2e2baeee98ca04c Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 4 Aug 2023 13:02:32 +0200
Subject: [PATCH 136/215] add suggestions by Tobias
---
.../web/rest/LearningPathResource.java | 24 +++++++++----------
.../competency/LearningPathUtilService.java | 3 ---
2 files changed, 12 insertions(+), 15 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
index 8aedc991981f..bfef936c606d 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/LearningPathResource.java
@@ -22,7 +22,7 @@
import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;
@RestController
-@RequestMapping("/api")
+@RequestMapping("api/")
public class LearningPathResource {
private final Logger log = LoggerFactory.getLogger(LearningPathResource.class);
@@ -40,15 +40,15 @@ public LearningPathResource(CourseRepository courseRepository, AuthorizationChec
}
/**
- * PUT /courses/:courseId/learning-paths/enable : Enables and generates learning paths for the course
+ * PUT courses/:courseId/learning-paths/enable : Enables and generates learning paths for the course
*
* @param courseId the id of the course for which the learning paths should be enabled
* @return the ResponseEntity with status 200 (OK)
*/
- @PutMapping("/courses/{courseId}/learning-paths/enable")
+ @PutMapping("courses/{courseId}/learning-paths/enable")
@FeatureToggle(Feature.LearningPaths)
@EnforceAtLeastInstructor
- public ResponseEntity enableLearningPathsForCourse(@PathVariable Long courseId) {
+ public ResponseEntity enableLearningPathsForCourse(@PathVariable long courseId) {
log.debug("REST request to enable learning paths for course with id: {}", courseId);
Course course = courseRepository.findWithEagerCompetenciesByIdElseThrow(courseId);
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
@@ -64,15 +64,15 @@ public ResponseEntity enableLearningPathsForCourse(@PathVariable Long cour
}
/**
- * PUT /courses/:courseId/learning-paths/generate-missing : Generates missing learning paths for the course
+ * PUT courses/:courseId/learning-paths/generate-missing : Generates missing learning paths for the course
*
* @param courseId the id of the course for which the learning paths should be created
* @return the ResponseEntity with status 200 (OK)
*/
- @PutMapping("/courses/{courseId}/learning-paths/generate-missing")
+ @PutMapping("courses/{courseId}/learning-paths/generate-missing")
@FeatureToggle(Feature.LearningPaths)
@EnforceAtLeastInstructor
- public ResponseEntity generateMissingLearningPathsForCourse(@PathVariable Long courseId) {
+ public ResponseEntity generateMissingLearningPathsForCourse(@PathVariable long courseId) {
log.debug("REST request to generate missing learning paths for course with id: {}", courseId);
Course course = courseRepository.findWithEagerCompetenciesByIdElseThrow(courseId);
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
@@ -84,16 +84,16 @@ public ResponseEntity generateMissingLearningPathsForCourse(@PathVariable
}
/**
- * GET /courses/:courseId/learning-paths : Gets all the learning paths of a course. The result is pageable.
+ * GET courses/:courseId/learning-paths : Gets all the learning paths of a course. The result is pageable.
*
* @param courseId the id of the course for which the learning paths should be fetched
* @param search the pageable search containing the page size, page number and query string
* @return the ResponseEntity with status 200 (OK) and with body the desired page, sorted and matching the given query
*/
- @GetMapping("/courses/{courseId}/learning-paths")
+ @GetMapping("courses/{courseId}/learning-paths")
@FeatureToggle(Feature.LearningPaths)
@EnforceAtLeastInstructor
- public ResponseEntity> getLearningPathsOnPage(@PathVariable Long courseId, PageableSearchDTO search) {
+ public ResponseEntity> getLearningPathsOnPage(@PathVariable long courseId, PageableSearchDTO search) {
log.debug("REST request to get learning paths for course with id: {}", courseId);
Course course = courseRepository.findByIdElseThrow(courseId);
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
@@ -105,12 +105,12 @@ public ResponseEntity> getLea
}
/**
- * GET /courses/:courseId/learning-path-health : Gets the health status of learning paths for the course.
+ * GET courses/:courseId/learning-path-health : Gets the health status of learning paths for the course.
*
* @param courseId the id of the course for which the health status should be fetched
* @return the ResponseEntity with status 200 (OK) and with body the health status
*/
- @GetMapping("/courses/{courseId}/learning-path-health")
+ @GetMapping("courses/{courseId}/learning-path-health")
@FeatureToggle(Feature.LearningPaths)
@EnforceAtLeastInstructor
public ResponseEntity getHealthStatusForCourse(@PathVariable long courseId) {
diff --git a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
index ab3c16048d8a..4cf709c0c51f 100644
--- a/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
+++ b/src/test/java/de/tum/in/www1/artemis/competency/LearningPathUtilService.java
@@ -30,9 +30,6 @@ public class LearningPathUtilService {
@Autowired
private CompetencyRepository competencyRepository;
- @Autowired
- UserRepository userRepository;
-
/**
* Enable and generate learning paths for course.
*
From a9e0321d1371ae5b7bacca773ab8a52d899deb3f Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 4 Aug 2023 13:18:58 +0200
Subject: [PATCH 137/215] add tests for error alert in learning path management
---
.../artemis/service/LearningPathService.java | 2 +-
...learning-path-management.component.spec.ts | 38 ++++++++++++++++++-
2 files changed, 37 insertions(+), 3 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index d7c1d572fe3d..9fb73dd2182a 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -167,7 +167,7 @@ private void updateLearningPathProgress(@NotNull LearningPath learningPath) {
* @param course the course for which the health status should be generated
* @return dto containing the health status and additional information (missing learning paths) if needed
*/
- public LearningPathHealthDTO getHealthStatusForCourse(Course course) {
+ public LearningPathHealthDTO getHealthStatusForCourse(@NotNull Course course) {
if (!course.getLearningPathsEnabled()) {
return new LearningPathHealthDTO(LearningPathHealthDTO.HealthStatus.DISABLED);
}
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
index e2ab1499590a..bf89f3f55e26 100644
--- a/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-management.component.spec.ts
@@ -10,15 +10,18 @@ import { ButtonComponent } from 'app/shared/components/button.component';
import { NgbPagination } from '@ng-bootstrap/ng-bootstrap';
import { SortByDirective } from 'app/shared/sort/sort-by.directive';
import { SortDirective } from 'app/shared/sort/sort.directive';
-import { of } from 'rxjs';
+import { of, throwError } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
-import { HttpResponse } from '@angular/common/http';
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
import { HealthStatus, LearningPathHealthDTO } from 'app/entities/competency/learning-path-health.model';
+import { AlertService } from 'app/core/util/alert.service';
describe('LearningPathManagementComponent', () => {
let fixture: ComponentFixture;
let comp: LearningPathManagementComponent;
+ let alertService: AlertService;
+ let alertServiceStub: jest.SpyInstance;
let pagingService: LearningPathPagingService;
let sortService: SortService;
let searchForLearningPathsStub: jest.SpyInstance;
@@ -53,6 +56,8 @@ describe('LearningPathManagementComponent', () => {
.then(() => {
fixture = TestBed.createComponent(LearningPathManagementComponent);
comp = fixture.componentInstance;
+ alertService = TestBed.inject(AlertService);
+ alertServiceStub = jest.spyOn(alertService, 'error');
pagingService = TestBed.inject(LearningPathPagingService);
sortService = TestBed.inject(SortService);
searchForLearningPathsStub = jest.spyOn(pagingService, 'searchForLearningPaths');
@@ -107,6 +112,15 @@ describe('LearningPathManagementComponent', () => {
});
}));
+ it('should alert error if loading health status fails', fakeAsync(() => {
+ const error = { status: 404 };
+ getHealthStatusForCourseStub.mockReturnValue(throwError(() => new HttpErrorResponse(error)));
+ fixture.detectChanges();
+ comp.ngOnInit();
+ expect(getHealthStatusForCourseStub).toHaveBeenCalledWith(courseId);
+ expect(alertServiceStub).toHaveBeenCalledOnce();
+ }));
+
it('should enable learning paths and load data', fakeAsync(() => {
const healthDisabled = new LearningPathHealthDTO(HealthStatus.DISABLED);
getHealthStatusForCourseStub.mockReturnValueOnce(of(new HttpResponse({ body: healthDisabled }))).mockReturnValueOnce(of(new HttpResponse({ body: health })));
@@ -120,6 +134,16 @@ describe('LearningPathManagementComponent', () => {
expect(comp.health).toEqual(health);
}));
+ it('should alert error if enable learning paths fails', fakeAsync(() => {
+ const error = { status: 404 };
+ enableLearningPathsStub.mockReturnValue(throwError(() => new HttpErrorResponse(error)));
+ fixture.detectChanges();
+ comp.ngOnInit();
+ comp.enableLearningPaths();
+ expect(enableLearningPathsStub).toHaveBeenCalledWith(courseId);
+ expect(alertServiceStub).toHaveBeenCalledOnce();
+ }));
+
it('should generate missing learning paths and load data', fakeAsync(() => {
const healthMissing = new LearningPathHealthDTO(HealthStatus.MISSING);
getHealthStatusForCourseStub.mockReturnValueOnce(of(new HttpResponse({ body: healthMissing }))).mockReturnValueOnce(of(new HttpResponse({ body: health })));
@@ -133,6 +157,16 @@ describe('LearningPathManagementComponent', () => {
expect(comp.health).toEqual(health);
}));
+ it('should alert error if generate missing learning paths fails', fakeAsync(() => {
+ const error = { status: 404 };
+ generateMissingLearningPathsForCourseStub.mockReturnValue(throwError(() => new HttpErrorResponse(error)));
+ fixture.detectChanges();
+ comp.ngOnInit();
+ comp.generateMissing();
+ expect(generateMissingLearningPathsForCourseStub).toHaveBeenCalledWith(courseId);
+ expect(alertServiceStub).toHaveBeenCalledOnce();
+ }));
+
it('should set content to paging result on sort', fakeAsync(() => {
expect(comp.listSorting).toBeTrue();
setStateAndCallOnInit(() => {
From e5471551db586bcdb4cce51e773277a5e6159462 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Fri, 4 Aug 2023 18:13:33 +0200
Subject: [PATCH 138/215] add suggenstions from review and testing session
---
.../artemis/service/feature/FeatureToggleService.java | 11 +++++++++--
.../course-management-tab-bar.component.html | 2 +-
.../artemis/lecture/LearningPathIntegrationTest.java | 4 +---
.../course-management-tab-bar.component.spec.ts | 2 ++
4 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/feature/FeatureToggleService.java b/src/main/java/de/tum/in/www1/artemis/service/feature/FeatureToggleService.java
index 544fe7bcac8e..8989f6ce1610 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/feature/FeatureToggleService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/feature/FeatureToggleService.java
@@ -25,10 +25,17 @@ public FeatureToggleService(WebsocketMessagingService websocketMessagingService,
features = hazelcastInstance.getMap("features");
// Features that are neither enabled nor disabled should be enabled by default
- // This ensures that all features are enabled once the system starts up
+ // This ensures that all features (except learning paths) are enabled once the system starts up
for (Feature feature : Feature.values()) {
if (!features.containsKey(feature)) {
- features.put(feature, true);
+ if (feature == Feature.LearningPaths) {
+ // disable learning paths per default
+ // TODO: remove this once learning paths are deliverable
+ features.put(feature, false);
+ }
+ else {
+ features.put(feature, true);
+ }
}
}
}
diff --git a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html
index 632188f51f4d..7bd8437a1a16 100644
--- a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html
+++ b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html
@@ -44,7 +44,7 @@
gradingCriteria = exerciseUtilService.addGradingInstructionsToExercise(textExercise);
gradingCriterionRepository.saveAll(gradingCriteria);
participationUtilService.addAssessmentWithFeedbackWithGradingInstructionsForExercise(textExercise, STUDENT_OF_COURSE);
diff --git a/src/test/javascript/spec/component/course/course-management-tab-bar.component.spec.ts b/src/test/javascript/spec/component/course/course-management-tab-bar.component.spec.ts
index 23b57fb1f705..dd17e48b8f8d 100644
--- a/src/test/javascript/spec/component/course/course-management-tab-bar.component.spec.ts
+++ b/src/test/javascript/spec/component/course/course-management-tab-bar.component.spec.ts
@@ -15,6 +15,7 @@ import { CourseExamArchiveButtonComponent } from 'app/shared/components/course-e
import { DeleteButtonDirective } from 'app/shared/delete-dialog/delete-button.directive';
import { HasAnyAuthorityDirective } from 'app/shared/auth/has-any-authority.directive';
import { FeatureToggleLinkDirective } from 'app/shared/feature-toggle/feature-toggle-link.directive';
+import { FeatureToggleHideDirective } from 'app/shared/feature-toggle/feature-toggle-hide.directive';
describe('Course Management Tab Bar Component', () => {
let component: CourseManagementTabBarComponent;
@@ -41,6 +42,7 @@ describe('Course Management Tab Bar Component', () => {
MockDirective(DeleteButtonDirective),
MockDirective(HasAnyAuthorityDirective),
MockDirective(FeatureToggleLinkDirective),
+ MockDirective(FeatureToggleHideDirective),
],
imports: [HttpClientTestingModule, RouterModule, ArtemisTestModule],
providers: [
From 03c8d06754706e25ec4b3a67ef4db6d511eebf80 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Sat, 5 Aug 2023 11:08:22 +0200
Subject: [PATCH 139/215] adjust test cases
---
.../artemis/lecture/LearningPathIntegrationTest.java | 10 ++++++++++
.../www1/artemis/service/FeatureToggleServiceTest.java | 8 ++++----
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index d7aba779da09..58df4717d4d7 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -34,6 +34,8 @@
import de.tum.in.www1.artemis.repository.*;
import de.tum.in.www1.artemis.service.CompetencyProgressService;
import de.tum.in.www1.artemis.service.LectureUnitService;
+import de.tum.in.www1.artemis.service.feature.Feature;
+import de.tum.in.www1.artemis.service.feature.FeatureToggleService;
import de.tum.in.www1.artemis.user.UserUtilService;
import de.tum.in.www1.artemis.util.PageableSearchUtilService;
import de.tum.in.www1.artemis.web.rest.dto.competency.*;
@@ -90,6 +92,9 @@ class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbuck
@Autowired
private LearningPathUtilService learningPathUtilService;
+ @Autowired
+ private FeatureToggleService featureToggleService;
+
private Course course;
private Competency[] competencies;
@@ -108,6 +113,11 @@ class LearningPathIntegrationTest extends AbstractSpringIntegrationBambooBitbuck
private User studentNotInCourse;
+ @BeforeEach
+ void enableLearningPathsFeatureToggle() {
+ featureToggleService.enableFeature(Feature.LearningPaths);
+ }
+
@BeforeEach
void setupTestScenario() throws Exception {
userUtilService.addUsers(TEST_PREFIX, NUMBER_OF_STUDENTS, 1, 1, 1);
diff --git a/src/test/java/de/tum/in/www1/artemis/service/FeatureToggleServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/FeatureToggleServiceTest.java
index 17072ad5d1a1..6d9abb76a563 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/FeatureToggleServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/FeatureToggleServiceTest.java
@@ -59,22 +59,22 @@ void testEnableDisableFeature() {
@Test
void testShouldNotEnableTwice() {
- assertThat(featureToggleService.enabledFeatures()).hasSameSizeAs(Feature.values());
+ assertThat(featureToggleService.enabledFeatures().size()).isEqualTo(Feature.values().length - 1);
featureToggleService.enableFeature(Feature.ProgrammingExercises);
// Feature should not be added multiple times
- assertThat(featureToggleService.enabledFeatures()).hasSameSizeAs(Feature.values());
+ assertThat(featureToggleService.enabledFeatures().size()).isEqualTo(Feature.values().length - 1);
}
@Test
void testShouldNotDisableTwice() {
featureToggleService.disableFeature(Feature.ProgrammingExercises);
- assertThat(featureToggleService.disabledFeatures()).hasSize(1);
+ assertThat(featureToggleService.disabledFeatures()).hasSize(2);
featureToggleService.disableFeature(Feature.ProgrammingExercises);
// Feature should not be added multiple times
- assertThat(featureToggleService.disabledFeatures()).hasSize(1);
+ assertThat(featureToggleService.disabledFeatures()).hasSize(2);
// Reset
featureToggleService.enableFeature(Feature.ProgrammingExercises);
From 0848fc3811b3332a4fefd0a6eb6542146e896b98 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Sat, 5 Aug 2023 11:28:41 +0200
Subject: [PATCH 140/215] Update learning-path-management.component.html
---
.../learning-path-management.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
index 9447ba7680ea..c6e89d1e7a8c 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
@@ -4,7 +4,7 @@
-
+
Learning Path Management
Disabled
From 1a8f586d1b0b27b39b25c3aa358f1712e38ba1ba Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Sat, 5 Aug 2023 12:21:45 +0200
Subject: [PATCH 141/215] adjust feature toggle for testing
---
.../www1/artemis/lecture/LearningPathIntegrationTest.java | 6 ++++++
.../in/www1/artemis/service/FeatureToggleServiceTest.java | 1 +
2 files changed, 7 insertions(+)
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 58df4717d4d7..35555200b01a 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -9,6 +9,7 @@
import java.util.function.Function;
import java.util.stream.Stream;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -118,6 +119,11 @@ void enableLearningPathsFeatureToggle() {
featureToggleService.enableFeature(Feature.LearningPaths);
}
+ @AfterEach
+ void disableLearningPathsFeatureToggle() {
+ featureToggleService.disableFeature(Feature.LearningPaths);
+ }
+
@BeforeEach
void setupTestScenario() throws Exception {
userUtilService.addUsers(TEST_PREFIX, NUMBER_OF_STUDENTS, 1, 1, 1);
diff --git a/src/test/java/de/tum/in/www1/artemis/service/FeatureToggleServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/FeatureToggleServiceTest.java
index 6d9abb76a563..758b8fc2e204 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/FeatureToggleServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/FeatureToggleServiceTest.java
@@ -23,6 +23,7 @@ void checkReset() {
// Verify that the test has reset the state
// Must be extended if additional features are added
assertThat(featureToggleService.isFeatureEnabled(Feature.ProgrammingExercises)).isTrue();
+ assertThat(featureToggleService.isFeatureEnabled(Feature.LearningPaths)).isFalse();
}
@Test
From 8107b727aaa69c113070db41c98ef8ecd475dc6e Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Mon, 7 Aug 2023 12:32:28 +0200
Subject: [PATCH 142/215] improve competency health check and warnings
---
.../CompetencyRelationRepository.java | 6 ++
.../repository/CompetencyRepository.java | 3 +
.../artemis/service/LearningPathService.java | 42 ++++++++++--
.../dto/competency/LearningPathHealthDTO.java | 10 +--
...-path-health-status-warning.component.html | 9 +++
...ng-path-health-status-warning.component.ts | 16 +++++
.../learning-path-management.component.html | 32 +++++----
.../learning-path-management.component.ts | 15 +++-
.../learning-paths/learning-paths.module.ts | 2 +
.../competency/learning-path-health.model.ts | 51 +++++++++++++-
src/main/webapp/i18n/de/competency.json | 12 ++++
src/main/webapp/i18n/en/competency.json | 12 ++++
.../service/LearningPathServiceTest.java | 32 ++++++++-
...th-health-status-warning.component.spec.ts | 68 +++++++++++++++++++
14 files changed, 280 insertions(+), 30 deletions(-)
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-health-status-warning.component.html
create mode 100644 src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-health-status-warning.component.ts
create mode 100644 src/test/javascript/spec/component/learning-paths/management/learning-path-health-status-warning.component.spec.ts
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/CompetencyRelationRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/CompetencyRelationRepository.java
index bea1a8864da1..57a60304e674 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/CompetencyRelationRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/CompetencyRelationRepository.java
@@ -33,4 +33,10 @@ public interface CompetencyRelationRepository extends JpaRepository
findAllByCourseId(@Param("courseId") Long courseId);
+ @Query("""
+ SELECT count(cr)
+ FROM CompetencyRelation cr
+ WHERE cr.headCompetency.course.id = :courseId OR cr.tailCompetency.course.id = :courseId
+ """)
+ long countByCourseId(@Param("courseId") long courseId);
}
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/CompetencyRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/CompetencyRepository.java
index 18be4967ccf2..cfffc608f4fd 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/CompetencyRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/CompetencyRepository.java
@@ -11,6 +11,7 @@
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
+import de.tum.in.www1.artemis.domain.Course;
import de.tum.in.www1.artemis.domain.competency.Competency;
import de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException;
@@ -180,4 +181,6 @@ default Competency findByIdWithExercisesAndLectureUnitsElseThrow(Long competency
default Competency findByIdWithExercisesElseThrow(Long competencyId) {
return findByIdWithExercises(competencyId).orElseThrow(() -> new EntityNotFoundException("Competency", competencyId));
}
+
+ long countByCourse(Course course);
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index 64f2bfff35cb..f5972e58d9ba 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -49,14 +49,17 @@ public class LearningPathService {
private final CourseRepository courseRepository;
+ private final CompetencyRepository competencyRepository;
+
private final CompetencyRelationRepository competencyRelationRepository;
public LearningPathService(UserRepository userRepository, LearningPathRepository learningPathRepository, CompetencyProgressRepository competencyProgressRepository,
- CourseRepository courseRepository, CompetencyRelationRepository competencyRelationRepository) {
+ CourseRepository courseRepository, CompetencyRepository competencyRepository, CompetencyRelationRepository competencyRelationRepository) {
this.userRepository = userRepository;
this.learningPathRepository = learningPathRepository;
this.competencyProgressRepository = competencyProgressRepository;
this.courseRepository = courseRepository;
+ this.competencyRepository = competencyRepository;
this.competencyRelationRepository = competencyRelationRepository;
}
@@ -178,17 +181,46 @@ private void updateLearningPathProgress(@NotNull LearningPath learningPath) {
*/
public LearningPathHealthDTO getHealthStatusForCourse(@NotNull Course course) {
if (!course.getLearningPathsEnabled()) {
- return new LearningPathHealthDTO(LearningPathHealthDTO.HealthStatus.DISABLED);
+ return new LearningPathHealthDTO(Set.of(LearningPathHealthDTO.HealthStatus.DISABLED));
+ }
+
+ Set status = new HashSet<>();
+ Long numberOfMissingLearningPaths = checkMissingLearningPaths(course, status);
+ checkNoCompetencies(course, status);
+ checkNoRelations(course, status);
+
+ // if no issues where found, add OK status
+ if (status.isEmpty()) {
+ status.add(LearningPathHealthDTO.HealthStatus.OK);
}
+ return new LearningPathHealthDTO(status, numberOfMissingLearningPaths);
+ }
+
+ private Long checkMissingLearningPaths(@NotNull Course course, @NotNull Set status) {
long numberOfStudents = userRepository.countUserInGroup(course.getStudentGroupName());
long numberOfLearningPaths = learningPathRepository.countLearningPathsOfEnrolledStudentsInCourse(course.getId());
+ Long numberOfMissingLearningPaths = numberOfStudents - numberOfLearningPaths;
- if (numberOfStudents == numberOfLearningPaths) {
- return new LearningPathHealthDTO(LearningPathHealthDTO.HealthStatus.OK);
+ if (numberOfMissingLearningPaths != 0) {
+ status.add(LearningPathHealthDTO.HealthStatus.MISSING);
}
else {
- return new LearningPathHealthDTO(LearningPathHealthDTO.HealthStatus.MISSING, numberOfStudents - numberOfLearningPaths);
+ numberOfMissingLearningPaths = null;
+ }
+
+ return numberOfMissingLearningPaths;
+ }
+
+ private void checkNoCompetencies(@NotNull Course course, @NotNull Set status) {
+ if (competencyRepository.countByCourse(course) == 0) {
+ status.add(LearningPathHealthDTO.HealthStatus.NO_COMPETENCIES);
+ }
+ }
+
+ private void checkNoRelations(@NotNull Course course, @NotNull Set status) {
+ if (competencyRelationRepository.countByCourseId(course.getId()) == 0) {
+ status.add(LearningPathHealthDTO.HealthStatus.NO_RELATIONS);
}
}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathHealthDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathHealthDTO.java
index f261bf7cb26a..cea77a15d594 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathHealthDTO.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/competency/LearningPathHealthDTO.java
@@ -1,17 +1,19 @@
package de.tum.in.www1.artemis.web.rest.dto.competency;
-import javax.validation.constraints.NotNull;
+import java.util.Set;
+
+import javax.validation.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
-public record LearningPathHealthDTO(@NotNull HealthStatus status, Long missingLearningPaths) {
+public record LearningPathHealthDTO(@NotEmpty Set status, Long missingLearningPaths) {
- public LearningPathHealthDTO(HealthStatus status) {
+ public LearningPathHealthDTO(Set status) {
this(status, null);
}
public enum HealthStatus {
- OK, DISABLED, MISSING
+ OK, DISABLED, MISSING, NO_COMPETENCIES, NO_RELATIONS
}
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-health-status-warning.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-health-status-warning.component.html
new file mode 100644
index 000000000000..5b2829f66531
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-health-status-warning.component.html
@@ -0,0 +1,9 @@
+
+
+
{{ getWarningTitle(status) | artemisTranslate }}
+
{{ getWarningBody(status) | artemisTranslate }}
+
+ {{ getWarningAction(status) | artemisTranslate }}
+
+
+
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-health-status-warning.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-health-status-warning.component.ts
new file mode 100644
index 000000000000..fe9cc0d7adcf
--- /dev/null
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-health-status-warning.component.ts
@@ -0,0 +1,16 @@
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { HealthStatus, getWarningAction, getWarningBody, getWarningHint, getWarningTitle } from 'app/entities/competency/learning-path-health.model';
+
+@Component({
+ selector: 'jhi-learning-path-health-status-warning',
+ templateUrl: './learning-path-health-status-warning.component.html',
+})
+export class LearningPathHealthStatusWarningComponent {
+ @Input() status: HealthStatus;
+ @Output() onButtonClicked: EventEmitter = new EventEmitter();
+
+ protected readonly getWarningTitle = getWarningTitle;
+ protected readonly getWarningBody = getWarningBody;
+ protected readonly getWarningHint = getWarningHint;
+ protected readonly getWarningAction = getWarningAction;
+}
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
index c6e89d1e7a8c..5542ff44ebf6 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.html
@@ -6,7 +6,7 @@
Learning Path Management
-
+
-
-
-
{{ 'artemisApp.learningPath.manageLearningPaths.health.missing.title' | artemisTranslate }}
-
{{ 'artemisApp.learningPath.manageLearningPaths.health.missing.body' | artemisTranslate }}
-
- {{
- 'artemisApp.learningPath.manageLearningPaths.health.missing.action' | artemisTranslate
- }}
-
-
-
-
+
+
+
+
+
+
Search for Learning Path:
diff --git a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
index f8cafc4a67f0..ca4f758813f4 100644
--- a/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-path-management/learning-path-management.component.ts
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
-import { ActivatedRoute } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
import { Subject } from 'rxjs';
import { LearningPathService } from 'app/course/learning-paths/learning-path.service';
import { debounceTime, finalize, switchMap, tap } from 'rxjs/operators';
@@ -11,7 +11,7 @@ import { LearningPathPagingService } from 'app/course/learning-paths/learning-pa
import { SortService } from 'app/shared/service/sort.service';
import { LearningPathPageableSearchDTO } from 'app/entities/competency/learning-path.model';
import { faSort, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
-import { HealthStatus, LearningPathHealthDTO } from 'app/entities/competency/learning-path-health.model';
+import { HealthStatus, LearningPathHealthDTO, getWarningAction, getWarningBody, getWarningHint, getWarningTitle } from 'app/entities/competency/learning-path-health.model';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { LearningPathProgressModalComponent } from 'app/course/learning-paths/learning-path-management/learning-path-progress-modal.component';
@@ -53,6 +53,7 @@ export class LearningPathManagementComponent implements OnInit {
constructor(
private activatedRoute: ActivatedRoute,
+ private router: Router,
private learningPathService: LearningPathService,
private alertService: AlertService,
private pagingService: LearningPathPagingService,
@@ -134,7 +135,7 @@ export class LearningPathManagementComponent implements OnInit {
.subscribe({
next: (res) => {
this.health = res.body!;
- if (this.health.status !== HealthStatus.DISABLED) {
+ if (!this.health.status?.includes(HealthStatus.DISABLED)) {
this.performSearch(this.sort, 0);
this.performSearch(this.search, 300);
}
@@ -163,6 +164,10 @@ export class LearningPathManagementComponent implements OnInit {
});
}
+ routeToCompetencyManagement() {
+ this.router.navigate(['../competency-management'], { relativeTo: this.activatedRoute });
+ }
+
/**
* Method to perform the search based on a search subject
*
@@ -213,4 +218,8 @@ export class LearningPathManagementComponent implements OnInit {
}
protected readonly HealthStatus = HealthStatus;
+ protected readonly getWarningTitle = getWarningTitle;
+ protected readonly getWarningBody = getWarningBody;
+ protected readonly getWarningAction = getWarningAction;
+ protected readonly getWarningHint = getWarningHint;
}
diff --git a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
index 8c37905813e9..4f5ba28c7ec7 100644
--- a/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
+++ b/src/main/webapp/app/course/learning-paths/learning-paths.module.ts
@@ -13,11 +13,13 @@ import { LearningPathGraphNodeComponent } from 'app/course/learning-paths/learni
import { CompetencyNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/competency-node-details.component';
import { LectureUnitNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/lecture-unit-node-details.component';
import { ExerciseNodeDetailsComponent } from 'app/course/learning-paths/learning-path-graph/node-details/exercise-node-details.component';
+import { LearningPathHealthStatusWarningComponent } from 'app/course/learning-paths/learning-path-management/learning-path-health-status-warning.component';
@NgModule({
imports: [ArtemisSharedModule, FormsModule, ReactiveFormsModule, ArtemisSharedComponentModule, NgxGraphModule, ArtemisLectureUnitsModule, ArtemisCompetenciesModule],
declarations: [
LearningPathManagementComponent,
+ LearningPathHealthStatusWarningComponent,
LearningPathProgressModalComponent,
LearningPathProgressNavComponent,
LearningPathGraphComponent,
diff --git a/src/main/webapp/app/entities/competency/learning-path-health.model.ts b/src/main/webapp/app/entities/competency/learning-path-health.model.ts
index bf3a9794d178..1cbcb13ba367 100644
--- a/src/main/webapp/app/entities/competency/learning-path-health.model.ts
+++ b/src/main/webapp/app/entities/competency/learning-path-health.model.ts
@@ -1,8 +1,8 @@
export class LearningPathHealthDTO {
- public status?: HealthStatus;
+ public status?: HealthStatus[];
public missingLearningPaths?: number;
- constructor(status: HealthStatus) {
+ constructor(status: HealthStatus[]) {
this.status = status;
}
}
@@ -11,4 +11,51 @@ export enum HealthStatus {
OK = 'OK',
DISABLED = 'DISABLED',
MISSING = 'MISSING',
+ NO_COMPETENCIES = 'NO_COMPETENCIES',
+ NO_RELATIONS = 'NO_RELATIONS',
+}
+
+function getWarningTranslation(status: HealthStatus, element: string) {
+ if (!status || status === HealthStatus.OK || status === HealthStatus.DISABLED) {
+ return '';
+ }
+
+ const translation = {
+ [HealthStatus.MISSING]: 'missing',
+ [HealthStatus.NO_COMPETENCIES]: 'noCompetencies',
+ [HealthStatus.NO_RELATIONS]: 'noRelations',
+ };
+ return `artemisApp.learningPath.manageLearningPaths.health.${translation[status]}.${element}`;
+}
+
+export function getWarningTitle(status: HealthStatus) {
+ if (!status || status === HealthStatus.OK || status === HealthStatus.DISABLED) {
+ return '';
+ }
+
+ return getWarningTranslation(status, 'title');
+}
+
+export function getWarningBody(status: HealthStatus) {
+ if (!status || status === HealthStatus.OK || status === HealthStatus.DISABLED) {
+ return '';
+ }
+
+ return getWarningTranslation(status, 'body');
+}
+
+export function getWarningAction(status: HealthStatus) {
+ if (!status || status === HealthStatus.OK || status === HealthStatus.DISABLED) {
+ return '';
+ }
+
+ return getWarningTranslation(status, 'action');
+}
+
+export function getWarningHint(status: HealthStatus) {
+ if (!status || status === HealthStatus.OK || status === HealthStatus.DISABLED) {
+ return '';
+ }
+
+ return getWarningTranslation(status, 'hint');
}
diff --git a/src/main/webapp/i18n/de/competency.json b/src/main/webapp/i18n/de/competency.json
index 6543d4a798a8..fdbad726d235 100644
--- a/src/main/webapp/i18n/de/competency.json
+++ b/src/main/webapp/i18n/de/competency.json
@@ -159,6 +159,18 @@
"body": "Für einige Studierende wurde noch kein Lernpfad erstellt. Dies ist nicht kritisch. Ihre Lernpfade werden generiert, wenn sie ihren Lernpfad das erste mal anfragen.",
"action": "Erstellen",
"hint": "Erstellen der fehlenden Lernpfade"
+ },
+ "noCompetencies": {
+ "title": "Keine Kompetenzen",
+ "body": "Es wurden noch keine Kompetenzen erstellt. Lernpfade setzen sich aus den Kompetenzen zusammen, die Studierende erreichen müssen. Gehe zum Kompetenzmanagement um neue Kompetenzen zu erstellen oder bestehende zu importieren.",
+ "action": "Kompetenzmanagement",
+ "hint": "Gehe zum Kompetenzmanagement"
+ },
+ "noRelations": {
+ "title": "Keine Beziehungen",
+ "body": "Es wurden noch keine Beziehungen zwischen Kompetenzen konfiguriert. Lernpfade nutzen die Informationen über die Struktur der Lerninhalte um zuverlässig qualitiv hochwertige Empfehlungen für Studierende zu generieren. Gehe zum Kompetenzmanagement um die Beziehungen zwischen Kompetenzen zu konfigurieren.",
+ "action": "Kompetenzmanagement",
+ "hint": "Gehe zum Kompetenzmanagement"
}
},
"isDisabled": "Lernpfade sind für diesen Kurs nicht aktiviert.",
diff --git a/src/main/webapp/i18n/en/competency.json b/src/main/webapp/i18n/en/competency.json
index ba6dc5f20b2e..8a1d3eb4a393 100644
--- a/src/main/webapp/i18n/en/competency.json
+++ b/src/main/webapp/i18n/en/competency.json
@@ -158,6 +158,18 @@
"body": "Some students have not generated their learning paths yet. This is not critical. Their learning paths will be created once they request their learning path for the first time.",
"action": "Generate",
"hint": "Generate missing Learning Paths"
+ },
+ "noCompetencies": {
+ "title": "No Competencies",
+ "body": "You have not created competencies yet. Learning paths are composed of the competencies students have to fulfill. Go to the competency management tab to create new competencies or import existing ones.",
+ "action": "Competency Management",
+ "hint": "Go to competency management"
+ },
+ "noRelations": {
+ "title": "No Relations",
+ "body": "You have not configured relations between competencies. Learning paths use this information about the structure of the learning materials to reliably provide high quality recommendations to students. Go to the competency management tab to configure competency relations.",
+ "action": "Competency Management",
+ "hint": "Go to competency management"
}
},
"isDisabled": "Learning Paths are currently disabled for this course.",
diff --git a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
index 2b2e865fc42e..87ace5375578 100644
--- a/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/service/LearningPathServiceTest.java
@@ -88,24 +88,50 @@ void setup() {
@Test
void testHealthStatusDisabled() {
var healthStatus = learningPathService.getHealthStatusForCourse(course);
- assertThat(healthStatus.status()).isEqualTo(LearningPathHealthDTO.HealthStatus.DISABLED);
+ assertThat(healthStatus.status()).containsExactly(LearningPathHealthDTO.HealthStatus.DISABLED);
+ assertThat(healthStatus.missingLearningPaths()).isNull();
}
@Test
void testHealthStatusOK() {
+ final var competency1 = competencyUtilService.createCompetency(course);
+ final var competency2 = competencyUtilService.createCompetency(course);
+ competencyUtilService.addRelation(competency1, CompetencyRelation.RelationType.MATCHES, competency2);
course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
var healthStatus = learningPathService.getHealthStatusForCourse(course);
- assertThat(healthStatus.status()).isEqualTo(LearningPathHealthDTO.HealthStatus.OK);
+ assertThat(healthStatus.status()).containsExactly(LearningPathHealthDTO.HealthStatus.OK);
+ assertThat(healthStatus.missingLearningPaths()).isNull();
}
@Test
void testHealthStatusMissing() {
+ final var competency1 = competencyUtilService.createCompetency(course);
+ final var competency2 = competencyUtilService.createCompetency(course);
+ competencyUtilService.addRelation(competency1, CompetencyRelation.RelationType.MATCHES, competency2);
course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
userUtilService.addStudent(TEST_PREFIX + "tumuser", TEST_PREFIX + "student1337");
var healthStatus = learningPathService.getHealthStatusForCourse(course);
- assertThat(healthStatus.status()).isEqualTo(LearningPathHealthDTO.HealthStatus.MISSING);
+ assertThat(healthStatus.status()).containsExactly(LearningPathHealthDTO.HealthStatus.MISSING);
assertThat(healthStatus.missingLearningPaths()).isEqualTo(1);
}
+
+ @Test
+ void testHealthStatusNoCompetencies() {
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ var healthStatus = learningPathService.getHealthStatusForCourse(course);
+ assertThat(healthStatus.status()).containsExactlyInAnyOrder(LearningPathHealthDTO.HealthStatus.NO_COMPETENCIES, LearningPathHealthDTO.HealthStatus.NO_RELATIONS);
+ assertThat(healthStatus.missingLearningPaths()).isNull();
+ }
+
+ @Test
+ void testHealthStatusNoRelations() {
+ competencyUtilService.createCompetency(course);
+ competencyUtilService.createCompetency(course);
+ course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course);
+ var healthStatus = learningPathService.getHealthStatusForCourse(course);
+ assertThat(healthStatus.status()).containsExactly(LearningPathHealthDTO.HealthStatus.NO_RELATIONS);
+ assertThat(healthStatus.missingLearningPaths()).isNull();
+ }
}
@Nested
diff --git a/src/test/javascript/spec/component/learning-paths/management/learning-path-health-status-warning.component.spec.ts b/src/test/javascript/spec/component/learning-paths/management/learning-path-health-status-warning.component.spec.ts
new file mode 100644
index 000000000000..f5396e32157d
--- /dev/null
+++ b/src/test/javascript/spec/component/learning-paths/management/learning-path-health-status-warning.component.spec.ts
@@ -0,0 +1,68 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ArtemisTestModule } from '../../../test.module';
+import { MockPipe } from 'ng-mocks';
+import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
+import { NgbTooltipMocksModule } from '../../../helpers/mocks/directive/ngbTooltipMocks.module';
+import { LearningPathHealthStatusWarningComponent } from 'app/course/learning-paths/learning-path-management/learning-path-health-status-warning.component';
+import { HealthStatus } from 'app/entities/competency/learning-path-health.model';
+import { MockHasAnyAuthorityDirective } from '../../../helpers/mocks/directive/mock-has-any-authority.directive';
+
+describe('LearningPathHealthStatusWarningComponent', () => {
+ let fixture: ComponentFixture
;
+ let comp: LearningPathHealthStatusWarningComponent;
+ let getWarningTitleStub: jest.SpyInstance;
+ let getWarningBodyStub: jest.SpyInstance;
+ let getWarningActionStub: jest.SpyInstance;
+ let getWarningHintStub: jest.SpyInstance;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ArtemisTestModule, NgbTooltipMocksModule],
+ declarations: [LearningPathHealthStatusWarningComponent, MockPipe(ArtemisTranslatePipe), MockHasAnyAuthorityDirective],
+ providers: [],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(LearningPathHealthStatusWarningComponent);
+ comp = fixture.componentInstance;
+ getWarningTitleStub = jest.spyOn(comp, 'getWarningTitle');
+ getWarningBodyStub = jest.spyOn(comp, 'getWarningBody');
+ getWarningActionStub = jest.spyOn(comp, 'getWarningAction');
+ getWarningHintStub = jest.spyOn(comp, 'getWarningHint');
+ fixture.detectChanges();
+ });
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should create', () => {
+ expect(fixture).toBeTruthy();
+ expect(comp).toBeTruthy();
+ });
+
+ it.each([HealthStatus.MISSING, HealthStatus.NO_COMPETENCIES, HealthStatus.NO_RELATIONS])('should load title', (status: HealthStatus) => {
+ comp.status = status;
+ fixture.detectChanges();
+ expect(getWarningTitleStub).toHaveBeenCalledWith(status);
+ });
+
+ it.each([HealthStatus.MISSING, HealthStatus.NO_COMPETENCIES, HealthStatus.NO_RELATIONS])('should load body', (status: HealthStatus) => {
+ comp.status = status;
+ fixture.detectChanges();
+ expect(getWarningBodyStub).toHaveBeenCalledWith(status);
+ });
+
+ it.each([HealthStatus.MISSING, HealthStatus.NO_COMPETENCIES, HealthStatus.NO_RELATIONS])('should load action', (status: HealthStatus) => {
+ comp.status = status;
+ fixture.detectChanges();
+ expect(getWarningActionStub).toHaveBeenCalledWith(status);
+ });
+
+ it.each([HealthStatus.MISSING, HealthStatus.NO_COMPETENCIES, HealthStatus.NO_RELATIONS])('should load hint', (status: HealthStatus) => {
+ comp.status = status;
+ fixture.detectChanges();
+ expect(getWarningHintStub).toHaveBeenCalledWith(status);
+ });
+});
From ec1fd79e4e00c50c0694890d6534ddbaf7876e95 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Tue, 8 Aug 2023 11:55:42 +0200
Subject: [PATCH 143/215] Adjust testGenerateLearningPathOnEnrollment for
changed return type of enrollement endpoint
---
.../tum/in/www1/artemis/repository/UserRepository.java | 3 +++
.../artemis/lecture/LearningPathIntegrationTest.java | 9 +++++----
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java
index 4f9cd9a77b77..6fc5ed000500 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java
@@ -97,6 +97,9 @@ public interface UserRepository extends JpaRepository, JpaSpecificat
@EntityGraph(type = LOAD, attributePaths = { "groups", "authorities", "guidedTourSettings" })
Optional findOneWithGroupsAuthoritiesAndGuidedTourSettingsByLogin(String login);
+ @EntityGraph(type = LOAD, attributePaths = { "learningPaths" })
+ Optional findOneWithLearningPathsByLogin(String login);
+
@EntityGraph(type = LOAD, attributePaths = { "learningPaths" })
Optional findWithLearningPathsById(long userId);
diff --git a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
index 35555200b01a..e478eb07629c 100644
--- a/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/lecture/LearningPathIntegrationTest.java
@@ -276,10 +276,11 @@ void testGenerateLearningPathOnEnrollment() throws Exception {
this.setupEnrollmentRequestMocks();
- final var updatedUser = request.postWithResponseBody("/api/courses/" + course.getId() + "/enroll", null, User.class, HttpStatus.OK);
- final var updatedUserWithLearningPaths = userRepository.findWithLearningPathsByIdElseThrow(updatedUser.getId());
- assertThat(updatedUserWithLearningPaths.getLearningPaths()).isNotNull();
- assertThat(updatedUserWithLearningPaths.getLearningPaths().size()).as("should create LearningPath for student").isEqualTo(1);
+ request.postWithResponseBody("/api/courses/" + course.getId() + "/enroll", null, Set.class, HttpStatus.OK);
+ final var user = userRepository.findOneWithLearningPathsByLogin(TEST_PREFIX + "student1337").orElseThrow();
+
+ assertThat(user.getLearningPaths()).isNotNull();
+ assertThat(user.getLearningPaths().size()).as("should create LearningPath for student").isEqualTo(1);
}
private void setupEnrollmentRequestMocks() throws JsonProcessingException, URISyntaxException {
From 592adb2c716feb862f7dcacbc6a1c060a220aaf1 Mon Sep 17 00:00:00 2001
From: Maximilian Anzinger
<44003963+MaximilianAnzinger@users.noreply.github.com>
Date: Tue, 8 Aug 2023 12:00:24 +0200
Subject: [PATCH 144/215] remove clusters
---
.../artemis/service/LearningPathService.java | 36 ++++++++-----------
.../web/rest/LearningPathResource.java | 8 ++---
.../dto/competency/NgxLearningPathDTO.java | 6 +---
.../learning-path-graph.component.html | 1 -
.../learning-path-graph.component.ts | 2 +-
.../learning-paths/learning-path.service.ts | 7 ++--
.../competency/learning-path.model.ts | 6 ----
.../lecture/LearningPathIntegrationTest.java | 16 ++++-----
.../service/LearningPathServiceTest.java | 29 +++++----------
.../learning-path-graph.component.spec.ts | 8 ++---
.../service/learning-path.service.spec.ts | 4 +--
11 files changed, 44 insertions(+), 79 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
index 64f2bfff35cb..4dc3b0fd2361 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/LearningPathService.java
@@ -193,23 +193,22 @@ public LearningPathHealthDTO getHealthStatusForCourse(@NotNull Course course) {
}
/**
- * Generates Ngx representation of the learning path.
+ * Generates Ngx graph representation of the learning path graph.
*
* @param learningPath the learning path for which the Ngx representation should be created
- * @return Ngx representation of the learning path
+ * @return Ngx graph representation of the learning path
* @see NgxLearningPathDTO
*/
- public NgxLearningPathDTO generateNgxRepresentation(@NotNull LearningPath learningPath) {
+ public NgxLearningPathDTO generateNgxGraphRepresentation(@NotNull LearningPath learningPath) {
Set nodes = new HashSet<>();
Set edges = new HashSet<>();
- Set clusters = new HashSet<>();
- learningPath.getCompetencies().forEach(competency -> generateNgxRepresentationForCompetency(learningPath, competency, nodes, edges, clusters));
- generateNgxRepresentationForRelations(learningPath, nodes, edges);
- return new NgxLearningPathDTO(nodes, edges, clusters);
+ learningPath.getCompetencies().forEach(competency -> generateNgxGraphRepresentationForCompetency(learningPath, competency, nodes, edges));
+ generateNgxGraphRepresentationForRelations(learningPath, nodes, edges);
+ return new NgxLearningPathDTO(nodes, edges);
}
/**
- * Generates Ngx representation for competency.
+ * Generates Ngx graph representation for competency.
*
* A competency's representation consists of
*
@@ -218,17 +217,15 @@ public NgxLearningPathDTO generateNgxRepresentation(@NotNull LearningPath learni
*