Skip to content

Commit

Permalink
Text exercises: Highlight selected result in result history timeline (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
EneaGore authored Jan 4, 2025
1 parent 4e56238 commit ec8e010
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div [id]="'exercise-' + textExercise?.id">
@if (displayHeader) {
@if (displayHeader()) {
<jhi-header-participation-page class="d-block mb-4" [exercise]="textExercise" [participation]="participation">
<span pagetitle>
{{ 'artemisApp.textSubmission.textEditor' | artemisTranslate }}:
Expand Down Expand Up @@ -54,7 +54,7 @@
@if (isReadOnlyWithShowResult) {
@if (showHistory) {
<div id="result-history" class="row mb-2">
<jhi-result-history [results]="sortedHistoryResults" [exercise]="textExercise" />
<jhi-result-history [results]="sortedHistoryResults" [exercise]="textExercise" [selectedResultId]="submission.latestResult?.id!" />
</div>
}
}
Expand All @@ -64,7 +64,7 @@
@if (textExercise) {
<jhi-resizeable-container
[isExerciseParticipation]="true"
[expandProblemStatement]="expandProblemStatement && !isReadOnlyWithShowResult"
[expandProblemStatement]="expandProblemStatement() && !isReadOnlyWithShowResult"
[collapsed]="isReadOnlyWithShowResult"
>
<!--region Left Panel-->
Expand All @@ -79,15 +79,15 @@
<span
class="badge bg-primary mb-2"
id="word-count"
[hidden]="submission && !submission.submitted && isExamSummary"
[hidden]="submission && !submission.submitted && isExamSummary()"
jhiTranslate="artemisApp.textExercise.wordCount"
[translateValues]="{ count: wordCount }"
>
</span>
<span
class="badge bg-primary mb-2"
id="character-count"
[hidden]="submission && !submission.submitted && isExamSummary"
[hidden]="submission && !submission.submitted && isExamSummary()"
jhiTranslate="artemisApp.textExercise.characterCount"
[translateValues]="{ count: characterCount }"
>
Expand All @@ -104,7 +104,7 @@
[disabled]="!isActive || !submission || !isOwnerOfParticipation"
(keydown.tab)="onTextEditorTab(textEditor, $event)"
(input)="onTextEditorInput($event)"
[hidden]="submission && !submission.submitted && isExamSummary"
[hidden]="submission && !submission.submitted && isExamSummary()"
></textarea>
@if (textExercise?.teamMode) {
<jhi-team-submission-sync
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input, OnDestroy, OnInit, inject } from '@angular/core';
import { Component, OnDestroy, OnInit, inject, input } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http';
import { AlertService } from 'app/core/util/alert.service';
Expand Down Expand Up @@ -58,14 +58,13 @@ export class TextEditorComponent implements OnInit, OnDestroy, ComponentCanDeact
readonly ChatServiceMode = ChatServiceMode;
protected readonly isAthenaAIResult = isAthenaAIResult;

@Input() participationId?: number;
@Input() displayHeader: boolean = true;
@Input() expandProblemStatement?: boolean = true;

@Input() inputExercise?: TextExercise;
@Input() inputSubmission?: TextSubmission;
@Input() inputParticipation?: StudentParticipation;
@Input() isExamSummary = false;
participationId = input<number>();
displayHeader = input<boolean>(true);
expandProblemStatement = input<boolean | undefined>(true);
inputExercise = input<TextExercise>();
inputSubmission = input<TextSubmission>();
inputParticipation = input<StudentParticipation>();
isExamSummary = input<boolean>(false);

textExercise: TextExercise;
participation: StudentParticipation;
Expand Down Expand Up @@ -109,7 +108,7 @@ export class TextEditorComponent implements OnInit, OnDestroy, ComponentCanDeact
if (this.inputValuesArePresent()) {
this.setupComponentWithInputValues();
} else {
const participationId = this.participationId !== undefined ? this.participationId : Number(this.route.snapshot.paramMap.get('participationId'));
const participationId = this.participationId() !== undefined ? this.participationId() : Number(this.route.snapshot.paramMap.get('participationId'));
this.submissionId = Number(this.route.snapshot.paramMap.get('submissionId')) || undefined;

if (Number.isNaN(participationId)) {
Expand All @@ -121,7 +120,7 @@ export class TextEditorComponent implements OnInit, OnDestroy, ComponentCanDeact
this.updateParticipation(this.participation, this.submissionId);
});

this.textService.get(participationId).subscribe({
this.textService.get(participationId!).subscribe({
next: (data: StudentParticipation) => this.updateParticipation(data, this.submissionId),
error: (error: HttpErrorResponse) => onError(this.alertService, error),
});
Expand Down Expand Up @@ -164,7 +163,7 @@ export class TextEditorComponent implements OnInit, OnDestroy, ComponentCanDeact
}

private inputValuesArePresent(): boolean {
return !!(this.inputExercise || this.inputSubmission || this.inputParticipation);
return !!(this.inputExercise() || this.inputSubmission() || this.inputParticipation());
}

/**
Expand All @@ -175,14 +174,14 @@ export class TextEditorComponent implements OnInit, OnDestroy, ComponentCanDeact
* @private
*/
private setupComponentWithInputValues() {
if (this.inputExercise) {
this.textExercise = this.inputExercise;
if (this.inputExercise() !== undefined) {
this.textExercise = this.inputExercise()!;
}
if (this.inputSubmission) {
this.submission = this.inputSubmission;
if (this.inputSubmission() !== undefined) {
this.submission = this.inputSubmission()!;
}
if (this.inputParticipation) {
this.participation = this.inputParticipation;
if (this.inputParticipation() !== undefined) {
this.participation = this.inputParticipation()!;
}

if (this.submission?.text) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
@for (result of displayedResults; track result; let i = $index) {
<div class="result-history-element">
<div class="result-score">
<div class="result-score-icon" [ngClass]="getTextColorClass(result, evaluateTemplateStatus(exercise, result.participation, result, false, MissingResultInfo.NONE))">
<fa-icon [icon]="getResultIconClass(result, evaluateTemplateStatus(exercise, result.participation, result, false, MissingResultInfo.NONE))" size="xl" />
<div
class="result-score-icon"
[ngClass]="
result.id === selectedResultId()
? 'text-primary'
: getTextColorClass(result, evaluateTemplateStatus(exercise(), result.participation, result, false, MissingResultInfo.NONE))
"
>
<fa-icon [icon]="getResultIconClass(result, evaluateTemplateStatus(exercise(), result.participation, result, false, MissingResultInfo.NONE))" size="xl" />
</div>
<jhi-result
class="result-score-info text-center"
[exercise]="exercise"
[exercise]="exercise()"
[result]="result"
[participation]="result.participation!"
[showUngradedResults]="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input, OnChanges } from '@angular/core';
import { Component, OnChanges, input } from '@angular/core';
import { Result } from 'app/entities/result.model';
import { Exercise, ExerciseType } from 'app/entities/exercise.model';
import { MissingResultInformation, evaluateTemplateStatus, getResultIconClass, getTextColorClass } from 'app/exercises/shared/result/result.utils';
Expand All @@ -17,24 +17,27 @@ export class ResultHistoryComponent implements OnChanges {
readonly evaluateTemplateStatus = evaluateTemplateStatus;
readonly MissingResultInfo = MissingResultInformation;

@Input() results: Result[];
@Input() exercise: Exercise;
results = input.required<Result[]>();
exercise = input<Exercise>();
selectedResultId = input<number>();

showPreviousDivider = false;
displayedResults: Result[];
movedLastRatedResult: boolean;

ngOnChanges(): void {
this.showPreviousDivider = this.results.length > MAX_RESULT_HISTORY_LENGTH;
if (this.exercise?.type === ExerciseType.TEXT || this.exercise?.type === ExerciseType.MODELING) {
this.displayedResults = this.results.filter((result) => result.successful !== undefined);
this.showPreviousDivider = this.results().length > MAX_RESULT_HISTORY_LENGTH;
if (this.exercise()?.type === ExerciseType.TEXT || this.exercise()?.type === ExerciseType.MODELING) {
this.displayedResults = this.results().filter((result) => result.successful !== undefined);
} else {
this.displayedResults = this.results;
this.displayedResults = this.results();
}
const successfulResultsLength = this.displayedResults.length;
if (successfulResultsLength > MAX_RESULT_HISTORY_LENGTH) {
this.displayedResults = this.displayedResults.slice(successfulResultsLength - MAX_RESULT_HISTORY_LENGTH);
const lastRatedResult = this.results.filter((result) => result.rated).last();
const lastRatedResult = this.results()
.filter((result) => result.rated)
.last();
if (!this.displayedResults.first()?.rated && lastRatedResult) {
this.displayedResults[0] = lastRatedResult;
this.movedLastRatedResult = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { input, runInInjectionContext } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ResultHistoryComponent } from 'app/overview/result-history/result-history.component';
import { MockPipe } from 'ng-mocks';
import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe';
import { ArtemisTestModule } from '../../../test.module';
import { Result } from 'app/entities/result.model';

describe('ResultHistoryComponent', () => {
let component: ResultHistoryComponent;
Expand All @@ -25,11 +27,13 @@ describe('ResultHistoryComponent', () => {
});

it('should initialize with same rated results', () => {
component.results = [
{ rated: true, id: 1 },
{ rated: true, id: 2 },
{ rated: true, id: 3 },
];
runInInjectionContext(TestBed, () => {
component.results = input<Result[]>([
{ rated: true, id: 1 },
{ rated: true, id: 2 },
{ rated: true, id: 3 },
]);
});
component.ngOnChanges();
expect(component.displayedResults).toEqual([
{ rated: true, id: 1 },
Expand All @@ -39,14 +43,16 @@ describe('ResultHistoryComponent', () => {
expect(component.showPreviousDivider).toBeFalse();
expect(component.movedLastRatedResult).toBeFalsy();

component.results = [
{ rated: false, id: 1 },
{ rated: false, id: 2 },
{ rated: false, id: 3 },
{ rated: false, id: 4 },
{ rated: false, id: 5 },
{ rated: false, id: 6 },
];
runInInjectionContext(TestBed, () => {
component.results = input<Result[]>([
{ rated: false, id: 1 },
{ rated: false, id: 2 },
{ rated: false, id: 3 },
{ rated: false, id: 4 },
{ rated: false, id: 5 },
{ rated: false, id: 6 },
]);
});
component.ngOnChanges();
expect(component.displayedResults).toEqual([
{ rated: false, id: 2 },
Expand All @@ -60,11 +66,13 @@ describe('ResultHistoryComponent', () => {
});

it('should initialize with mixed rated results', () => {
component.results = [
{ rated: true, id: 1 },
{ rated: false, id: 2 },
{ rated: false, id: 3 },
];
runInInjectionContext(TestBed, () => {
component.results = input<Result[]>([
{ rated: true, id: 1 },
{ rated: false, id: 2 },
{ rated: false, id: 3 },
]);
});
component.ngOnChanges();
expect(component.displayedResults).toEqual([
{ rated: true, id: 1 },
Expand All @@ -74,14 +82,16 @@ describe('ResultHistoryComponent', () => {
expect(component.showPreviousDivider).toBeFalse();
expect(component.movedLastRatedResult).toBeFalsy();

component.results = [
{ rated: true, id: 1 },
{ rated: false, id: 2 },
{ rated: false, id: 3 },
{ rated: false, id: 4 },
{ rated: false, id: 5 },
{ rated: false, id: 6 },
];
runInInjectionContext(TestBed, () => {
component.results = input<Result[]>([
{ rated: true, id: 1 },
{ rated: false, id: 2 },
{ rated: false, id: 3 },
{ rated: false, id: 4 },
{ rated: false, id: 5 },
{ rated: false, id: 6 },
]);
});
component.ngOnChanges();
expect(component.displayedResults).toEqual([
{ rated: true, id: 1 },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DebugElement } from '@angular/core';
import { DebugElement, input, runInInjectionContext } from '@angular/core';
import dayjs from 'dayjs/esm';
import { ActivatedRoute, RouterModule, convertToParamMap } from '@angular/router';
import { ComponentFixture, TestBed, fakeAsync, flush, tick } from '@angular/core/testing';
Expand Down Expand Up @@ -110,9 +110,11 @@ describe('TextEditorComponent', () => {
});

it('should use inputValues if present instead of loading new details', fakeAsync(() => {
comp.inputExercise = textExercise;
comp.inputParticipation = participation;
comp.inputSubmission = { id: 1, text: 'test' };
runInInjectionContext(TestBed, () => {
comp.inputExercise = input<TextExercise>(textExercise);
comp.inputParticipation = input<StudentParticipation>(participation);
comp.inputSubmission = input<TextSubmission>({ id: 1, text: 'test' });
});
// @ts-ignore updateParticipation is private
const updateParticipationSpy = jest.spyOn(comp, 'updateParticipation');
// @ts-ignore setupComponentWithInputValuesSpy is private
Expand Down

0 comments on commit ec8e010

Please sign in to comment.