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

Plagiarism checks: Enhance navigation to plagiarism cases from detection page #10078

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ <h4 jhiTranslate="artemisApp.plagiarism.cases.pageSubtitle"></h4>
</div>
</div>
@for (exercise of exercisesWithPlagiarismCases; track exercise.id; let exerciseIndex = $index) {
<div class="card mb-2">
<div #plagExerciseElement class="card mb-2" [id]="'exercise-with-plagiarism-case-' + exercise.id">
<div class="card-header">
<div class="row">
<div class="col-3">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HttpResponse } from '@angular/common/http';
import { Component, OnInit, inject } from '@angular/core';
import { Component, ElementRef, OnInit, effect, viewChildren, inject } from '@angular/core';

Check failure on line 2 in src/main/webapp/app/course/plagiarism-cases/instructor-view/plagiarism-cases-instructor-view.component.ts

View workflow job for this annotation

GitHub Actions / client-style

Member 'inject' of the import declaration should be sorted alphabetically
import { ActivatedRoute, RouterLink } from '@angular/router';
import { PlagiarismCasesService } from 'app/course/plagiarism-cases/shared/plagiarism-cases.service';
import { PlagiarismCase } from 'app/exercises/shared/plagiarism/types/PlagiarismCase';
Expand Down Expand Up @@ -42,6 +42,8 @@
groupedPlagiarismCases: GroupedPlagiarismCases;
exercisesWithPlagiarismCases: Exercise[] = [];

exerciseWithPlagCasesElements = viewChildren<ElementRef>('plagExerciseElement');

// method called as html template variable, angular only recognises reference variables in html if they are a property
// of the corresponding component class
getExerciseUrlSegment = getExerciseUrlSegment;
Expand All @@ -52,7 +54,6 @@
ngOnInit(): void {
this.courseId = Number(this.route.snapshot.paramMap.get('courseId'));
this.examId = Number(this.route.snapshot.paramMap.get('examId'));

const plagiarismCasesForInstructor$ = this.examId
? this.plagiarismCasesService.getExamPlagiarismCasesForInstructor(this.courseId, this.examId)
: this.plagiarismCasesService.getCoursePlagiarismCasesForInstructor(this.courseId);
Expand All @@ -63,6 +64,28 @@
this.groupedPlagiarismCases = this.getGroupedPlagiarismCasesByExercise(this.plagiarismCases);
},
});
// effect needs to be in constructor context, due to the possibility of ngOnInit being called from a non-injection
//context
effect(() => {
const exerciseId = Number(this.route.snapshot.queryParamMap?.get('exerciseId'));
if (exerciseId) {
this.scrollToExerciseAfterViewInit(exerciseId);
}
});
}

