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: Improve file selection in comparison #9789

Merged
merged 47 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
346499e
add ui for toggling and translation for labels
AjayvirS Nov 14, 2024
9d7a69f
add working implementation
AjayvirS Nov 15, 2024
6dedb68
add code quality changes and comments
AjayvirS Nov 15, 2024
0d37874
fix failing test
AjayvirS Nov 15, 2024
e8d46fb
attempt fix tests
AjayvirS Nov 18, 2024
f02813f
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
AjayvirS Nov 18, 2024
7f268d2
attempt fix more tests
AjayvirS Nov 18, 2024
bbdb814
Merge remote-tracking branch 'origin/feature/plagiarism-checks/same-f…
AjayvirS Nov 18, 2024
efb80bd
fix last failing test
AjayvirS Nov 18, 2024
55c5a4e
add mock translate directive to fix test
AjayvirS Nov 18, 2024
f573a95
add coderabbit suggestions
AjayvirS Nov 18, 2024
8206899
add tests for feature
AjayvirS Nov 18, 2024
68b65ec
fix test
AjayvirS Nov 18, 2024
a106166
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
AjayvirS Nov 18, 2024
52d28fb
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
AjayvirS Nov 20, 2024
f0e0bfd
adapt functionality to handle cases where files are not identically n…
AjayvirS Nov 22, 2024
f5b3748
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
AjayvirS Nov 22, 2024
9c88a73
Merge branch 'feature/plagiarism-checks/same-file-comparison-view' of…
AjayvirS Nov 26, 2024
e6cef0a
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
AjayvirS Nov 26, 2024
14fbd0a
fix minor indexing and add tests for coverage
AjayvirS Nov 27, 2024
6976196
xMerge branch 'develop' into feature/plagiarism-checks/same-file-comp…
AjayvirS Nov 27, 2024
ac6c46a
adapt split pane header to new client code guidelines and adapt tests
AjayvirS Nov 28, 2024
b2272f9
fix test
AjayvirS Nov 28, 2024
613dc23
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
AjayvirS Nov 28, 2024
24bc889
fix test
AjayvirS Nov 28, 2024
dfc8c28
fix test
AjayvirS Nov 28, 2024
16325a8
Merge remote-tracking branch 'origin/feature/plagiarism-checks/same-f…
AjayvirS Nov 28, 2024
aa7b290
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
AjayvirS Dec 3, 2024
6c2a4c4
ramona fixes
AjayvirS Dec 3, 2024
ea194cb
ramona fixes
AjayvirS Dec 3, 2024
a0b53fa
ramona fixes
AjayvirS Dec 3, 2024
d7a636a
Merge branch 'feature/plagiarism-checks/same-file-comparison-view' of…
AjayvirS Dec 3, 2024
38910db
ramona fixes
AjayvirS Dec 3, 2024
442d81f
ramona fix tests
AjayvirS Dec 3, 2024
3f4a980
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
MarkusPaulsen Dec 10, 2024
cd3ede4
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
MarkusPaulsen Dec 10, 2024
1d6b25c
ramona changes
AjayvirS Dec 10, 2024
596e0a6
Merge remote-tracking branch 'origin/feature/plagiarism-checks/same-f…
AjayvirS Dec 10, 2024
8e9a155
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
AjayvirS Dec 10, 2024
993b38b
ramona changes
AjayvirS Dec 10, 2024
9973ade
Merge remote-tracking branch 'origin/feature/plagiarism-checks/same-f…
AjayvirS Dec 10, 2024
8aa0c0f
remove non null assertion
AjayvirS Dec 11, 2024
f53e92a
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
sachmii Dec 11, 2024
38b0505
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
AjayvirS Dec 11, 2024
5a95c27
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
AjayvirS Dec 17, 2024
e7a7a75
add auto close dropdown on mouseleave for better usability
AjayvirS Dec 17, 2024
a85d119
Merge branch 'develop' into feature/plagiarism-checks/same-file-compa…
Strohgelaender Dec 19, 2024
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
@@ -1,6 +1,9 @@
@if (comparison) {
<div class="plagiarism-details">
<jhi-plagiarism-header [splitControlSubject]="splitControlSubject" [exercise]="exercise" [comparison]="comparison" />
<jhi-plagiarism-split-view [comparison]="comparison" [exercise]="exercise" [splitControlSubject]="splitControlSubject" />
<jhi-plagiarism-header [splitControlSubject]="splitControlSubject" [exercise]="exercise" [comparison]="comparison" (isLockFilesEnabledChange)="isLockFilesEnabled = $event">
</jhi-plagiarism-header>

<jhi-plagiarism-split-view [comparison]="comparison" [exercise]="exercise" [splitControlSubject]="splitControlSubject" [isLockFilesEnabled]="isLockFilesEnabled">
</jhi-plagiarism-split-view>
</div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ export class PlagiarismDetailsComponent {
* Subject to be passed into PlagiarismSplitViewComponent to control the split view.
*/
splitControlSubject: Subject<string> = new Subject<string>();

/**
* Boolean to determine whether file locking is enabled.
*/
isLockFilesEnabled: boolean = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ <h5 class="fw-medium">
jhiTranslate="artemisApp.plagiarism.deny"
></button>

<button class="btn btn-outline-secondary btn-sm" (click)="toggleLockFiles()" [class.active]="isLockFilesEnabled" data-qa="lock-files-toggle-button">
<fa-icon [icon]="isLockFilesEnabled ? faLock : faUnlock" class="fa-solid m-2" aria-label="Synchronize files while navigating on both sides"> </fa-icon>
<span [jhiTranslate]="isLockFilesEnabled ? 'artemisApp.plagiarism.unlockSync' : 'artemisApp.plagiarism.lockSync'"></span>
</button>

<div class="vertical-divider"></div>

<div class="split-controls">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Subject } from 'rxjs';
import { PlagiarismStatus } from 'app/exercises/shared/plagiarism/types/PlagiarismStatus';
import { PlagiarismComparison } from 'app/exercises/shared/plagiarism/types/PlagiarismComparison';
Expand All @@ -8,6 +8,7 @@ import { PlagiarismCasesService } from 'app/course/plagiarism-cases/shared/plagi
import { ConfirmAutofocusModalComponent } from 'app/shared/components/confirm-autofocus-modal.component';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Exercise, getCourseId } from 'app/entities/exercise.model';
import { IconDefinition, faLock, faUnlock } from '@fortawesome/free-solid-svg-icons';

