Skip to content

Commit

Permalink
Properly use assessment due date for programming exercises (#2137)
Browse files Browse the repository at this point in the history
  • Loading branch information
fde312 committed Oct 6, 2020
1 parent b70cffe commit f8f1903
Show file tree
Hide file tree
Showing 28 changed files with 473 additions and 46 deletions.
4 changes: 4 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/domain/Exercise.java
Expand Up @@ -591,6 +591,10 @@ else if (resultDate1.isAfter(resultDate2)) {
* @return all results of given participation, or null, if none exist
*/
public Set<Result> findResultsFilteredForStudents(Participation participation) {
boolean isAssessmentOver = getAssessmentDueDate() == null || getAssessmentDueDate().isBefore(ZonedDateTime.now());
if (!isAssessmentOver) {
return Set.of();
}
return participation.getResults().stream().filter(result -> result.getCompletionDate() != null).collect(Collectors.toSet());
}

Expand Down
Expand Up @@ -8,6 +8,7 @@
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.persistence.*;
Expand All @@ -24,6 +25,7 @@
import de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage;
import de.tum.in.www1.artemis.domain.enumeration.RepositoryType;
import de.tum.in.www1.artemis.domain.enumeration.SubmissionType;
import de.tum.in.www1.artemis.domain.participation.Participation;
import de.tum.in.www1.artemis.domain.participation.SolutionProgrammingExerciseParticipation;
import de.tum.in.www1.artemis.domain.participation.TemplateProgrammingExerciseParticipation;

Expand Down Expand Up @@ -286,7 +288,9 @@ public void generateAndSetProjectKey() {
public Submission findAppropriateSubmissionByResults(Set<Submission> submissions) {
return submissions.stream().filter(submission -> {
if (submission.getResult() != null) {
return submission.getResult().isRated();
return (submission.getResult().isRated() && !submission.getResult().getAssessmentType().equals(AssessmentType.MANUAL))
|| submission.getResult().getAssessmentType().equals(AssessmentType.MANUAL)
&& (this.getAssessmentDueDate() == null || this.getAssessmentDueDate().isBefore(ZonedDateTime.now()));
}
return this.getDueDate() == null || submission.getType().equals(SubmissionType.INSTRUCTOR) || submission.getType().equals(SubmissionType.TEST)
|| submission.getSubmissionDate().isBefore(this.getDueDate());
Expand Down Expand Up @@ -482,6 +486,44 @@ public void filterSensitiveInformation() {
super.filterSensitiveInformation();
}

@Override
public Set<Result> findResultsFilteredForStudents(Participation participation) {
boolean isAssessmentOver = getAssessmentDueDate() == null || getAssessmentDueDate().isBefore(ZonedDateTime.now());
return participation.getResults().stream()
.filter(result -> (result.getAssessmentType().equals(AssessmentType.MANUAL) && isAssessmentOver) || result.getAssessmentType().equals(AssessmentType.AUTOMATIC))
.collect(Collectors.toSet());
}

@Override
@Nullable
public Submission findLatestSubmissionWithRatedResultWithCompletionDate(Participation participation, Boolean ignoreAssessmentDueDate) {
// for most types of exercises => return latest result (all results are relevant)
Submission latestSubmission = null;
// we get the results over the submissions
if (participation.getSubmissions() == null || participation.getSubmissions().isEmpty()) {
return null;
}
for (var submission : participation.getSubmissions()) {
var result = submission.getResult();
if (result == null) {
continue;
}
// NOTE: for the dashboard we only use rated results with completion date or automatic result
boolean isAssessmentOver = ignoreAssessmentDueDate || getAssessmentDueDate() == null || getAssessmentDueDate().isBefore(ZonedDateTime.now());
if ((result.getAssessmentType().equals(AssessmentType.MANUAL) && isAssessmentOver) || result.getAssessmentType().equals(AssessmentType.AUTOMATIC)) {
// take the first found result that fulfills the above requirements
if (latestSubmission == null) {
latestSubmission = submission;
}
// take newer results and thus disregard older ones
else if (latestSubmission.getResult().getCompletionDate().isBefore(result.getCompletionDate())) {
latestSubmission = submission;
}
}
}
return latestSubmission;
}

/**
* Check if manual results are allowed for the exercise
* @return true if manual results are allowed, false otherwise
Expand Down
Expand Up @@ -2,6 +2,7 @@

import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD;

import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
Expand All @@ -21,8 +22,10 @@
@Repository
public interface ProgrammingExerciseStudentParticipationRepository extends JpaRepository<ProgrammingExerciseStudentParticipation, Long> {

@Query("select p from ProgrammingExerciseStudentParticipation p left join fetch p.results pr left join fetch pr.feedbacks left join fetch pr.submission where p.id = :participationId and (pr.id = (select max(id) from p.results) or pr.id = null)")
Optional<ProgrammingExerciseStudentParticipation> findByIdWithLatestResultAndFeedbacksAndRelatedSubmissions(@Param("participationId") Long participationId);
@Query("select p from ProgrammingExerciseStudentParticipation p left join fetch p.results pr left join fetch pr.feedbacks left join fetch pr.submission "
+ "where p.id = :participationId and (pr.id = (select max(prr.id) from p.results prr where prr.assessmentType = 'AUTOMATIC' or (prr.completionDate IS NOT NULL and (p.exercise.assessmentDueDate IS NULL OR p.exercise.assessmentDueDate < :#{#dateTime}))) or pr.id IS NULL)")
Optional<ProgrammingExerciseStudentParticipation> findByIdWithLatestResultAndFeedbacksAndRelatedSubmissions(@Param("participationId") Long participationId,
@Param("dateTime") ZonedDateTime dateTime);

@Query("select p from ProgrammingExerciseStudentParticipation p left join fetch p.results pr left join fetch pr.feedbacks left join fetch pr.submission left join fetch pr.assessor where p.id = :participationId")
Optional<ProgrammingExerciseStudentParticipation> findByIdWithResultsAndFeedbacksAndRelatedSubmissionsAndAssessor(@Param("participationId") Long participationId);
Expand Down
@@ -1,6 +1,7 @@
package de.tum.in.www1.artemis.service;

import java.security.Principal;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -146,7 +147,7 @@ public List<ProgrammingExerciseStudentParticipation> findByExerciseAndParticipat
}

public Optional<ProgrammingExerciseStudentParticipation> findStudentParticipationWithLatestResultAndFeedbacksAndRelatedSubmissions(Long participationId) {
return studentParticipationRepository.findByIdWithLatestResultAndFeedbacksAndRelatedSubmissions(participationId);
return studentParticipationRepository.findByIdWithLatestResultAndFeedbacksAndRelatedSubmissions(participationId, ZonedDateTime.now());
}

public Optional<ProgrammingExerciseStudentParticipation> findStudentParticipationWithResultsAndFeedbacksAndRelatedSubmissionsAndAssessor(Long participationId) {
Expand Down
Expand Up @@ -5,6 +5,15 @@ <h3 class="top-container">
<span jhiTranslate="artemisApp.assessment.assessment">Assessment</span>
</div>
<jhi-alert></jhi-alert>
<ngb-alert
*ngIf="hasAssessmentDueDatePassed && !result?.completionDate"
[style.fontSize]="'65%'"
[type]="'warning'"
(close)="hasAssessmentDueDatePassed = false"
jhiTranslate="artemisApp.assessment.assessmentDueDateIsOver"
>
Assessment Due Date is over, the assessment will be published immediately after submitting
</ngb-alert>
<div class="row-container" *ngIf="!isLoading">
<span
id="assessmentLocked"
Expand Down
Expand Up @@ -32,6 +32,7 @@ export class AssessmentHeaderComponent {
@Input() hasComplaint = false;
@Input() complaintHandled = false;
@Input() assessmentsAreValid: boolean;
@Input() hasAssessmentDueDatePassed: boolean;

@Output() save = new EventEmitter<void>();
@Output() submit = new EventEmitter<void>();
Expand Down
Expand Up @@ -12,6 +12,7 @@
[isAtLeastInstructor]="isAtLeastInstructor"
[canOverride]="canOverride"
[assessmentsAreValid]="assessmentsAreValid"
[hasAssessmentDueDatePassed]="hasAssessmentDueDatePassed"
[result]="result"
(save)="save.emit()"
(submit)="submit.emit()"
Expand Down
Expand Up @@ -36,6 +36,7 @@ export class AssessmentLayoutComponent {
@Input() assessmentsAreValid: boolean;
ComplaintType = ComplaintType;
@Input() complaint: Complaint | null;
@Input() hasAssessmentDueDatePassed: boolean;

@Output() save = new EventEmitter<void>();
@Output() submit = new EventEmitter<void>();
Expand Down
Expand Up @@ -9,6 +9,7 @@
[result]="result"
[assessmentsAreValid]="assessmentsAreValid"
[complaint]="complaint"
[hasAssessmentDueDatePassed]="hasAssessmentDueDatePassed"
(save)="onSaveAssessment()"
(submit)="onSubmitAssessment()"
(cancel)="onCancelAssessment()"
Expand Down
Expand Up @@ -27,6 +27,7 @@ import { Result } from 'app/entities/result.model';
import { StructuredGradingCriterionService } from 'app/exercises/shared/structured-grading-criterion/structured-grading-criterion.service';
import { assessmentNavigateBack } from 'app/exercises/shared/navigate-back.util';
import { getCourseFromExercise } from 'app/entities/exercise.model';
import { now } from 'moment';

@Component({
providers: [FileUploadAssessmentsService],
Expand Down Expand Up @@ -58,6 +59,7 @@ export class FileUploadAssessmentComponent implements OnInit, AfterViewInit, OnD
isLoading = true;
isTestRun = false;
courseId: number;
hasAssessmentDueDatePassed: boolean;

/** Resizable constants **/
resizableMinWidth = 100;
Expand Down Expand Up @@ -170,6 +172,7 @@ export class FileUploadAssessmentComponent implements OnInit, AfterViewInit, OnD
this.submission = submission;
this.participation = this.submission.participation as StudentParticipation;
this.exercise = this.participation.exercise as FileUploadExercise;
this.hasAssessmentDueDatePassed = !!this.exercise.assessmentDueDate && moment(this.exercise.assessmentDueDate).isBefore(now());
this.result = this.submission.result!;
if (this.result.hasComplaint) {
this.getComplaint();
Expand Down
Expand Up @@ -11,6 +11,7 @@
[result]="result"
[assessmentsAreValid]="assessmentsAreValid"
[complaint]="complaint"
[hasAssessmentDueDatePassed]="hasAssessmentDueDatePassed"
(save)="onSaveAssessment()"
(submit)="onSubmitAssessment()"
(cancel)="onCancelAssessment()"
Expand Down
Expand Up @@ -22,6 +22,7 @@ import { Feedback, FeedbackHighlightColor, FeedbackType } from 'app/entities/fee
import { Complaint, ComplaintType } from 'app/entities/complaint.model';
import { ModelingAssessmentService } from 'app/exercises/modeling/assess/modeling-assessment.service';
import { assessmentNavigateBack } from 'app/exercises/shared/navigate-back.util';
import { now } from 'moment';

@Component({
selector: 'jhi-modeling-assessment-editor',
Expand Down Expand Up @@ -52,6 +53,7 @@ export class ModelingAssessmentEditorComponent implements OnInit {
isLoading = true;
isTestRun = false;
hasAutomaticFeedback = false;
hasAssessmentDueDatePassed: boolean;

private cancelConfirmationText: string;

Expand Down Expand Up @@ -146,6 +148,7 @@ export class ModelingAssessmentEditorComponent implements OnInit {
const studentParticipation = this.submission.participation as StudentParticipation;
this.modelingExercise = studentParticipation.exercise as ModelingExercise;
this.result = this.submission.result;
this.hasAssessmentDueDatePassed = !!this.modelingExercise!.assessmentDueDate && moment(this.modelingExercise!.assessmentDueDate).isBefore(now());
if (this.result?.hasComplaint) {
this.getComplaint(this.result.id);
}
Expand Down
Expand Up @@ -25,6 +25,7 @@
[result]="manualResult"
[assessmentsAreValid]="assessmentsAreValid"
[complaint]="complaint"
[hasAssessmentDueDatePassed]="hasAssessmentDueDatePassed"
(save)="save()"
(submit)="submit()"
(cancel)="cancel()"
Expand Down
Expand Up @@ -27,6 +27,7 @@ import { CodeEditorContainerComponent } from 'app/exercises/programming/shared/c
import { assessmentNavigateBack } from 'app/exercises/shared/navigate-back.util';
import { Course } from 'app/entities/course.model';
import { Feedback, FeedbackType } from 'app/entities/feedback.model';
import { now } from 'moment';

@Component({
selector: 'jhi-code-editor-tutor-assessment',
Expand Down Expand Up @@ -61,6 +62,7 @@ export class CodeEditorTutorAssessmentContainerComponent implements OnInit, OnDe
loadingParticipation = false;
participationCouldNotBeFetched = false;
showEditorInstructions = true;
hasAssessmentDueDatePassed: boolean;

private get course(): Course | undefined {
return this.exercise?.course || this.exercise?.exerciseGroup?.exam?.course;
Expand Down Expand Up @@ -117,6 +119,7 @@ export class CodeEditorTutorAssessmentContainerComponent implements OnInit, OnDe
// Either submission from latest manual or automatic result
this.submission = this.getLatestResult(this.participation.results)?.submission as ProgrammingSubmission;
this.exercise = this.participation.exercise as ProgrammingExercise;
this.hasAssessmentDueDatePassed = !!this.exercise!.assessmentDueDate && moment(this.exercise!.assessmentDueDate).isBefore(now());

this.checkPermissions();
this.handleFeedback();
Expand Down
Expand Up @@ -158,6 +158,11 @@ export const areManualResultsAllowed = (exercise: Exercise) => {
}
// Only allow new results if manual reviews are activated and the due date/after due date has passed
const progEx = exercise as ProgrammingExercise;
const relevantDueDate = progEx.buildAndTestStudentSubmissionsAfterDueDate ? progEx.buildAndTestStudentSubmissionsAfterDueDate : progEx.dueDate;
return progEx.isAtLeastTutor === true && progEx.assessmentType === AssessmentType.SEMI_AUTOMATIC && (!relevantDueDate || moment(relevantDueDate).isBefore(now()));
const relevantDueDate = progEx.buildAndTestStudentSubmissionsAfterDueDate ?? progEx.dueDate;
return (
progEx.isAtLeastTutor === true &&
progEx.assessmentType === AssessmentType.SEMI_AUTOMATIC &&
(!relevantDueDate || moment(relevantDueDate).isBefore(now())) &&
(!progEx.assessmentDueDate || progEx.assessmentDueDate.isAfter(now()))
);
};
Expand Up @@ -13,6 +13,7 @@
[assessmentsAreValid]="assessmentsAreValid"
[complaint]="complaint"
[isTestRun]="isTestRun"
[hasAssessmentDueDatePassed]="hasAssessmentDueDatePassed"
(save)="save()"
(submit)="submit()"
(cancel)="cancel()"
Expand Down
Expand Up @@ -23,6 +23,7 @@ import { NEW_ASSESSMENT_PATH } from 'app/exercises/text/assess-new/text-submissi
import { StructuredGradingCriterionService } from 'app/exercises/shared/structured-grading-criterion/structured-grading-criterion.service';
import { Course } from 'app/entities/course.model';
import { assessmentNavigateBack } from 'app/exercises/shared/navigate-back.util';
import { now } from 'moment';

@Component({
selector: 'jhi-text-submission-assessment',
Expand Down Expand Up @@ -56,6 +57,7 @@ export class TextSubmissionAssessmentComponent implements OnInit {
isAtLeastInstructor: boolean;
assessmentsAreValid: boolean;
noNewSubmissions: boolean;
hasAssessmentDueDatePassed: boolean;

/*
* Non-resetted properties:
Expand Down Expand Up @@ -161,6 +163,8 @@ export class TextSubmissionAssessmentComponent implements OnInit {
this.exercise = this.participation?.exercise as TextExercise;
this.result = this.submission?.result;

this.hasAssessmentDueDatePassed = !!this.exercise!.assessmentDueDate && moment(this.exercise!.assessmentDueDate).isBefore(now());

this.prepareTextBlocksAndFeedbacks();
this.getComplaint();
this.updateUrlIfNeeded();
Expand Down
2 changes: 1 addition & 1 deletion src/main/webapp/app/shared/util/utils.ts
Expand Up @@ -81,5 +81,5 @@ export const round = (value: any, exp: number) => {
* @param results
*/
export const findLatestResult = (results?: Result[]) => {
return results?.length ? results.reduce((current, result) => (current.id! > result.id! ? current : result)) : undefined;
return results && results.length > 0 ? results.reduce((current, result) => (current.id! > result.id! ? current : result)) : undefined;
};
1 change: 1 addition & 0 deletions src/main/webapp/i18n/de/assessment.json
Expand Up @@ -14,6 +14,7 @@
"assessmentLockedCurrentUser": "Diese Bewertung ist von dir gesperrt",
"assessmentReadOnlyUnhandledComplaint": "Es gibt eine Beschwerde für diese Bewertung. Ein anderer Tutor muss antworten.",
"assessmentReadOnlyHandledComplaint": "Die Beschwerde über deine Bewertung wurde bearbeitet.",
"assessmentDueDateIsOver": "Die Einreichungsfrist der Bewertung ist vorbei, die nächste Einreichung wird direkt veröffentlicht",
"button": {
"overrideAssessment": "Bewertung überschreiben",
"nextSubmission": "Nächste Abgabe bewerten",
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/en/assessment.json
Expand Up @@ -14,6 +14,7 @@
"assessmentLockedCurrentUser": "You have the lock for this assessment",
"assessmentReadOnlyUnhandledComplaint": "There is a complaint for this assessment. Another tutor must respond.",
"assessmentReadOnlyHandledComplaint": "The complaint about your assessment has been resolved.",
"assessmentDueDateIsOver": "Assessment Due Date is over, the assessment will be published immediately after submitting",
"button": {
"overrideAssessment": "Override Assessment",
"nextSubmission": "Assess Next Submission",
Expand Down

0 comments on commit f8f1903

Please sign in to comment.