Skip to content

Commit

Permalink
Plagiarism checks: Change separation operator for plagiarism csv file (
Browse files Browse the repository at this point in the history
  • Loading branch information
AjayvirS authored Dec 3, 2024
1 parent de61561 commit 4a5bf0f
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PlagiarismCase } from 'app/exercises/shared/plagiarism/types/Plagiarism
import { Exercise, getIcon } from 'app/entities/exercise.model';
import { downloadFile } from 'app/shared/util/download.util';
import { DocumentationType } from 'app/shared/components/documentation-button/documentation-button.component';
import { AlertService } from 'app/core/util/alert.service';

@Component({
selector: 'jhi-plagiarism-cases-instructor-view',
Expand All @@ -24,6 +25,7 @@ export class PlagiarismCasesInstructorViewComponent implements OnInit {
constructor(
private plagiarismCasesService: PlagiarismCasesService,
private route: ActivatedRoute,
private alertService: AlertService,
) {}

ngOnInit(): void {
Expand All @@ -37,23 +39,31 @@ export class PlagiarismCasesInstructorViewComponent implements OnInit {
plagiarismCasesForInstructor$.subscribe({
next: (res: HttpResponse<PlagiarismCase[]>) => {
this.plagiarismCases = res.body!;
this.groupedPlagiarismCases = this.plagiarismCases.reduce((acc: { [exerciseId: number]: PlagiarismCase[] }, plagiarismCase) => {
const caseExerciseId = plagiarismCase.exercise?.id;
if (caseExerciseId === undefined) {
return acc;
}

// Group initialization
if (!acc[caseExerciseId]) {
acc[caseExerciseId] = [];
this.exercisesWithPlagiarismCases.push(plagiarismCase.exercise!);
}

// Grouping
acc[caseExerciseId].push(plagiarismCase);
this.groupedPlagiarismCases = this.plagiarismCases.reduce(
(
acc: {
[exerciseId: number]: PlagiarismCase[];
},
plagiarismCase,
) => {
const caseExerciseId = plagiarismCase.exercise?.id;
if (caseExerciseId === undefined) {
return acc;
}

// Group initialization
if (!acc[caseExerciseId]) {
acc[caseExerciseId] = [];
this.exercisesWithPlagiarismCases.push(plagiarismCase.exercise!);
}

// Grouping
acc[caseExerciseId].push(plagiarismCase);

return acc;
}, {});
return acc;
},
{},
);
},
});
}
Expand Down Expand Up @@ -131,20 +141,48 @@ export class PlagiarismCasesInstructorViewComponent implements OnInit {
}

/**
* export the plagiarism cases in CSV format
* set placeholder for undefined values and sanitize the operators away
* @param value to be sanitized or replaced with -
* @private
*/
private sanitizeCSVField(value: any): string {
if (value === null || value === undefined) {
// used as placeholder for null or if the passed value does not exist
return '-';
}
// sanitize the operators away in case they appear in the values
return String(value).replace(/;/g, '";"');
}

/**
* export the cases in CSV format
*/
exportPlagiarismCases(): void {
const blobParts: string[] = ['Student Login,Exercise,Verdict, Verdict Date\n'];
this.plagiarismCases.forEach((plagiarismCase) => {
const exerciseTitleCSVSanitized = plagiarismCase.exercise?.title?.replace(',', '","');
const headers = ['Student Login', 'Matr. Nr.', 'Exercise', 'Verdict', 'Verdict Date', 'Verdict By'];
const blobParts: string[] = [headers.join(';') + '\n'];
this.plagiarismCases.reduce((acc, plagiarismCase) => {
const fields = [
this.sanitizeCSVField(plagiarismCase.student?.login),
this.sanitizeCSVField(plagiarismCase.student?.visibleRegistrationNumber),
this.sanitizeCSVField(plagiarismCase.exercise?.title),
];
if (plagiarismCase.verdict) {
blobParts.push(
`${plagiarismCase.student?.login},${exerciseTitleCSVSanitized},${plagiarismCase.verdict},${plagiarismCase.verdictDate},${plagiarismCase.verdictBy!.name}\n`,
fields.push(
this.sanitizeCSVField(plagiarismCase.verdict),
this.sanitizeCSVField(plagiarismCase.verdictDate),
this.sanitizeCSVField(plagiarismCase.verdictBy?.name),
);
} else {
blobParts.push(`${plagiarismCase.student?.login},${exerciseTitleCSVSanitized}, No verdict yet, -, -\n`);
fields.push('No verdict yet', '-', '-');
}
});
downloadFile(new Blob(blobParts, { type: 'text/csv' }), 'plagiarism-cases.csv');
acc.push(fields.join(';') + '\n');
return acc;
}, blobParts);

try {
downloadFile(new Blob(blobParts, { type: 'text/csv' }), 'plagiarism-cases.csv');
} catch (error) {
this.alertService.error('artemisApp.plagiarism.plagiarismCases.export.error');
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { AfterViewInit, ChangeDetectorRef, Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { StudentExam } from 'app/entities/student-exam.model';
import { Exercise, ExerciseType } from 'app/entities/exercise.model';
Expand All @@ -8,7 +8,7 @@ import { ExamNavigationBarComponent } from 'app/exam/participate/exam-navigation
import { SubmissionService } from 'app/exercises/shared/submission/submission.service';
import dayjs from 'dayjs/esm';
import { SubmissionVersion } from 'app/entities/submission-version.model';
import { Observable, Subscription, forkJoin, map, mergeMap, toArray } from 'rxjs';
import { Observable, Subscription, forkJoin, map, mergeMap, tap, toArray } from 'rxjs';
import { ProgrammingSubmission } from 'app/entities/programming/programming-submission.model';
import { Submission } from 'app/entities/submission.model';
import { FileUploadSubmission } from 'app/entities/file-upload-submission.model';
Expand Down Expand Up @@ -57,6 +57,7 @@ export class StudentExamTimelineComponent implements OnInit, AfterViewInit, OnDe
private submissionService: SubmissionService,
private submissionVersionService: SubmissionVersionService,
private programmingExerciseParticipationService: ProgrammingExerciseParticipationService,
private cdr: ChangeDetectorRef,
) {}

ngOnInit(): void {
Expand Down Expand Up @@ -203,7 +204,8 @@ export class StudentExamTimelineComponent implements OnInit, AfterViewInit, OnDe
);
}
});
return forkJoin([...submissionObservables]);

return forkJoin([...submissionObservables]).pipe(tap(() => this.cdr.detectChanges()));
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/main/webapp/i18n/de/plagiarism.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@
"exportCsv": "CSV exportieren",
"confirm": "Fall bestätigen",
"discard": "Fall verwerfen"
},
"export": {
"error": "Fehler beim Exportieren der CSV-Datei."
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/webapp/i18n/en/plagiarism.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@
"exportCsv": "Export CSV",
"confirm": "Confirm case",
"discard": "Discard case"
},
"export": {
"error": "Error exporting CSV."
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,9 @@ describe('Plagiarism Cases Instructor View Component', () => {
const downloadSpy = jest.spyOn(DownloadUtil, 'downloadFile');
component.plagiarismCases = [plagiarismCase1, plagiarismCase4];
const expectedBlob = [
'Student Login,Exercise,Verdict, Verdict Date\n',
`Student 1, Test Exercise 1, PLAGIARISM, ${date}, Test Instructor 1\n`,
'Student 2, Test Exercise 2, No verdict yet, -, -\n',
'Student Login; Matr. Nr.; Exercise;Verdict; Verdict Date\n',
`Student 1; -; Test Exercise 1; PLAGIARISM; ${date}; Test Instructor 1\n`,
'Student 2; -; Test Exercise 2; No verdict yet; -; -\n',
];
component.exportPlagiarismCases();
expect(downloadSpy).toHaveBeenCalledOnce();
Expand Down

0 comments on commit 4a5bf0f

Please sign in to comment.