@Component({
selector: 'jhi-plagiarism-header',
Expand All @@ -19,8 +20,13 @@ export class PlagiarismHeaderComponent {
@Input() exercise: Exercise;
@Input() splitControlSubject: Subject<string>;

@Output() isLockFilesEnabledChange = new EventEmitter<boolean>();

protected readonly faLock: IconDefinition = faLock;
protected readonly faUnlock: IconDefinition = faUnlock;
readonly plagiarismStatus = PlagiarismStatus;
isLoading = false;
isLockFilesEnabled = false;

constructor(
private plagiarismCasesService: PlagiarismCasesService,
Expand Down Expand Up @@ -76,4 +82,12 @@ export class PlagiarismHeaderComponent {
resetSplitPanes() {
this.splitControlSubject.next('even');
}

/**
* Toggles the state of file locking and emits the new state to the parent component.
*/
toggleLockFiles() {
this.isLockFilesEnabled = !this.isLockFilesEnabled;
this.isLockFilesEnabledChange.emit(this.isLockFilesEnabled);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@
<jhi-modeling-submission-viewer [exercise]="exercise" [plagiarismSubmission]="getModelingSubmissionA()" [hideContent]="false" />
}
@if (isProgrammingOrTextExercise) {
<jhi-text-submission-viewer [exercise]="exercise" [matches]="matchesA" [plagiarismSubmission]="getTextSubmissionA()" [hideContent]="false" />
<jhi-text-submission-viewer
[fileSelectedSubject]="fileSelectedSubject"
[exercise]="exercise"
[matches]="matchesA"
[plagiarismSubmission]="getTextSubmissionA()"
[isLockFilesEnabled]="isLockFilesEnabled"
[hideContent]="false"
/>
}
}
</div>
Expand All @@ -20,9 +27,11 @@
}
@if (isProgrammingOrTextExercise) {
<jhi-text-submission-viewer
[fileSelectedSubject]="fileSelectedSubject"
[exercise]="exercise"
[matches]="matchesB"
[plagiarismSubmission]="getTextSubmissionB()"
[isLockFilesEnabled]="isLockFilesEnabled"
[hideContent]="forStudent && dayjs(exercise.dueDate).isAfter(dayjs())"
/>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PlagiarismCasesService } from 'app/course/plagiarism-cases/shared/plagi
import { HttpResponse } from '@angular/common/http';
import { SimpleMatch } from 'app/exercises/shared/plagiarism/types/PlagiarismMatch';
import dayjs from 'dayjs/esm';
import { TextPlagiarismFileElement } from 'app/exercises/shared/plagiarism/types/text/TextPlagiarismFileElement';

@Directive({ selector: '[jhiPane]' })
export class SplitPaneDirective {
Expand All @@ -27,10 +28,12 @@ export class PlagiarismSplitViewComponent implements AfterViewInit, OnChanges, O
@Input() splitControlSubject: Subject<string>;
@Input() sortByStudentLogin: string;
@Input() forStudent: boolean;
@Input() isLockFilesEnabled = false;

@ViewChildren(SplitPaneDirective) panes!: QueryList<SplitPaneDirective>;

plagiarismComparison: PlagiarismComparison<TextSubmissionElement | ModelingSubmissionElement>;
fileSelectedSubject = new Subject<TextPlagiarismFileElement>();
AjayvirS marked this conversation as resolved.
Show resolved Hide resolved

public split: Split.Instance;

Expand All @@ -45,7 +48,7 @@ export class PlagiarismSplitViewComponent implements AfterViewInit, OnChanges, O
constructor(private plagiarismCasesService: PlagiarismCasesService) {}

/**
* Initialize third party libs inside this lifecycle hook.
* Initialize third-party libraries inside this lifecycle hook.
*/
ngAfterViewInit(): void {
const paneElements = this.panes.map((pane: SplitPaneDirective) => pane.elementRef.nativeElement);
Expand Down Expand Up @@ -122,7 +125,7 @@ export class PlagiarismSplitViewComponent implements AfterViewInit, OnChanges, O
* @param matches list of objects containing the index and length of matched elements
* @param submission the submission to map the elements of
*/
mapMatchesToElements(matches: SimpleMatch[], submission: PlagiarismSubmission<TextSubmissionElement>) {
public mapMatchesToElements(matches: SimpleMatch[], submission: PlagiarismSubmission<TextSubmissionElement>) {
AjayvirS marked this conversation as resolved.
Show resolved Hide resolved
// sort submission elements so that from and to indexes from matches reference correct elements
const elements = submission.elements?.sort((a, b) => a.id - b.id);
const filesToMatchedElements = new Map<string, FromToElement[]>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
import { Subject, Subscription } from 'rxjs';
import { TextPlagiarismFileElement } from 'app/exercises/shared/plagiarism/types/text/TextPlagiarismFileElement';

/**
* A file name that additionally stores if a plagiarism match has been found for it.
Expand All @@ -14,17 +16,30 @@ export type FileWithHasMatch = {
templateUrl: './split-pane-header.component.html',
styleUrls: ['./split-pane-header.component.scss'],
})
export class SplitPaneHeaderComponent implements OnChanges {
export class SplitPaneHeaderComponent implements OnChanges, OnInit, OnDestroy {
@Input() files: FileWithHasMatch[];
@Input() studentLogin: string;
@Input() fileSelectedSubject!: Subject<TextPlagiarismFileElement>;
@Input() isLockFilesEnabled!: boolean;

@Output() selectFile = new EventEmitter<string>();

public showFiles = false;
public activeFileIndex = 0;

fileSelectSubscription: Subscription;

// Icons
faChevronDown = faChevronDown;

ngOnInit(): void {
this.fileSelectSubscription = this.fileSelectedSubject.subscribe((val) => {
if (val.file && this.isLockFilesEnabled) {
this.handleFileSelectWithoutPropagation(val.file, val.idx);
}
});
}

ngOnChanges(changes: SimpleChanges) {
if (changes.files) {
const fileWithHasMatch: FileWithHasMatch[] = changes.files.currentValue;
Expand All @@ -37,6 +52,12 @@ export class SplitPaneHeaderComponent implements OnChanges {
}
}

ngOnDestroy(): void {
if (this.fileSelectSubscription) {
this.fileSelectSubscription.unsubscribe();
}
}
AjayvirS marked this conversation as resolved.
Show resolved Hide resolved

hasActiveFile(): boolean {
return this.hasFiles() && this.activeFileIndex < this.files.length;
}
Expand All @@ -45,7 +66,24 @@ export class SplitPaneHeaderComponent implements OnChanges {
return this.files[this.activeFileIndex].file;
}

/**
* handles selection of file from dropdown, propagates change to fileslectionsubject component for lock sync
* @param file to be selected
* @param idx index of the file from the dropdown
*/
handleFileSelect(file: FileWithHasMatch, idx: number): void {
this.fileSelectedSubject.next({ idx: idx, file: file });
AjayvirS marked this conversation as resolved.
Show resolved Hide resolved
this.activeFileIndex = idx;
this.showFiles = false;
this.selectFile.emit(file.file);
}

/**
* handles selection of file from dropdown, do NOT propagates change to fileslectionsubject component for lock sync
* @param file to be selected
* @param idx index of the file from the dropdown
*/
handleFileSelectWithoutPropagation(file: FileWithHasMatch, idx: number) {
this.activeFileIndex = idx;
this.showFiles = false;
this.selectFile.emit(file.file);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
<jhi-split-pane-header [files]="files" (selectFile)="handleFileSelect($event)" studentLogin="{{ plagiarismSubmission?.studentLogin }}" />
<jhi-split-pane-header
[files]="files"
[isLockFilesEnabled]="this.isLockFilesEnabled"
[fileSelectedSubject]="fileSelectedSubject"
(selectFile)="handleFileSelect($event)"
studentLogin="{{ plagiarismSubmission?.studentLogin }}"
/>
@if (cannotLoadFiles) {
<div class="text-submission-viewer text-warning">
<div class="text-submission-viewer-warning">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { FileWithHasMatch } from 'app/exercises/shared/plagiarism/plagiarism-spl
import { escape } from 'lodash-es';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { TEXT_FILE_EXTENSIONS } from 'app/shared/constants/file-extensions.constants';
import { Subject } from 'rxjs';
import { TextPlagiarismFileElement } from 'app/exercises/shared/plagiarism/types/text/TextPlagiarismFileElement';

type FilesWithType = { [p: string]: FileType };

Expand All @@ -26,6 +28,8 @@ export class TextSubmissionViewerComponent implements OnChanges {
@Input() matches: Map<string, FromToElement[]>;
@Input() plagiarismSubmission: PlagiarismSubmission<TextSubmissionElement>;
@Input() hideContent: boolean;
@Input() fileSelectedSubject!: Subject<TextPlagiarismFileElement>;
@Input() isLockFilesEnabled = false;

/**
* Name of the currently selected file.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { FileWithHasMatch } from 'app/exercises/shared/plagiarism/plagiarism-split-view/split-pane-header/split-pane-header.component';

export class TextPlagiarismFileElement {
idx: number;
file: FileWithHasMatch;
}
AjayvirS marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions src/main/webapp/i18n/de/plagiarism.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"plagiarismDetection": "Plagiatskontrolle",
"confirm": "Bestätigen",
"deny": "Ablehnen",
"lockSync": "Sync sperren",
"unlockSync": "Sync entsperren",
"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.",
"warning": "Die Bestätigung/Ablehnung eines Plagiats geht beim Neuladen der Seite verloren.",
Expand Down
2 changes: 2 additions & 0 deletions src/main/webapp/i18n/en/plagiarism.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"plagiarismDetection": "Plagiarism Detection",
"confirm": "Confirm",
"deny": "Deny",
"lockSync": "Lock Sync",
"unlockSync": "Unlock Sync",
"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.",
"warning": "Confirmation/Denial of plagiarism will be lost when refreshing the page.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { HttpResponse } from '@angular/common/http';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { MockNgbModalService } from '../../helpers/mocks/service/mock-ngb-modal.service';
import { ButtonComponent } from 'app/shared/components/button.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';
AjayvirS marked this conversation as resolved.
Show resolved Hide resolved

describe('Plagiarism Header Component', () => {
let comp: PlagiarismHeaderComponent;
Expand All @@ -27,6 +28,7 @@ describe('Plagiarism Header Component', () => {
{ provide: TranslateService, useClass: MockTranslateService },
{ provide: NgbModal, useClass: MockNgbModalService },
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();

fixture = TestBed.createComponent(PlagiarismHeaderComponent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ describe('Plagiarism Split View Component', () => {
});

it('should parse text matches', () => {
jest.spyOn(comp, 'mapMatchesToElements').mockReturnValue(new Map());
jest.spyOn(comp, 'mapMatchesToElements').mockReturnValue(new Map<string, FromToElement[]>());

const matches: PlagiarismMatch[] = [
{ startA: 0, startB: 0, length: 5 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { PlagiarismDetailsComponent } from 'app/exercises/shared/plagiarism/plag
import { PlagiarismRunDetailsComponent } from 'app/exercises/shared/plagiarism/plagiarism-run-details/plagiarism-run-details.component';
import { PlagiarismSidebarComponent } from 'app/exercises/shared/plagiarism/plagiarism-sidebar/plagiarism-sidebar.component';
import { MockComponent, MockDirective, MockPipe } from 'ng-mocks';
import { TextPlagiarismFileElement } from 'app/exercises/shared/plagiarism/types/text/TextPlagiarismFileElement';
import { Subject } from 'rxjs';

describe('SplitPaneHeaderComponent', () => {
let comp: SplitPaneHeaderComponent;
Expand Down Expand Up @@ -42,6 +44,7 @@ describe('SplitPaneHeaderComponent', () => {
comp.files = [];
comp.studentLogin = 'ts10abc';
comp.selectFile = new EventEmitter<string>();
comp.fileSelectedSubject = new Subject<TextPlagiarismFileElement>();
});
});

Expand Down
Loading