Skip to content

Commit

Permalink
Merge branch 'develop' into feature/programming-exercises/code-hint-m…
Browse files Browse the repository at this point in the history
…onaco
  • Loading branch information
pzdr7 committed Aug 11, 2024
2 parents 23956cd + 8c19389 commit 931018f
Show file tree
Hide file tree
Showing 32 changed files with 605 additions and 164 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ public interface LearningObject {
Long getId();

Set<CourseCompetency> getCompetencies();

boolean isVisibleToStudents();
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,18 @@ public void setCompletedUsers(Set<LectureUnitCompletion> completedUsers) {
this.completedUsers = completedUsers;
}

/**
* Checks if the lecture unit is visible to the students.
* A lecture unit is visible to the students if the lecture is visible to the students and the release date is null or in the past.
*
* @return true if the lecture unit is visible to the students, false otherwise
*/
@JsonProperty("visibleToStudents")
public boolean isVisibleToStudents() {
if (lecture == null || !lecture.isVisibleToStudents()) {
return false;
}

if (releaseDate == null) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,27 +347,32 @@ public NgxLearningPathDTO generateNgxPathRepresentation(@NotNull LearningPath le
* @return the navigation overview
*/
public LearningPathNavigationOverviewDTO getLearningPathNavigationOverview(long learningPathId) {
var learningPath = findWithCompetenciesAndLearningObjectsAndCompletedUsersById(learningPathId);
var learningPath = findWithCompetenciesAndReleasedLearningObjectsAndCompletedUsersById(learningPathId);
if (!userRepository.getUser().equals(learningPath.getUser())) {
throw new AccessForbiddenException("You are not allowed to access this learning path");
}
return learningPathNavigationService.getNavigationOverview(learningPath);
}

/**
* Finds a learning path by its id and eagerly fetches the competencies, linked lecture units and exercises, and the corresponding domain objects storing the progress of the
* connected user.
* Finds a learning path by its id and eagerly fetches the competencies, linked and released lecture units and exercises, and the corresponding domain objects storing the
* progress of the connected user.
* <p>
* As Spring Boot 3 doesn't support conditional JOIN FETCH statements, we have to retrieve the data manually.
*
* @param learningPathId the id of the learning path to fetch
* @return the learning path with fetched data
*/
public LearningPath findWithCompetenciesAndLearningObjectsAndCompletedUsersById(long learningPathId) {
public LearningPath findWithCompetenciesAndReleasedLearningObjectsAndCompletedUsersById(long learningPathId) {
LearningPath learningPath = learningPathRepository.findWithCompetenciesAndLectureUnitsAndExercisesByIdElseThrow(learningPathId);
// Remove exercise units, since they are already retrieved as exercises
learningPath.getCompetencies().stream().forEach(competency -> competency
.setLectureUnits(competency.getLectureUnits().stream().filter(lectureUnit -> !(lectureUnit instanceof ExerciseUnit)).collect(Collectors.toSet())));

// Remove exercises that are not visible to students
learningPath.getCompetencies()
.forEach(competency -> competency.setExercises(competency.getExercises().stream().filter(Exercise::isVisibleToStudents).collect(Collectors.toSet())));
// Remove unreleased lecture units as well as exercise units, since they are already retrieved as exercises
learningPath.getCompetencies().forEach(competency -> competency.setLectureUnits(competency.getLectureUnits().stream()
.filter(lectureUnit -> !(lectureUnit instanceof ExerciseUnit) && lectureUnit.isVisibleToStudents()).collect(Collectors.toSet())));

if (learningPath.getUser() == null) {
learningPath.getCompetencies().forEach(competency -> {
competency.setUserProgress(Collections.emptySet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public ResponseEntity<LearningPathNavigationDTO> getRelativeLearningPathNavigati
@RequestParam LearningObjectType learningObjectType, @RequestParam long competencyId) {
log.debug("REST request to get navigation for learning path with id: {} relative to learning object with id: {} and type: {} in competency with id: {}", learningPathId,
learningObjectId, learningObjectType, competencyId);
var learningPath = learningPathService.findWithCompetenciesAndLearningObjectsAndCompletedUsersById(learningPathId);
var learningPath = learningPathService.findWithCompetenciesAndReleasedLearningObjectsAndCompletedUsersById(learningPathId);
checkLearningPathAccessElseThrow(Optional.empty(), learningPath, Optional.empty());
return ResponseEntity.ok(learningPathNavigationService.getNavigationRelativeToLearningObject(learningPath, learningObjectId, learningObjectType, competencyId));
}
Expand All @@ -267,7 +267,7 @@ public ResponseEntity<LearningPathNavigationDTO> getRelativeLearningPathNavigati
@EnforceAtLeastStudent
public ResponseEntity<LearningPathNavigationDTO> getLearningPathNavigation(@PathVariable long learningPathId) {
log.debug("REST request to get navigation for learning path with id: {}", learningPathId);
var learningPath = learningPathService.findWithCompetenciesAndLearningObjectsAndCompletedUsersById(learningPathId);
var learningPath = learningPathService.findWithCompetenciesAndReleasedLearningObjectsAndCompletedUsersById(learningPathId);
checkLearningPathAccessElseThrow(Optional.empty(), learningPath, Optional.empty());
return ResponseEntity.ok(learningPathNavigationService.getNavigation(learningPath));
}
Expand All @@ -287,7 +287,7 @@ public ResponseEntity<LearningPathNavigationOverviewDTO> getLearningPathNavigati
}

private ResponseEntity<NgxLearningPathDTO> getLearningPathNgx(@PathVariable long learningPathId, NgxRequestType type) {
LearningPath learningPath = learningPathService.findWithCompetenciesAndLearningObjectsAndCompletedUsersById(learningPathId);
LearningPath learningPath = learningPathService.findWithCompetenciesAndReleasedLearningObjectsAndCompletedUsersById(learningPathId);
Course course = courseRepository.findByIdElseThrow(learningPath.getCourse().getId());
courseService.checkLearningPathsEnabledElseThrow(course);

Expand Down Expand Up @@ -372,7 +372,7 @@ public ResponseEntity<Set<CompetencyProgressForLearningPathDTO>> getCompetencyPr
@EnforceAtLeastStudent
public ResponseEntity<List<CompetencyNameDTO>> getCompetencyOrderForLearningPath(@PathVariable long learningPathId) {
log.debug("REST request to get competency order for learning path: {}", learningPathId);
final var learningPath = learningPathService.findWithCompetenciesAndLearningObjectsAndCompletedUsersById(learningPathId);
final var learningPath = learningPathService.findWithCompetenciesAndReleasedLearningObjectsAndCompletedUsersById(learningPathId);

checkLearningPathAccessElseThrow(Optional.of(learningPath.getCourse()), learningPath, Optional.empty());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* @param type the type of the learning object
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record LearningPathNavigationObjectDTO(long id, boolean completed, String name, long competencyId, LearningObjectType type) {
public record LearningPathNavigationObjectDTO(long id, boolean completed, String name, long competencyId, LearningObjectType type, boolean unreleased) {

/**
* Create a navigation object DTO from a learning object.
Expand All @@ -25,11 +25,26 @@ public record LearningPathNavigationObjectDTO(long id, boolean completed, String
* @return the navigation object DTO
*/
public static LearningPathNavigationObjectDTO of(LearningObject learningObject, boolean completed, long competencyId) {
return switch (learningObject) {
case LectureUnit lectureUnit -> new LearningPathNavigationObjectDTO(lectureUnit.getId(), completed, lectureUnit.getName(), competencyId, LearningObjectType.LECTURE);
case Exercise exercise -> new LearningPathNavigationObjectDTO(learningObject.getId(), completed, exercise.getTitle(), competencyId, LearningObjectType.EXERCISE);
long id = learningObject.getId();
String name;
LearningObjectType type;
boolean unreleased = !learningObject.isVisibleToStudents();

switch (learningObject) {
case LectureUnit lectureUnit -> {
name = lectureUnit.getName();
type = LearningObjectType.LECTURE;
}
case Exercise exercise -> {
name = exercise.getTitle();
type = LearningObjectType.EXERCISE;
}
default -> throw new IllegalArgumentException("Learning object must be either LectureUnit or Exercise");
};
}

name = unreleased ? "" : name;

return new LearningPathNavigationObjectDTO(id, completed, name, competencyId, type, unreleased);
}

public enum LearningObjectType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@
@for (learningObject of learningObjects(); let last = $last; track learningObject) {
<div
(click)="selectLearningObject(learningObject)"
class="row p-3 m-0 clickable align-items-center"
[ngClass]="{ 'selected-learning-object': currentLearningObject()?.id === learningObject.id && currentLearningObject()?.type === learningObject.type }"
class="row p-3 m-0 align-items-center"
[ngClass]="{
'selected-learning-object': currentLearningObject()?.id === learningObject.id && currentLearningObject()?.type === learningObject.type,
clickable: !learningObject.unreleased,
}"
>
<span class="col-md-auto p-0">{{ learningObject.name }}</span>
@if (learningObject.unreleased) {
<span class="col-md-auto p-0 text-muted" jhiTranslate="artemisApp.learningPath.navigation.overview.unreleasedLearningObjectLabel"></span>
<fa-icon [icon]="faLock" class="col-md-auto text-muted" />
} @else {
<span class="col-md-auto p-0">{{ learningObject.name }}</span>
}
@if (learningObject.completed) {
<fa-icon [icon]="faCheckCircle" class="col-md-auto text-success" />
} @else if (nextLearningObjectOnPath()?.id === learningObject.id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AlertService } from 'app/core/util/alert.service';
import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service';
import { LearningPathNavigationService } from 'app/course/learning-paths/services/learning-path-navigation.service';
import { LearningPathNavigationObjectDTO } from 'app/entities/competency/learning-path.model';
import { IconDefinition, faCheckCircle } from '@fortawesome/free-solid-svg-icons';
import { IconDefinition, faCheckCircle, faLock } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap';

Expand All @@ -17,6 +17,7 @@ import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap';
})
export class LearningPathNavOverviewLearningObjectsComponent implements OnInit {
protected readonly faCheckCircle: IconDefinition = faCheckCircle;
protected readonly faLock: IconDefinition = faLock;

private readonly alertService: AlertService = inject(AlertService);
private readonly learningPathApiService: LearningPathApiService = inject(LearningPathApiService);
Expand Down Expand Up @@ -57,7 +58,9 @@ export class LearningPathNavOverviewLearningObjectsComponent implements OnInit {
}

selectLearningObject(learningObject: LearningPathNavigationObjectDTO): void {
this.learningPathNavigationService.loadRelativeLearningPathNavigation(this.learningPathId(), learningObject);
this.onLearningObjectSelected.emit();
if (!learningObject.unreleased) {
this.learningPathNavigationService.loadRelativeLearningPathNavigation(this.learningPathId(), learningObject);
this.onLearningObjectSelected.emit();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ export interface LearningPathCompetencyDTO {
export interface LearningPathNavigationObjectDTO {
id: number;
completed: boolean;
name: string;
name?: string;
competencyId: number;
type: LearningObjectType;
unreleased: boolean;
}

export interface LearningPathNavigationDTO {
Expand Down
4 changes: 4 additions & 0 deletions src/main/webapp/app/exercises/quiz/manage/quiz-exercise.scss
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@

.markupEditorArea {
margin-bottom: 14px;

.markdown-editor {
border: 1px solid var(--border-color);
}
}

.mapping-numbers-wrapper {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export class QuizQuestionListEditComponent {
'Enter your long question if needed\n\n' +
'Select a part of the text and click on Add Spot to automatically create an input field and the corresponding mapping\n\n' +
'You can define a input field like this: This [-spot 1] an [-spot 2] field.\n\n' +
'To define the solution for the input fields you need to create a mapping (multiple mapping also possible):\n\n' +
'To define the solution for the input fields you need to create a mapping (multiple mapping also possible):\n\n\n' +
'[-option 1] is\n' +
'[-option 2] input\n' +
'[-option 1,2] correctInBothFields';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,24 +277,18 @@ <h3 class="mb-0"><span class="badge bg-success align-text-top" style="width: 60p
</div>
}
<div class="markupEditorArea" [hidden]="showVisualMode">
@if (!reEvaluationInProgress) {
<div class="toolbar">
<div class="btn-group">
<div class="btn btn-outline-secondary" jhiTranslate="artemisApp.shortAnswerQuestion.editor.addSpot" (click)="addSpotAtCursor()"></div>
<div class="btn btn-outline-secondary" jhiTranslate="artemisApp.shortAnswerQuestion.editor.addOption" (click)="addOption()"></div>
</div>
</div>
}
@if (!reEvaluationInProgress) {
<div class="question-content">
<jhi-ace-editor
<jhi-markdown-editor-monaco
#questionEditor
[(text)]="questionEditorText"
[mode]="questionEditorMode"
[autoUpdateContent]="questionEditorAutoUpdate"
(textChange)="onTextChange($event)"
style="min-height: 200px; width: 100%; overflow: auto"
class="form-control"
[enableResize]="false"
[enableFileUpload]="false"
[showPreviewButton]="false"
[defaultActions]="markdownActions"
[colorAction]="undefined"
[initialEditorHeight]="'external'"
[(markdown)]="questionEditorText"
(markdownChange)="onTextChange($event)"
/>
</div>
}
Expand Down
Loading

0 comments on commit 931018f

Please sign in to comment.