Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exam mode: Add save exercise button to exercises #9569

Merged
merged 22 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
<jhi-quiz-submission-exam
[quizConfiguration]="exercise"
[studentSubmission]="exercise.studentParticipations[0].submissions![0]"
(saveCurrentExercise)="triggerSave(false)"
/>
}
@case (FILEUPLOAD) {
Expand All @@ -68,10 +69,19 @@
/>
}
@case (TEXT) {
<jhi-text-editor-exam [exercise]="exercise" [studentSubmission]="exercise.studentParticipations[0].submissions![0]" />
<jhi-text-editor-exam
[exercise]="exercise"
[studentSubmission]="exercise.studentParticipations[0].submissions![0]"
(saveCurrentExercise)="triggerSave(false)"
/>
}
@case (MODELING) {
<jhi-modeling-submission-exam [exercise]="exercise" [studentSubmission]="exercise.studentParticipations[0].submissions![0]" />
<jhi-modeling-submission-exam
[exercise]="exercise"
[studentSubmission]="exercise.studentParticipations[0].submissions![0]"
[isSubmissionSynced]="exercise.studentParticipations[0].submissions![0].isSynced"
edkaya marked this conversation as resolved.
Show resolved Hide resolved
(saveCurrentExercise)="triggerSave(false)"
/>
}
@case (PROGRAMMING) {
<jhi-programming-submission-exam
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ArtemisCodeEditorModule } from 'app/exercises/programming/shared/code-e
import { ArtemisFullscreenModule } from 'app/shared/fullscreen/fullscreen.module';
import { ArtemisModelingEditorModule } from 'app/exercises/modeling/shared/modeling-editor.module';
import { ArtemisProgrammingSubmissionPolicyStatusModule } from 'app/exercises/programming/participate/programming-submission-policy-status.module';
import { ExerciseSaveButtonComponent } from './exercise-save-button/exercise-save-button.component';

