Skip to content

Tutor exam dashboard for the assessment of exams #1727

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

Merged
merged 79 commits into from
Jul 4, 2020
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
046e031
Adjust CourseResource and CourseService to handle exam-dashboard-requ…
JonasPetry Jun 27, 2020
e6fbb11
Adjust server to initialize exam-dashboard for one specific exam
JonasPetry Jun 27, 2020
6a89be4
Link tutor-course-dashboard.component.ts for exam in the client
JonasPetry Jun 27, 2020
606f68c
Adjust back link in tutor-course-dashboard.component.ts depending on …
JonasPetry Jun 27, 2020
47e3b8c
Add missing javadoc to CourseService and CourseResource
JonasPetry Jun 27, 2020
fdfea88
Only get interesting exercises for the dashboard if the end date of t…
JonasPetry Jun 29, 2020
2b28c6c
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jun 29, 2020
2c34ac6
Adjust page title of tutor exam dashboard in exam-management.route.ts
JonasPetry Jun 29, 2020
bcfaa32
Add exam case distinction to text exercise assessment
JonasPetry Jun 29, 2020
221ce29
Adjust CourseResource and CourseService to handle exam-dashboard-requ…
JonasPetry Jun 27, 2020
1399c91
Adjust server to initialize exam-dashboard for one specific exam
JonasPetry Jun 27, 2020
d20ab0d
Link tutor-course-dashboard.component.ts for exam in the client
JonasPetry Jun 27, 2020
6d883b8
Adjust back link in tutor-course-dashboard.component.ts depending on …
JonasPetry Jun 27, 2020
08eb362
Add missing javadoc to CourseService and CourseResource
JonasPetry Jun 27, 2020
cf0ab35
Only get interesting exercises for the dashboard if the end date of t…
JonasPetry Jun 29, 2020
85a7367
Adjust page title of tutor exam dashboard in exam-management.route.ts
JonasPetry Jun 29, 2020
eaa72eb
Add exam case distinction to text exercise assessment
JonasPetry Jun 29, 2020
3b355a4
Merge remote-tracking branch 'origin/exam-mode/variants/tutor-dashboa…
JonasPetry Jun 29, 2020
f7450c5
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jun 29, 2020
6f041a8
Update debug message in CourseResource
JonasPetry Jun 29, 2020
aa93b63
Remove unnecessary debug message
JonasPetry Jun 29, 2020
a9da60a
Add createCourseWithExamAndExerciseGroupAndExercises() to DatabaseUti…
JonasPetry Jun 29, 2020
4a84244
Add createCourseWithExamAndExerciseGroupAndExercises() to DatabaseUti…
JonasPetry Jun 29, 2020
1afd7df
Add first integration test for course exam dashboard
JonasPetry Jun 29, 2020
2f11085
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jun 29, 2020
c898301
Adjust links in text exercise assessment
JonasPetry Jun 29, 2020
bd95961
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jun 30, 2020
2f207c6
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jun 30, 2020
7e3e88d
Adjust links in text exercise assessment
JonasPetry Jun 30, 2020
e802faa
Fix buttons in tutor-exercise-dashboard.component.ts
JonasPetry Jun 30, 2020
ffd9f93
Fix back button in text exercise assessment
JonasPetry Jun 30, 2020
8a7a5fd
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jun 30, 2020
d0eff6a
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jun 30, 2020
4b542de
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jun 30, 2020
ebb385f
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jul 1, 2020
d539075
Remove unused parameter in tutor-exercise-dashboard.component.ts
JonasPetry Jul 1, 2020
73660aa
Update documentation
JonasPetry Jul 1, 2020
7892991
Restore integration test that got lost during merge
JonasPetry Jul 1, 2020
fb70cdc
Add todo for correct status calculation
JonasPetry Jul 1, 2020
634f3ca
Add more test cases for CourseIntegrationTest
JonasPetry Jul 1, 2020
3e3fd73
Add more integration tests and remove todo
JonasPetry Jul 1, 2020
105270e
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jul 1, 2020
0dd87fc
Implement feedback on pull request
JonasPetry Jul 1, 2020
4768cbb
Extract method to remove duplicated code
JonasPetry Jul 1, 2020
2dba396
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jul 1, 2020
9e69e00
Fix typescript error
JonasPetry Jul 1, 2020
ad10812
Try to fix failing test case
JonasPetry Jul 1, 2020
dfcbaef
Update unclear method name and documentation in CourseService
JonasPetry Jul 2, 2020
b53b2ce
small improvements
Jul 2, 2020
40ee79f
some adaptions regarding the population of exercises
Jul 2, 2020
963ad89
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
JonasPetry Jul 2, 2020
5aee45a
Load exercises through exam and not through course
JonasPetry Jul 2, 2020
07ffdff
Rename test cases
JonasPetry Jul 2, 2020
06b4d8b
Make tutor-course-dashboard work
JonasPetry Jul 2, 2020
774c736
Add ngIf to unused heading
JonasPetry Jul 2, 2020
a6fb2ee
Merge develop and resolve conflicts
kloessst Jul 3, 2020
e082e6d
Check route consistency and only prepare interesting exercises for as…
kloessst Jul 3, 2020
e69254e
Convert date from server
kloessst Jul 3, 2020
ce35ba8
Improve loop with reduce
kloessst Jul 3, 2020
e672b2e
Simplify conditional
kloessst Jul 3, 2020
145ac47
Revert reduce loop
kloessst Jul 3, 2020
9fac49b
Small improvement
kloessst Jul 3, 2020
2e5c067
Move REST call to the right service
kloessst Jul 3, 2020
d28faa3
Improve doc
kloessst Jul 3, 2020
a4764d1
TS style
kloessst Jul 3, 2020
ad9d6de
Remove unnecessary wrapper and try to fix serialization
kloessst Jul 3, 2020
a0bb6fd
Disable eslint no-unused-expression
kloessst Jul 3, 2020
8c7e9ae
refactored duplicated code
Jul 3, 2020
2033472
add default constructor
Jul 3, 2020
d850bd3
Merge branch 'exam-mode/variants/tutor-dashboard' of https://github.c…
kloessst Jul 3, 2020
f2e7743
allowSetters is the key to the universe
Jul 3, 2020
9559827
Merge branch 'exam-mode/variants/tutor-dashboard' of https://github.c…
Jul 3, 2020
46a2244
Deactivate test. Thanks Jackson!
kloessst Jul 3, 2020
40f722e
Add doc
kloessst Jul 3, 2020
4a3dc89
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
Jul 4, 2020
32a2635
remove unused imports
Jul 4, 2020
dc086cd
Merge branch 'develop' into exam-mode/variants/tutor-dashboard
Jul 4, 2020
8852d62
fix disabled test with correct annotation @JsonIgnoreProperties(value…
Jul 4, 2020
22f43d3
avoid duplicated code and improve code understanding
Jul 4, 2020
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 @@ -49,4 +49,7 @@ public interface CourseRepository extends JpaRepository<Course, Long> {
List<Course> findAllCurrentlyActiveAndNotOnlineAndEnabled(@Param("now") ZonedDateTime now);

List<Course> findAllByShortName(String shortName);

Optional<Course> findById(long courseId);

}
37 changes: 37 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/service/CourseService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package de.tum.in.www1.artemis.service;

import java.time.ZonedDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.validation.constraints.NotNull;
Expand All @@ -14,6 +17,7 @@
import de.tum.in.www1.artemis.domain.Course;
import de.tum.in.www1.artemis.domain.Exercise;
import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.domain.exam.Exam;
import de.tum.in.www1.artemis.domain.exam.ExerciseGroup;
import de.tum.in.www1.artemis.repository.CourseRepository;
import de.tum.in.www1.artemis.repository.UserRepository;
Expand Down Expand Up @@ -183,6 +187,39 @@ public Course findOneWithExercises(long courseId) {
return courseRepository.findWithEagerExercisesById(courseId);
}

/**
* Get one course by id with all exercises for all exams
* @param courseId - The id of the entity
* @param examId - Id of the exam that contains the exercises
* @return the entity
*/
public Course findOneWithExamExercises(long courseId, long examId) {
log.debug("Request to get Course : {}", courseId);
Optional<Course> optionalCourse = courseRepository.findById(courseId);

if (optionalCourse.isPresent()) {
Course course = optionalCourse.get();
Set<Exercise> exercises = new HashSet<>();

Exam exam = this.examService.findOneWithExerciseGroupsAndExercises(examId);

// check that exam is over
if (exam.getEndDate().isBefore(ZonedDateTime.now())) {
// extract all exercises for all the exam
List<ExerciseGroup> exerciseGroups = exam.getExerciseGroups();
for (ExerciseGroup exerciseGroup : exerciseGroups) {
exercises.addAll(exerciseGroup.getExercises());
}
}

// set all exam exercises
course.setExercises(exercises);

return course;
}
return null;
}

public Course findOneWithExercisesAndLectures(long courseId) {
log.debug("Request to get Course : {}", courseId);
return courseRepository.findWithEagerExercisesAndLecturesById(courseId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,67 @@ public ResponseEntity<Course> getCourseForTutorDashboard(@PathVariable long cour
return ResponseUtil.wrapOrNotFound(Optional.of(course));
}

/**
* GET /courses/:courseId/for-exam-tutor-dashboard
*
* @param courseId the id of the course to retrieve
* @param examId the id of the exam that contains the exercises
* @return data about a course including all exercises, plus some data for the tutor as tutor status for assessment
*/
@GetMapping("/courses/{courseId}/exam/{examId}/for-exam-tutor-dashboard")
@PreAuthorize("hasAnyRole('TA', 'INSTRUCTOR', 'ADMIN')")
public ResponseEntity<Course> getCourseForExamTutorDashboard(@PathVariable long courseId, @PathVariable long examId) {
log.debug("REST request /courses/{courseId}/exam/{examId}/for-exam-tutor-dashboard");
Course course = courseService.findOneWithExamExercises(courseId, examId);
if (course == null) {
return notFound();
}
User user = userService.getUserWithGroupsAndAuthorities();
if (!userHasPermission(course, user)) {
return forbidden();
}

Set<Exercise> interestingExercises = course.getInterestingExercisesForAssessmentDashboards();
course.setExercises(interestingExercises);

List<TutorParticipation> tutorParticipations = tutorParticipationService.findAllByCourseAndTutor(course, user);

for (Exercise exercise : course.getExercises()) {

DueDateStat numberOfSubmissions;
DueDateStat numberOfAssessments;

if (exercise instanceof ProgrammingExercise) {
numberOfSubmissions = new DueDateStat(programmingExerciseService.countSubmissionsByExerciseIdSubmitted(exercise.getId()), 0L);
numberOfAssessments = new DueDateStat(programmingExerciseService.countAssessmentsByExerciseIdSubmitted(exercise.getId()), 0L);
}
else {
numberOfSubmissions = submissionService.countSubmissionsForExercise(exercise.getId());
numberOfAssessments = resultService.countNumberOfFinishedAssessmentsForExercise(exercise.getId());
}

exercise.setNumberOfSubmissions(numberOfSubmissions);
exercise.setNumberOfAssessments(numberOfAssessments);

exerciseService.calculateNrOfOpenComplaints(exercise);

List<ExampleSubmission> exampleSubmissions = this.exampleSubmissionRepository.findAllByExerciseId(exercise.getId());
// Do not provide example submissions without any assessment
exampleSubmissions.removeIf(exampleSubmission -> exampleSubmission.getSubmission() == null || exampleSubmission.getSubmission().getResult() == null);
exercise.setExampleSubmissions(new HashSet<>(exampleSubmissions));

TutorParticipation tutorParticipation = tutorParticipations.stream().filter(participation -> participation.getAssessedExercise().getId().equals(exercise.getId()))
.findFirst().orElseGet(() -> {
TutorParticipation emptyTutorParticipation = new TutorParticipation();
emptyTutorParticipation.setStatus(TutorParticipationStatus.NOT_PARTICIPATED);
return emptyTutorParticipation;
});
exercise.setTutorParticipations(Collections.singleton(tutorParticipation));
}

return ResponseUtil.wrapOrNotFound(Optional.of(course));
}

/**
* GET /courses/:courseId/stats-for-tutor-dashboard A collection of useful statistics for the tutor course dashboard, including: - number of submissions to the course - number of
* assessments - number of assessments assessed by the tutor - number of complaints
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export class TutorCourseDashboardComponent implements OnInit, AfterViewInit {

exerciseForGuidedTour: Exercise | null;

isExamMode: boolean;

constructor(
private courseService: CourseManagementService,
private jhiAlertService: AlertService,
Expand Down Expand Up @@ -84,7 +86,9 @@ export class TutorCourseDashboardComponent implements OnInit, AfterViewInit {
* Percentages are calculated and rounded towards zero.
*/
loadAll() {
this.courseService.getForTutors(this.courseId).subscribe(
const examId = Number(this.route.snapshot.paramMap.get('examId'));
this.isExamMode = !!examId;
this.courseService.getForTutors(this.courseId, examId).subscribe(
(res: HttpResponse<Course>) => {
this.course = Course.from(res.body!);
this.course.isAtLeastTutor = this.accountService.isAtLeastTutorInCourse(this.course);
Expand Down Expand Up @@ -163,7 +167,11 @@ export class TutorCourseDashboardComponent implements OnInit, AfterViewInit {
* Navigate back to the course management page.
*/
back() {
this.router.navigate(['course-management']);
if (this.isExamMode) {
this.router.navigate(['course-management', this.course.id, 'exams']);
} else {
this.router.navigate(['course-management']);
}
}

sortRows() {
Expand Down
15 changes: 11 additions & 4 deletions src/main/webapp/app/course/manage/course-management.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,18 @@ export class CourseManagementService {
/**
* returns the course with the provided unique identifier for the tutor dashboard
* @param courseId - the id of the course
* @param examId - Id of the exam when it is in exam mode, otherwise undefined
*/
getForTutors(courseId: number): Observable<EntityResponseType> {
return this.http
.get<Course>(`${this.resourceUrl}/${courseId}/for-tutor-dashboard`, { observe: 'response' })
.pipe(map((res: EntityResponseType) => this.convertDateFromServer(res)));
getForTutors(courseId: number, examId: number): Observable<EntityResponseType> {
if (examId) {
return this.http
.get<Course>(`${this.resourceUrl}/${courseId}/exam/${examId}/for-exam-tutor-dashboard`, { observe: 'response' })
.pipe(map((res: EntityResponseType) => this.convertDateFromServer(res)));
} else {
return this.http
.get<Course>(`${this.resourceUrl}/${courseId}/for-tutor-dashboard`, { observe: 'response' })
.pipe(map((res: EntityResponseType) => this.convertDateFromServer(res)));
}
}

/**
Expand Down
13 changes: 13 additions & 0 deletions src/main/webapp/app/exam/manage/exam-management.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ <h4 id="course-page-heading" jhiTranslate="artemisApp.examManagement.title">Exam
<td>{{ exam.startDate | durationTo: exam.endDate }}</td>
<td>{{ exam.numberOfRegisteredUsers }}</td>
<td class="text-right">
<div class="btn-group-vertical mr-1 mb-1">
<button
*ngIf="isAtLeastInstructor"
type="submit"
[routerLink]="[exam.id, 'tutor-exam-dashboard']"
class="btn btn-primary btn-sm mr-1 mb-1 exercise-button"
id="exercises-button-{{ exam.id }}"
>
<fa-icon [icon]="'th-list'"></fa-icon>
<span class="d-none d-md-inline">{{ 'artemisApp.examManagement.tutorDashboard' | translate }}</span>
</button>
</div>

<div class="btn-group flex-btn-group-container">
<div class="btn-group-vertical mr-1 mb-1">
<button
Expand Down
10 changes: 10 additions & 0 deletions src/main/webapp/app/exam/manage/exam-management.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { FileUploadExerciseResolve } from 'app/exercises/file-upload/manage/file
import { QuizExerciseDetailComponent } from 'app/exercises/quiz/manage/quiz-exercise-detail.component';
import { ProgrammingExerciseUpdateComponent } from 'app/exercises/programming/manage/update/programming-exercise-update.component';
import { ProgrammingExerciseResolve } from 'app/exercises/programming/manage/programming-exercise-management-routing.module';
import { TutorCourseDashboardComponent } from 'app/course/dashboards/tutor-course-dashboard/tutor-course-dashboard.component';

@Injectable({ providedIn: 'root' })
export class ExamResolve implements Resolve<Exam> {
Expand Down Expand Up @@ -349,6 +350,15 @@ export const examManagementRoute: Routes = [
},
canActivate: [UserRouteAccessService],
},
{
path: ':examId/tutor-exam-dashboard',
component: TutorCourseDashboardComponent,
data: {
authorities: ['ROLE_ADMIN', 'ROLE_INSTRUCTOR', 'ROLE_TA'],
pageTitle: 'artemisApp.examManagement.tutorDashboard',
},
canActivate: [UserRouteAccessService],
},
];

const EXAM_MANAGEMENT_ROUTES = [...examManagementRoute];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, OnInit, AfterViewInit } from '@angular/core';
import { SafeHtml } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
import { CourseManagementService } from 'app/course/manage/course-management.service';
import { AlertService } from 'app/core/alert/alert.service';
import { User } from 'app/core/user/user.model';
Expand Down Expand Up @@ -452,6 +452,10 @@ export class TutorExerciseDashboardComponent implements OnInit, AfterViewInit {
* Navigates back to the tutor dashboard
*/
back() {
this.router.navigate([`/course-management/${this.courseId}/tutor-dashboard`]);
if (this.exercise?.course) {
this.router.navigate([`/course-management/${this.courseId}/tutor-dashboard`]);
} else {
this.router.navigate([`/course-management/${this.courseId}/exams/${this.exercise!.exerciseGroup!.exam!.id}/tutor-exam-dashboard`]);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { StructuredGradingCriterionService } from 'app/exercises/shared/structur
export class TextSubmissionAssessmentComponent implements OnInit {
private userId: number | null;
exerciseId: number;
courseId: number;
participation: StudentParticipation | null;
submission: TextSubmission | null;
exercise: TextExercise | null;
Expand Down Expand Up @@ -133,7 +134,14 @@ export class TextSubmissionAssessmentComponent implements OnInit {
this.submission = this.participation?.submissions[0] as TextSubmission;
this.exercise = this.participation?.exercise as TextExercise;
this.result = this.submission?.result;
this.isAtLeastInstructor = this.accountService.isAtLeastInstructorInCourse(this.exercise!.course!);
this.courseId = this.exercise?.course ? this.exercise?.course?.id! : this.exercise?.exerciseGroup?.exam?.course?.id!;

// case distinction for exam mode
if (this.exercise!.course) {
this.isAtLeastInstructor = this.accountService.isAtLeastInstructorInCourse(this.exercise!.course);
} else {
this.isAtLeastInstructor = this.accountService.isAtLeastInstructorInCourse(this.exercise!.exerciseGroup!.exam!.course!);
}
this.prepareTextBlocksAndFeedbacks();
this.getComplaint();
this.updateUrlIfNeeded();
Expand All @@ -147,7 +155,7 @@ export class TextSubmissionAssessmentComponent implements OnInit {
if (this.isNewAssessmentRoute) {
// Update the url with the new id, without reloading the page, to make the history consistent
const newUrl = this.router
.createUrlTree(['course-management', this.exercise?.course?.id, 'text-exercises', this.exercise?.id, 'submissions', this.submission?.id, 'assessment'])
.createUrlTree(['course-management', this.courseId, 'text-exercises', this.exercise?.id, 'submissions', this.submission?.id, 'assessment'])
.toString();
this.location.go(newUrl);
}
Expand Down Expand Up @@ -215,7 +223,7 @@ export class TextSubmissionAssessmentComponent implements OnInit {
*/
async nextSubmission(): Promise<void> {
this.nextSubmissionBusy = true;
await this.router.navigate(['/course-management', this.exercise?.course?.id, 'text-exercises', this.exercise?.id, 'submissions', 'new', 'assessment']);
await this.router.navigate(['/course-management', this.courseId, 'text-exercises', this.exercise?.id, 'submissions', 'new', 'assessment']);
}

/**
Expand All @@ -242,11 +250,11 @@ export class TextSubmissionAssessmentComponent implements OnInit {
}

navigateBack() {
if (this.exercise && this.exercise.teamMode && this.exercise.course && this.submission) {
if (this.exercise && this.exercise.teamMode && this.courseId && this.submission) {
const teamId = (this.submission.participation as StudentParticipation).team.id;
this.router.navigateByUrl(`/courses/${this.exercise.course.id}/exercises/${this.exercise.id}/teams/${teamId}`);
} else if (this.exercise && !this.exercise.teamMode && this.exercise.course) {
this.router.navigateByUrl(`/course-management/${this.exercise.course.id}/exercises/${this.exercise.id}/tutor-dashboard`);
this.router.navigateByUrl(`/courses/${this.courseId}/exercises/${this.exercise.id}/teams/${teamId}`);
} else if (this.exercise && !this.exercise.teamMode && this.courseId) {
this.router.navigateByUrl(`/course-management/${this.courseId}/exercises/${this.exercise.id}/tutor-dashboard`);
} else {
this.location.back();
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/webapp/i18n/de/exam.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@
"delete": {
"question": "Soll die Klausur <strong>{{ title }}</strong> wirklich dauerhaft gelöscht werden? Alle zugehörigen Elemente werden auch gelöscht. Diese Aktion kann NICHT rückgängig gemacht werden!",
"typeNameToConfirm": "Bitte gib den Namen der Klausur zur Bestätigung ein."
}
},
"tutorDashboard": "Tutor Klausur Dashboard"
},
"studentExamDetail": {
"studentExam": "Klausur ({{ examTitle }})",
Expand Down
3 changes: 2 additions & 1 deletion src/main/webapp/i18n/en/exam.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@
"delete": {
"question": "Are you sure you want to permanently delete the Exam <strong>{{ title }}</strong>? All associated elements will be deleted. This action can NOT be undone!",
"typeNameToConfirm": "Please type in the name of the Exam to confirm."
}
},
"tutorDashboard": "Tutor Exam Dashboard"
},
"studentExamDetail": {
"studentExam": "Student exam ({{ examTitle }})",
Expand Down