/**
* scroll to the exercise with
*/
scrollToExerciseAfterViewInit(exerciseId: number) {
const element = this.exerciseWithPlagCasesElements().find((elem) => elem.nativeElement.id === 'exercise-with-plagiarism-case-' + exerciseId);
if (element) {
element.nativeElement.scrollIntoView({
behavior: 'smooth',
block: 'start',
inline: 'nearest',
});
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,31 @@ <h5 class="fw-medium">
</div>

<div class="plagiarism-header-right">
<button
[disabled]="comparison.status === plagiarismStatus.CONFIRMED || isLoading || exercise.teamMode"
class="btn btn-success btn-sm"
(click)="confirmPlagiarism()"
data-qa="confirm-plagiarism-button"
jhiTranslate="artemisApp.plagiarism.confirm"
></button>
@if (comparison.status === plagiarismStatus.CONFIRMED) {
<button
class="btn btn-primary btn-sm"
data-qa="view-plagiarism-cases-button"
jhiTranslate="artemisApp.plagiarism.viewCases"
[routerLink]="['/course-management', exercise.course?.id, 'plagiarism-cases']"
[queryParams]="{ exerciseId: exercise.id }"
[disabled]="isLoading || exercise.teamMode"
></button>
} @else {
<button
class="btn btn-success btn-sm"
(click)="confirmPlagiarism()"
data-qa="confirm-plagiarism-button"
jhiTranslate="artemisApp.plagiarism.confirm"
[disabled]="isLoading || exercise.teamMode"
></button>
}

<button
[disabled]="comparison.status === plagiarismStatus.DENIED || isLoading || exercise.teamMode"
class="btn btn-danger btn-sm"
(click)="denyPlagiarism()"
data-qa="deny-plagiarism-button"
jhiTranslate="artemisApp.plagiarism.deny"
[disabled]="comparison.status === plagiarismStatus.DENIED || isLoading || exercise.teamMode"
></button>

<div class="vertical-divider"></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Exercise, getCourseId } from 'app/entities/exercise.model';
import { TranslateDirective } from 'app/shared/language/translate.directive';
import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
import { RouterModule } from '@angular/router';

@Component({
selector: 'jhi-plagiarism-header',
styleUrls: ['./plagiarism-header.component.scss'],
templateUrl: './plagiarism-header.component.html',
imports: [TranslateDirective, ArtemisTranslatePipe],
imports: [TranslateDirective, ArtemisTranslatePipe, RouterModule],
})
export class PlagiarismHeaderComponent {
private plagiarismCasesService = inject(PlagiarismCasesService);
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/de/plagiarism.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"plagiarism": {
"plagiarismDetection": "Plagiatskontrolle",
"confirm": "Bestätigen",
"viewCases": "Fälle ansehen",
"deny": "Ablehnen",
"denyAfterConfirmModalTitle": "Wechsel von Bestätigen zu Ablehnen",
"denyAfterConfirmModalText": "Bist du dir sicher, dass du die Entscheidung von \"Bestätigung des Plagiats\" in \"Ablehnung\" ändern möchtest? Dadurch wird der entsprechende Plagiatsfall einschließlich der Kommunikation mit dem/der Studierenden und des Urteils gelöscht und kann nicht rückgängig gemacht werden.",
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/en/plagiarism.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"plagiarism": {
"plagiarismDetection": "Plagiarism Detection",
"confirm": "Confirm",
"viewCases": "View Case(s)",
"deny": "Deny",
"denyAfterConfirmModalTitle": "Change from confirm to deny",
"denyAfterConfirmModalText": "Are you sure that you want to change the decision from confirming the plagiarism to denying it? This will delete the corresponding plagiarism case incl. the communication with the student and the verdict and cannot be undone.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
import { ProgressBarComponent } from 'app/shared/dashboards/tutor-participation-graph/progress-bar/progress-bar.component';
import { PlagiarismCaseVerdictComponent } from 'app/course/plagiarism-cases/shared/verdict/plagiarism-case-verdict.component';
import { MockNotificationService } from '../../helpers/mocks/service/mock-notification.service';
import { Component } from '@angular/core';
import { ElementRef, signal, Component } from '@angular/core';

Check failure on line 25 in src/test/javascript/spec/component/plagiarism/plagiarism-cases-instructor-view.component.spec.ts

View workflow job for this annotation

GitHub Actions / client-style

Member 'Component' of the import declaration should be sorted alphabetically
import { Location } from '@angular/common';


Check failure on line 28 in src/test/javascript/spec/component/plagiarism/plagiarism-cases-instructor-view.component.spec.ts

View workflow job for this annotation

GitHub Actions / client-style

Delete `⏎`
@Component({ template: '' })
class DummyComponent {}

Expand Down Expand Up @@ -251,4 +252,23 @@
tick();
expect(location.path()).toBe(`/course-management/${courseId}/${exercise1.type}-exercises/${exerciseId}/plagiarism`);
}));

it('should scroll to the correct exercise element when scrollToExercise is called', () => {
const nativeElement1 = { id: 'exercise-with-plagiarism-case-1', scrollIntoView: jest.fn() };
const nativeElement2 = { id: 'exercise-with-plagiarism-case-2', scrollIntoView: jest.fn() };

const elementRef1 = new ElementRef(nativeElement1);
const elementRef2 = new ElementRef(nativeElement2);

component.exerciseWithPlagCasesElements = signal([elementRef1, elementRef2]);

component.scrollToExerciseAfterViewInit(1);

expect(nativeElement1.scrollIntoView).toHaveBeenCalledWith({
behavior: 'smooth',
block: 'start',
inline: 'nearest',
});
expect(nativeElement2.scrollIntoView).not.toHaveBeenCalled();
});
});
Loading