@NgModule({
declarations: [
Expand All @@ -42,6 +43,7 @@ import { ArtemisProgrammingSubmissionPolicyStatusModule } from 'app/exercises/pr
ArtemisModelingEditorModule,
ArtemisProgrammingSubmissionPolicyStatusModule,
ExamExerciseUpdateHighlighterModule,
ExerciseSaveButtonComponent,
edkaya marked this conversation as resolved.
Show resolved Hide resolved
],
exports: [FileUploadExamSubmissionComponent, QuizExamSubmissionComponent, ProgrammingExamSubmissionComponent, TextExamSubmissionComponent, ModelingExamSubmissionComponent],
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<button id="save-exam" type="submit" [disabled]="submission()?.isSynced" class="btn btn-primary" (click)="onSave()">
krusche marked this conversation as resolved.
Show resolved Hide resolved
<fa-icon class="saved" [fixedWidth]="true" [icon]="submission()?.submitted && submission()?.isSynced ? facSaveSuccess : faFloppyDisk"> </fa-icon>
<span
class="d-none d-sm-inline"
[jhiTranslate]="submission()?.submitted && submission()?.isSynced ? 'artemisApp.examParticipation.exerciseSaved' : 'artemisApp.examParticipation.saveExercise'"
>
</span>
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.saved {
--fa-secondary-opacity: 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Component, input, output } from '@angular/core';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { faFloppyDisk } from '@fortawesome/free-solid-svg-icons';
import { facSaveSuccess } from '../../../../../content/icons/icons';
import { Submission } from 'app/entities/submission.model';
import { TranslateDirective } from 'app/shared/language/translate.directive';

edkaya marked this conversation as resolved.
Show resolved Hide resolved
@Component({
selector: 'jhi-exercise-save-button',
templateUrl: './exercise-save-button.component.html',
styleUrls: ['./exercise-save-button.component.scss'],
standalone: true,
imports: [FaIconComponent, TranslateDirective],
})
export class ExerciseSaveButtonComponent {
protected readonly faFloppyDisk = faFloppyDisk;
protected readonly facSaveSuccess = facSaveSuccess;

submission = input<Submission>();
save = output<void>();

onSave() {
this.save.emit();
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
@if (exercise) {
<h3 class="text-align-left fw-normal">
<span>
{{ exercise.exerciseGroup?.title }}
</span>
<!-- prettier-ignore -->
<span left-header>&nbsp;({{ exercise.maxPoints }} {{ 'artemisApp.examParticipation.points' | artemisTranslate }}@if (exercise.bonusPoints) {
<span
>, {{ exercise.bonusPoints }} {{ 'artemisApp.examParticipation.bonus' | artemisTranslate }}</span>
<div class="d-flex justify-content-between align-items-center">
<h3 class="text-align-left fw-normal mb-0">
<span>
{{ exercise.exerciseGroup?.title }}
</span>
<!-- prettier-ignore -->
<span left-header>&nbsp;({{ exercise.maxPoints }} {{ 'artemisApp.examParticipation.points' | artemisTranslate }}@if (exercise.bonusPoints) {
<span
>, {{ exercise.bonusPoints }} {{ 'artemisApp.examParticipation.bonus' | artemisTranslate }}</span>
}) @if (exercise.includedInOverallScore !== IncludedInOverallScore.INCLUDED_COMPLETELY) {
<jhi-included-in-score-badge
[includedInOverallScore]="exercise.includedInOverallScore" />
}</span>
</h3>
<jhi-included-in-score-badge
[includedInOverallScore]="exercise.includedInOverallScore" />
}</span>
</h3>
<jhi-exercise-save-button [submission]="studentSubmission" (save)="notifyTriggerSave()" />
</div>
<hr />

<jhi-resizeable-container class="col-12" [examTimeline]="examTimeline">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, ViewChild, input, output } from '@angular/core';
import { UMLModel } from '@ls1intum/apollon';
import dayjs from 'dayjs/esm';
import { ModelingSubmission } from 'app/entities/modeling-submission.model';
Expand Down Expand Up @@ -34,6 +34,11 @@ export class ModelingExamSubmissionComponent extends ExamSubmissionComponent imp
exercise: ModelingExercise;
umlModel: UMLModel; // input model for Apollon+

// explicitly needed to track if submission.isSynced is changed, otherwise component
// does not update the state due to onPush strategy
isSubmissionSynced = input<boolean>();
saveCurrentExercise = output<void>();

edkaya marked this conversation as resolved.
Show resolved Hide resolved
explanationText: string; // current explanation text

readonly IncludedInOverallScore = IncludedInOverallScore;
Expand Down Expand Up @@ -154,4 +159,11 @@ export class ModelingExamSubmissionComponent extends ExamSubmissionComponent imp
this.changeDetectorReference.detectChanges();
}
}

/**
* Trigger save action in exam participation component
*/
notifyTriggerSave() {
this.saveCurrentExercise.emit();
}
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
<h3 class="text-align-left fw-normal">
<span>
{{ quizConfiguration.exerciseGroup?.title }}
</span>
<span
>({{ quizConfiguration.maxPoints }} {{ 'artemisApp.examParticipation.points' | artemisTranslate }})
@if (quizConfiguration.includedInOverallScore !== IncludedInOverallScore.INCLUDED_COMPLETELY) {
<jhi-included-in-score-badge [includedInOverallScore]="quizConfiguration.includedInOverallScore" />
}
</span>
</h3>
<div class="d-flex justify-content-between align-items-center">
<h3 class="text-align-left fw-normal mb-0">
<span>
{{ quizConfiguration.exerciseGroup?.title }}
</span>
<span
>({{ quizConfiguration.maxPoints }} {{ 'artemisApp.examParticipation.points' | artemisTranslate }})
@if (quizConfiguration.includedInOverallScore !== IncludedInOverallScore.INCLUDED_COMPLETELY) {
edkaya marked this conversation as resolved.
Show resolved Hide resolved
<jhi-included-in-score-badge [includedInOverallScore]="quizConfiguration.includedInOverallScore" />
}
</span>
</h3>
<jhi-exercise-save-button [submission]="studentSubmission" (save)="notifyTriggerSave()" />
</div>
<hr />

<div class="w-auto m-0 pb-5">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, Input, OnInit, QueryList, ViewChildren } from '@angular/core';
import { ChangeDetectorRef, Component, Input, OnInit, QueryList, ViewChildren, output } from '@angular/core';
import { Exercise, ExerciseType, IncludedInOverallScore } from 'app/entities/exercise.model';
edkaya marked this conversation as resolved.
Show resolved Hide resolved
import { AbstractQuizSubmission } from 'app/entities/quiz/abstract-quiz-exam-submission.model';
import { AnswerOption } from 'app/entities/quiz/answer-option.model';
Expand Down Expand Up @@ -53,6 +53,8 @@ export class QuizExamSubmissionComponent extends ExamSubmissionComponent impleme
@Input() examTimeline = false;
@Input() quizConfiguration: QuizConfiguration;

saveCurrentExercise = output<void>();

edkaya marked this conversation as resolved.
Show resolved Hide resolved
selectedAnswerOptions = new Map<number, AnswerOption[]>();
dragAndDropMappings = new Map<number, DragAndDropMapping[]>();
shortAnswerSubmittedTexts = new Map<number, ShortAnswerSubmittedText[]>();
Expand Down Expand Up @@ -285,4 +287,11 @@ export class QuizExamSubmissionComponent extends ExamSubmissionComponent impleme
this.submissionVersion = submissionVersion;
this.updateViewFromSubmissionVersion();
}

/**
* Trigger save action in exam participation component
*/
notifyTriggerSave() {
this.saveCurrentExercise.emit();
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
@if (exercise) {
<h3 class="text-align-left fw-normal">
<span>
{{ exercise.exerciseGroup?.title }}
</span>
<!-- prettier-ignore -->
<span left-header>&nbsp;({{ exercise.maxPoints }} {{ 'artemisApp.examParticipation.points' | artemisTranslate }}@if (exercise.bonusPoints) {
<span
>, {{ exercise.bonusPoints }} {{ 'artemisApp.examParticipation.bonus' | artemisTranslate }}</span>
<div class="d-flex justify-content-between align-items-center">
<h3 class="text-align-left fw-normal mb-0">
<span>
{{ exercise.exerciseGroup?.title }}
</span>
<!-- prettier-ignore -->
<span left-header>&nbsp;({{ exercise.maxPoints }} {{ 'artemisApp.examParticipation.points' | artemisTranslate }}@if (exercise.bonusPoints) {
<span
>, {{ exercise.bonusPoints }} {{ 'artemisApp.examParticipation.bonus' | artemisTranslate }}</span>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this is not part of your PR, but could you try to remove the <!-- prettier-ignore -->, the odd formatting, and the use of &nbsp;? The same applies to the modeling-exam-submission.component.ts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We tried with setting preserveWhitespace to false and reformatting the html file, didn't work.

}) @if (exercise.includedInOverallScore !== IncludedInOverallScore.INCLUDED_COMPLETELY) {
<jhi-included-in-score-badge
[includedInOverallScore]="exercise.includedInOverallScore" />
}</span>
</h3>
<jhi-included-in-score-badge
[includedInOverallScore]="exercise.includedInOverallScore" />
}</span>
</h3>
<jhi-exercise-save-button [submission]="studentSubmission" (save)="notifyTriggerSave()" />
</div>
<hr />
<!--resizable container-->
<jhi-resizeable-container class="col-12" [examTimeline]="examTimeline">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, Input, OnInit, output } from '@angular/core';
import { TextEditorService } from 'app/exercises/text/participate/text-editor.service';
import { Subject } from 'rxjs';
import { TextSubmission } from 'app/entities/text/text-submission.model';
import { StringCountService } from 'app/exercises/text/participate/string-count.service';
import { Exercise, ExerciseType, IncludedInOverallScore } from 'app/entities/exercise.model';
import { ExamSubmissionComponent } from 'app/exam/participate/exercises/exam-submission.component';
import { Submission } from 'app/entities/submission.model';
import { faListAlt } from '@fortawesome/free-regular-svg-icons';
import { faListAlt } from '@fortawesome/free-solid-svg-icons';
import { MAX_SUBMISSION_TEXT_LENGTH } from 'app/shared/constants/input.constants';
import { SubmissionVersion } from 'app/entities/submission-version.model';
import { htmlForMarkdown } from 'app/shared/util/markdown.conversion.util';
Expand All @@ -26,6 +26,8 @@ export class TextExamSubmissionComponent extends ExamSubmissionComponent impleme
@Input()
exercise: Exercise;

saveCurrentExercise = output<void>();

readonly IncludedInOverallScore = IncludedInOverallScore;
readonly maxCharacterCount = MAX_SUBMISSION_TEXT_LENGTH;

Expand All @@ -35,7 +37,7 @@ export class TextExamSubmissionComponent extends ExamSubmissionComponent impleme
private textEditorInput = new Subject<string>();

// Icons
edkaya marked this conversation as resolved.
Show resolved Hide resolved
farListAlt = faListAlt;
readonly farListAlt = faListAlt;

edkaya marked this conversation as resolved.
Show resolved Hide resolved
constructor(
private textService: TextEditorService,
Expand Down Expand Up @@ -121,4 +123,11 @@ export class TextExamSubmissionComponent extends ExamSubmissionComponent impleme
this.submissionVersion = submissionVersion;
this.updateViewFromSubmissionVersion();
}

/**
* Trigger save action in exam participation component
*/
notifyTriggerSave() {
this.saveCurrentExercise.emit();
}
}
4 changes: 3 additions & 1 deletion src/main/webapp/i18n/de/exam.json
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@
"noActionRequired": "Artemis erfordert keine weitere Aktion, das Fenster kann geschlossen werden.",
"followExamProtocol": "Halte dich an die Klausuranweisungen deiner Lehrenden.",
"button": "Zusammenfassung der Klausur anzeigen{{ countdown }}"
}
},
"saveExercise": "Aufgabe Speichern",
"exerciseSaved": "Aufgabe Gespeichert"
edkaya marked this conversation as resolved.
Show resolved Hide resolved
edkaya marked this conversation as resolved.
Show resolved Hide resolved
},
"exerciseGroup": {
"created": "Neue Aufgabengruppe erstellt",
Expand Down
4 changes: 3 additions & 1 deletion src/main/webapp/i18n/en/exam.json
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@
"noActionRequired": "Artemis does not require any further action and this window can be closed.",
"followExamProtocol": "Be sure to follow your instructor's exam protocol.",
"button": "Show exam summary{{countdown}}"
}
},
"saveExercise": "Save Exercise",
"exerciseSaved": "Exercise Saved"
edkaya marked this conversation as resolved.
Show resolved Hide resolved
},
"exerciseGroup": {
"created": "New exercise group created",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { IncludedInScoreBadgeComponent } from 'app/exercises/shared/exercise-hea
import { ExamExerciseUpdateHighlighterComponent } from 'app/exam/participate/exercises/exam-exercise-update-highlighter/exam-exercise-update-highlighter.component';
import { NgbTooltipMocksModule } from '../../../../helpers/mocks/directive/ngbTooltipMocks.module';
import { SubmissionVersion } from 'app/entities/submission-version.model';
import { ExerciseSaveButtonComponent } from 'app/exam/participate/exercises/exercise-save-button/exercise-save-button.component';

describe('ModelingExamSubmissionComponent', () => {
let fixture: ComponentFixture<ModelingExamSubmissionComponent>;
Expand Down Expand Up @@ -49,6 +50,7 @@ describe('ModelingExamSubmissionComponent', () => {
MockPipe(HtmlForMarkdownPipe, (markdown) => markdown as SafeHtml),
MockComponent(IncludedInScoreBadgeComponent),
MockComponent(ExamExerciseUpdateHighlighterComponent),
MockComponent(ExerciseSaveButtonComponent),
],
providers: [MockProvider(ChangeDetectorRef)],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ModelingSubmission } from 'app/entities/modeling-submission.model';
import { QuizExercise } from 'app/entities/quiz/quiz-exercise.model';
import { Course } from 'app/entities/course.model';
import { provideRouter } from '@angular/router';
import { ExerciseSaveButtonComponent } from 'app/exam/participate/exercises/exercise-save-button/exercise-save-button.component';

describe('QuizExamSubmissionComponent', () => {
let fixture: ComponentFixture<QuizExamSubmissionComponent>;
Expand Down Expand Up @@ -55,6 +56,7 @@ describe('QuizExamSubmissionComponent', () => {
MockComponent(MultipleChoiceQuestionComponent),
MockComponent(DragAndDropQuestionComponent),
MockComponent(ShortAnswerQuestionComponent),
MockComponent(ExerciseSaveButtonComponent),
],
providers: [provideRouter([]), MockProvider(ArtemisQuizService)],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ArtemisTestModule } from '../../../../test.module';
import { ResizeableContainerComponent } from 'app/shared/resizeable-container/resizeable-container.component';
import dayjs from 'dayjs/esm';
import { TranslateDirective } from 'app/shared/language/translate.directive';
import { ExerciseSaveButtonComponent } from 'app/exam/participate/exercises/exercise-save-button/exercise-save-button.component';

describe('TextExamSubmissionComponent', () => {
let fixture: ComponentFixture<TextExamSubmissionComponent>;
Expand All @@ -39,6 +40,7 @@ describe('TextExamSubmissionComponent', () => {
MockComponent(IncludedInScoreBadgeComponent),
MockComponent(ExamExerciseUpdateHighlighterComponent),
MockComponent(ResizeableContainerComponent),
MockComponent(ExerciseSaveButtonComponent),
edkaya marked this conversation as resolved.
Show resolved Hide resolved
MockDirective(TranslateDirective),
],
providers: [MockProvider(TextEditorService), MockProvider(ArtemisMarkdownService)],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ test.describe('Exam date verification', () => {
await examNavigation.openOrSaveExerciseByTitle(exercise.exerciseGroup!.title!);

await page.hover('.fa-save-success');
await expect(page.getByText('Exercise saved')).toBeVisible();
await expect(page.getByText('Exercise saved', { exact: true })).toBeVisible();
edkaya marked this conversation as resolved.
Show resolved Hide resolved
});

test('Exam ends after end time', async ({ page, login, examAPIRequests, exerciseAPIRequests, examStartEnd, examNavigation, textExerciseEditor, examParticipation }) => {
Expand Down
Loading