Skip to content

Commit

Permalink
fix some bugs and make the user experience in exercise details more c…
Browse files Browse the repository at this point in the history
…onsistent

Signed-off-by: Stephan Krusche <[email protected]>
  • Loading branch information
Stephan Krusche committed Apr 9, 2019
1 parent 25549b7 commit 319bbc6
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 70 deletions.
90 changes: 37 additions & 53 deletions src/main/java/de/tum/in/www1/artemis/domain/Exercise.java
Original file line number Diff line number Diff line change
@@ -1,41 +1,38 @@
package de.tum.in.www1.artemis.domain;

import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.Collectors;

import javax.persistence.*;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import com.fasterxml.jackson.annotation.*;
import com.google.common.collect.Sets;

import de.tum.in.www1.artemis.domain.enumeration.DifficultyLevel;
import de.tum.in.www1.artemis.domain.enumeration.InitializationState;
import de.tum.in.www1.artemis.domain.view.QuizView;
import de.tum.in.www1.artemis.service.scheduled.QuizScheduleService;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import javax.persistence.*;
import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.*;

/**
* A Exercise.
*/
@Entity
@Table(name = "exercise")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name = "discriminator",
discriminatorType = DiscriminatorType.STRING
)
@DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue(value = "E")
// NOTE: Use strict cache to prevent lost updates when updating statistics in semaphore (see StatisticService.java)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
// Annotation necessary to distinguish between concrete implementations of Exercise when deserializing from JSON
@JsonSubTypes({
@JsonSubTypes.Type(value = ProgrammingExercise.class, name = "programming"),
@JsonSubTypes.Type(value = ModelingExercise.class, name = "modeling"),
@JsonSubTypes.Type(value = QuizExercise.class, name = "quiz"),
@JsonSubTypes.Type(value = TextExercise.class, name = "text"),
@JsonSubTypes.Type(value = FileUploadExercise.class, name = "file-upload"),
})
@JsonSubTypes({ @JsonSubTypes.Type(value = ProgrammingExercise.class, name = "programming"), @JsonSubTypes.Type(value = ModelingExercise.class, name = "modeling"),
@JsonSubTypes.Type(value = QuizExercise.class, name = "quiz"), @JsonSubTypes.Type(value = TextExercise.class, name = "text"),
@JsonSubTypes.Type(value = FileUploadExercise.class, name = "file-upload"), })
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public abstract class Exercise implements Serializable {

Expand Down Expand Up @@ -92,7 +89,7 @@ public abstract class Exercise implements Serializable {
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@JsonIgnoreProperties("exercise")
private Set<Participation> participations = new HashSet<>();

@OneToMany(mappedBy = "assessedExercise", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@JsonIgnoreProperties("assessedExercise")
Expand All @@ -113,7 +110,6 @@ public abstract class Exercise implements Serializable {
@JsonIgnoreProperties("exercise")
private Set<StudentQuestion> studentQuestions = new HashSet<>();


// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
public Long getId() {
return id;
Expand Down Expand Up @@ -227,7 +223,6 @@ public void setGradingInstructions(String gradingInstructions) {
this.gradingInstructions = gradingInstructions;
}


public DifficultyLevel getDifficulty() {
return difficulty;
}
Expand Down Expand Up @@ -378,7 +373,7 @@ public Boolean isEnded() {
*/
@JsonView(QuizView.Before.class)
public Boolean isVisibleToStudents() {
if (releaseDate == null) { //no release date means the exercise is visible to students
if (releaseDate == null) { // no release date means the exercise is visible to students
return true;
}
return releaseDate.isBefore(ZonedDateTime.now());
Expand All @@ -392,8 +387,7 @@ public void filterSensitiveInformation() {
}

/**
* find a relevant participation for this exercise
* (relevancy depends on InitializationState)
* find a relevant participation for this exercise (relevancy depends on InitializationState)
*
* @param participations the list of available participations
* @return the found participation, or null, if none exist
Expand All @@ -406,11 +400,13 @@ public Participation findRelevantParticipation(List<Participation> participation
// InitializationState INITIALIZED is preferred
// => if we find one, we can return immediately
return participation;
} else if (participation.getInitializationState() == InitializationState.INACTIVE) {
}
else if (participation.getInitializationState() == InitializationState.INACTIVE) {
// InitializationState INACTIVE is also ok
// => if we can't find INITIALIZED, we return that one
relevantParticipation = participation;
} else if (participation.getExercise() instanceof ModelingExercise || participation.getExercise() instanceof TextExercise) {
}
else if (participation.getExercise() instanceof ModelingExercise || participation.getExercise() instanceof TextExercise) {
return participation;
}
}
Expand All @@ -419,8 +415,8 @@ public Participation findRelevantParticipation(List<Participation> participation
}

/**
* Get the latest relevant result from the given participation (rated == true or rated == null)
* (relevancy depends on Exercise type => this should be overridden by subclasses if necessary)
* Get the latest relevant result from the given participation (rated == true or rated == null) (relevancy depends on Exercise type => this should be overridden by subclasses
* if necessary)
*
* @param participation the participation whose results we are considering
* @return the latest relevant result in the given participation, or null, if none exist
Expand All @@ -429,14 +425,14 @@ public Result findLatestRatedResultWithCompletionDate(Participation participatio
// for most types of exercises => return latest result (all results are relevant)
Result latestResult = null;
for (Result result : participation.getResults()) {
//NOTE: for the dashboard we only use rated results with completion date
//TODO: result.isRated() == null is a compatibility mechanism that we should deactivate soon
// NOTE: for the dashboard we only use rated results with completion date
// TODO: result.isRated() == null is a compatibility mechanism that we should deactivate soon
if (result.getCompletionDate() != null && (result.isRated() == null || result.isRated() == Boolean.TRUE)) {
//take the first found result that fulfills the above requirements
// take the first found result that fulfills the above requirements
if (latestResult == null) {
latestResult = result;
}
//take newer results and thus disregard older ones
// take newer results and thus disregard older ones
else if (latestResult.getCompletionDate().isBefore(result.getCompletionDate())) {
latestResult = result;
}
Expand All @@ -446,21 +442,19 @@ else if (latestResult.getCompletionDate().isBefore(result.getCompletionDate()))
}

/**
* Returns all results of an exercise for give participation.
* If the exercise is restricted like {@link QuizExercise} please override this function with the respective filter.
* (relevancy depends on Exercise type => this should be overridden by subclasses if necessary)
* Returns all results of an exercise for give participation that have a completion date. If the exercise is restricted like {@link QuizExercise} please override this function
* with the respective filter. (relevancy depends on Exercise type => this should be overridden by subclasses if necessary)
*
* @param participation the participation whose results we are considering
* @return all results of given participation, or null, if none exist
*/
public Set<Result> findResultsFilteredForStudents(Participation participation) {
return participation.getResults();
return participation.getResults().stream().filter(result -> result.getCompletionDate() != null).collect(Collectors.toSet());
}

/**
* Find the participation in participations that belongs to the given exercise
* that includes the exercise data, plus the found participation with its most recent relevant result.
* Filter everything else that is not relevant
* Find the participation in participations that belongs to the given exercise that includes the exercise data, plus the found participation with its most recent relevant
* result. Filter everything else that is not relevant
*
* @param participations the set of participations, wherein to search for the relevant participation
* @param username
Expand Down Expand Up @@ -523,22 +517,12 @@ public int hashCode() {
return Objects.hashCode(getId());
}


@Override
public String toString() {
return "Exercise{" +
"id=" + getId() +
", problemStatement='" + getProblemStatement() + "'" +
", gradingInstructions='" + getGradingInstructions() + "'" +
", title='" + getTitle() + "'" +
", shortName='" + getShortName() + "'" +
", releaseDate='" + getReleaseDate() + "'" +
", dueDate='" + getDueDate() + "'" +
", assessmentDueDate='" + getAssessmentDueDate() + "'" +
", maxScore=" + getMaxScore() +
", difficulty='" + getDifficulty() + "'" +
", categories='" + getCategories() + "'" +
"}";
return "Exercise{" + "id=" + getId() + ", problemStatement='" + getProblemStatement() + "'" + ", gradingInstructions='" + getGradingInstructions() + "'" + ", title='"
+ getTitle() + "'" + ", shortName='" + getShortName() + "'" + ", releaseDate='" + getReleaseDate() + "'" + ", dueDate='" + getDueDate() + "'"
+ ", assessmentDueDate='" + getAssessmentDueDate() + "'" + ", maxScore=" + getMaxScore() + ", difficulty='" + getDifficulty() + "'" + ", categories='"
+ getCategories() + "'" + "}";
}

public Set<TutorParticipation> getTutorParticipations() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,12 @@ public ResponseEntity<Long[]> getNextOptimalModelSubmissions(@PathVariable Long
if (compassService.isSupported(modelingExercise.getDiagramType())) {
Set<Long> optimalModelSubmissions = compassService.getModelsWaitingForAssessment(exerciseId);
if (optimalModelSubmissions.isEmpty()) {
return ResponseEntity.notFound().build();
return ResponseEntity.ok(new Long[] {}); // empty
}
return ResponseEntity.ok(optimalModelSubmissions.toArray(new Long[] {}));
}
else {
// TODO: proper error message Not supported
return ResponseEntity.notFound().build();
return ResponseEntity.ok(new Long[] {}); // empty
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ <h3 class="card-title">
<div class="card-body">
<h3>{{ exercise.title }}</h3>
<br />
<jhi-expandable-paragraph header="{{ 'assessmentInstructions.problemStatement' | translate }}" [paragraph]="exercise.problemStatement"></jhi-expandable-paragraph>
<jhi-expandable-paragraph header="{{ 'assessmentInstructions.problemStatement' | translate }}" text="{{formattedProblemStatement}}"></jhi-expandable-paragraph>
<br />
<jhi-expandable-sample-solution [exercise]="exercise"></jhi-expandable-sample-solution>
<br />
<jhi-expandable-paragraph header="{{ 'assessmentInstructions.gradingCriteria' | translate }}" [paragraph]="exercise.gradingInstructions"></jhi-expandable-paragraph>
<jhi-expandable-paragraph header="{{ 'assessmentInstructions.gradingCriteria' | translate }}" text="{{formattedGradingCriteria}}"></jhi-expandable-paragraph>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AfterViewInit, Component, Input, OnInit } from '@angular/core';
import * as interact from 'interactjs';
import { Exercise } from 'app/entities/exercise';
import { ArtemisMarkdown } from 'app/components/util/markdown.service';

@Component({
selector: 'jhi-assessment-instructions',
Expand All @@ -11,9 +12,15 @@ export class AssessmentInstructionsComponent implements OnInit, AfterViewInit {
@Input() exercise: Exercise;
@Input() collapsed = false;

constructor() {}
formattedProblemStatement: string;
formattedGradingCriteria: string;

ngOnInit() {}
constructor(private artemisMarkdown: ArtemisMarkdown) {}

ngOnInit() {
this.formattedProblemStatement = this.artemisMarkdown.htmlForMarkdown(this.exercise.problemStatement);
this.formattedGradingCriteria = this.artemisMarkdown.htmlForMarkdown(this.exercise.gradingInstructions);
}

ngAfterViewInit(): void {
interact('.expanded-instructions')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ <h5 class="expandable-header" (click)="isCollapsed = !isCollapsed">
{{ header }}
<fa-icon [icon]="isCollapsed ? 'angle-right' : 'angle-down'"></fa-icon>
</h5>
<p [ngbCollapse]="isCollapsed">{{ paragraph }}</p>
<p [ngbCollapse]="isCollapsed" [innerHTML]="text"></p>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Component, OnInit, Input } from '@angular/core';
})
export class ExpandableParagraphComponent implements OnInit {
@Input() header = 'Toggle paragraph';
@Input() paragraph: string;
@Input() text: string;
@Input() isCollapsed = false;
constructor() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ <h5 class="expandable-header" (click)="isCollapsed = !isCollapsed">
</h5>
<div [ngbCollapse]="isCollapsed">
<jhi-modeling-editor *ngIf="exercise.sampleSolutionModel"></jhi-modeling-editor>
<p>{{ exercise.sampleSolutionExplanation }}</p>
<p [innerHTML]="formattedSampleSolutionExplanation"></p>
</div>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { ModelingExercise } from 'app/entities/modeling-exercise';
import { ArtemisMarkdown } from 'app/components/util/markdown.service';

@Component({
selector: 'jhi-expandable-sample-solution',
Expand All @@ -9,7 +10,12 @@ import { ModelingExercise } from 'app/entities/modeling-exercise';
export class ExpandableSampleSolutionComponent implements OnInit {
@Input() exercise: ModelingExercise;
@Input() isCollapsed = false;
constructor() {}

ngOnInit() {}
formattedSampleSolutionExplanation: string;

constructor(private artemisMarkdown: ArtemisMarkdown) {}

ngOnInit() {
this.formattedSampleSolutionExplanation = this.artemisMarkdown.htmlForMarkdown(this.exercise.sampleSolutionExplanation);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { DiagramType } from '@ls1intum/apollon';

export class ModelingExercise extends Exercise {
public diagramType: DiagramType;
public sampleSolutionModel: String;
public sampleSolutionExplanation: String;
public sampleSolutionModel: string;
public sampleSolutionExplanation: string;

constructor(diagramType: DiagramType, course?: Course) {
super(ExerciseType.MODELING);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,18 @@
outlined="true"
[buttonLoading]="exercise.loading"
[smallButton]="smallButtons"
*ngIf="exercise.participations && exercise.participations.length > 0 && exercise.participations[0].initializationState === 'FINISHED'"
*ngIf="exercise.participations && exercise.participations.length > 0 && exercise.participations[0].initializationState === 'FINISHED' && !exercise.participations[0].results"
routerLink="/modeling-submission/{{ exercise.participations[0].id }}"
></button>
<!-- TODO improve the distinction, in particular if there are multiple submissions and results -->
<button
jhi-exercise-action-button
buttonIcon="folder-open"
[buttonLabel]="'arTeMiSApp.exerciseActions.viewResults' | translate"
outlined="true"
[buttonLoading]="exercise.loading"
[smallButton]="smallButtons"
*ngIf="exercise.participations && exercise.participations.length > 0 && exercise.participations[0].initializationState === 'FINISHED' && exercise.participations[0].results"
routerLink="/modeling-submission/{{ exercise.participations[0].id }}"
></button>
</div>
Expand Down Expand Up @@ -229,13 +240,24 @@
*ngIf="participationStatus() === UNINITIALIZED"
(click)="startExercise()"
></button>
<button
jhi-exercise-action-button
buttonIcon="play-circle"
[buttonLabel]="'arTeMiSApp.exerciseActions.resumeExercise' | translate"
[buttonLoading]="exercise.loading"
[smallButton]="smallButtons"
[hideLabelMobile]="false"
*ngIf="participationStatus() === INACTIVE"
(click)="resumeExercise()"
></button>
<button
jhi-exercise-action-button
buttonIcon="folder-open"
[buttonLabel]="'arTeMiSApp.exerciseActions.openTextEditor' | translate"
[buttonLoading]="exercise.loading"
[smallButton]="smallButtons"
*ngIf="participationStatus() === TEXT_EXERCISE && exercise.participations && exercise.participations.length > 0"
[hideLabelMobile]="false"
*ngIf="exercise.participations && exercise.participations.length > 0 && exercise.participations[0].initializationState === 'INITIALIZED'"
routerLink="/text/{{ exercise.participations[0].id }}"
></button>
</div>
Expand All @@ -250,7 +272,18 @@
[buttonLoading]="exercise.loading"
[smallButton]="smallButtons"
[hideLabelMobile]="false"
*ngIf="participationStatus() === TEXT_EXERCISE && exercise.participations && exercise.participations.length > 0"
*ngIf="exercise.participations && exercise.participations.length > 0 && exercise.participations[0].initializationState === 'FINISHED' && !exercise.participations[0].results"
routerLink="/text/{{ exercise.participations[0].id }}"
></button>
<!-- TODO improve the distinction, in particular if there are multiple submissions and results -->
<button
jhi-exercise-action-button
buttonIcon="folder-open"
[buttonLabel]="'arTeMiSApp.exerciseActions.viewResults' | translate"
outlined="true"
[buttonLoading]="exercise.loading"
[smallButton]="smallButtons"
*ngIf="exercise.participations && exercise.participations.length > 0 && exercise.participations[0].initializationState === 'FINISHED' && exercise.participations[0].results"
routerLink="/text/{{ exercise.participations[0].id }}"
></button>
</div>
Expand Down

0 comments on commit 319bbc6

Please sign in to comment.