From cdb258e98061a8dab69c2e4ca704095018e342a3 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Fri, 24 Nov 2023 12:47:17 +0100 Subject: [PATCH 1/3] feat(frontend): Highlight failing students in course table --- .../course/students/students.component.html | 25 +++++++++++++------ .../course/students/students.component.ts | 4 +++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/assignment/modules/course/students/students.component.html b/frontend/src/app/assignment/modules/course/students/students.component.html index 898813d0a..cbd5e2bab 100644 --- a/frontend/src/app/assignment/modules/course/students/students.component.html +++ b/frontend/src/app/assignment/modules/course/students/students.component.html @@ -55,14 +55,23 @@ } @for (solution of student.solutions;track solution;let i = $index) { - - - - {{ solution?.points || 0 }} + + @if (!solution) { + 0 + } @else { + + {{ solution.points ?? '-' }} + + } @if (solution && course) { diff --git a/frontend/src/app/assignment/modules/course/students/students.component.ts b/frontend/src/app/assignment/modules/course/students/students.component.ts index 3687b4e82..b3ff5f2d5 100644 --- a/frontend/src/app/assignment/modules/course/students/students.component.ts +++ b/frontend/src/app/assignment/modules/course/students/students.component.ts @@ -9,6 +9,7 @@ import {ReadAssignmentDto} from "../../../model/assignment"; import {AssigneeService} from "../../../services/assignee.service"; import {BulkUpdateAssigneeDto} from "../../../model/assignee"; import {ToastService} from "@mean-stream/ngbx"; +import {TaskService} from "../../../services/task.service"; @Component({ selector: 'app-students', @@ -19,6 +20,7 @@ export class StudentsComponent implements OnInit { course?: Course; students?: CourseStudent[]; assignments: (ReadAssignmentDto | undefined)[] = []; + assignmentPoints: number[] = []; assignmentNames: string[] = []; assignees: string[] = []; @@ -30,6 +32,7 @@ export class StudentsComponent implements OnInit { private assignmentService: AssignmentService, private assigneeService: AssigneeService, private toastService: ToastService, + private taskService: TaskService, ) { } @@ -41,6 +44,7 @@ export class StudentsComponent implements OnInit { ).subscribe(assignments => { this.assignments = assignments; this.assignmentNames = this.courseService.getAssignmentNames(assignments); + this.assignmentPoints = assignments.map(a => a ? this.taskService.sumPositivePoints(a.tasks) : 0); }); this.route.params.pipe( From 02d31cacbb58be05a626e11a4912b10f3f051cc6 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Fri, 24 Nov 2023 13:15:11 +0100 Subject: [PATCH 2/3] feat(assignments-service): Add Assignment.passingPoints --- .../apps/assignments/src/assignment/assignment.schema.ts | 7 +++++++ .../apps/assignments/src/statistics/statistics.service.ts | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/services/apps/assignments/src/assignment/assignment.schema.ts b/services/apps/assignments/src/assignment/assignment.schema.ts index 343b424c8..699bd8ac5 100644 --- a/services/apps/assignments/src/assignment/assignment.schema.ts +++ b/services/apps/assignments/src/assignment/assignment.schema.ts @@ -12,6 +12,7 @@ import { IsNotEmpty, IsNumber, IsOptional, + IsPositive, IsString, IsUrl, ValidateNested, @@ -185,6 +186,12 @@ export class Assignment { @Type(() => ClassroomInfo) classroom?: ClassroomInfo; + @Prop() + @ApiPropertyOptional() + @IsOptional() + @IsPositive() + passingPoints?: number; + @Prop() @ApiProperty({type: [Task]}) @IsArray() diff --git a/services/apps/assignments/src/statistics/statistics.service.ts b/services/apps/assignments/src/statistics/statistics.service.ts index 5b24fede0..dd14cd0ae 100644 --- a/services/apps/assignments/src/statistics/statistics.service.ts +++ b/services/apps/assignments/src/statistics/statistics.service.ts @@ -163,7 +163,7 @@ export class StatisticsService { } private async solutionStatistics(assignment: AssignmentDocument): Promise { - const passingMin = assignment.tasks.reduce((a, c) => c.points > 0 ? a + c.points : a, 0) / 2; + const passingPoints = assignment.passingPoints ?? assignment.tasks.reduce((a, c) => c.points > 0 ? a + c.points : a, 0) / 2; const [result] = await this.solutionService.model.aggregate([ {$match: {assignment: assignment._id}}, { @@ -172,7 +172,7 @@ export class StatisticsService { total: {$sum: 1}, points: {$sum: '$points'}, graded: {$sum: {$cond: [{$gt: ['$points', null]}, 1, 0]}}, - passed: {$sum: {$cond: [{$gte: ['$points', passingMin]}, 1, 0]}}, + passed: {$sum: {$cond: [{$gte: ['$points', passingPoints]}, 1, 0]}}, }, }, ]); From 576daec5452d98e2b30cc009c764209f6fd99e1d Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Fri, 24 Nov 2023 13:15:30 +0100 Subject: [PATCH 3/3] feat(frontend): Add assignment passing points setting and use in students table --- frontend/src/app/assignment/model/assignment.ts | 1 + .../modules/course/students/students.component.html | 2 +- .../modules/course/students/students.component.ts | 2 +- .../edit-assignment/tasks/tasks.component.html | 11 +++++++++++ .../modules/edit-assignment/tasks/tasks.component.ts | 7 +++++++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/assignment/model/assignment.ts b/frontend/src/app/assignment/model/assignment.ts index 476b14b16..ec2c344f4 100644 --- a/frontend/src/app/assignment/model/assignment.ts +++ b/frontend/src/app/assignment/model/assignment.ts @@ -28,6 +28,7 @@ export default class Assignment { classroom?: ClassroomInfo; + passingPoints?: number; tasks: Task[]; } diff --git a/frontend/src/app/assignment/modules/course/students/students.component.html b/frontend/src/app/assignment/modules/course/students/students.component.html index cbd5e2bab..6419fe92b 100644 --- a/frontend/src/app/assignment/modules/course/students/students.component.html +++ b/frontend/src/app/assignment/modules/course/students/students.component.html @@ -64,7 +64,7 @@ 'd-block', 'text-decoration-none', solution.points === undefined ? 'text-muted' - : solution.points < assignmentPoints[i] / 2 ? 'text-warning' + : solution.points < assignmentPoints[i] ? 'text-warning' : 'text-body' ] // TODO make passing threshold configurable" [routerLink]="['/assignments', course?.assignments?.[i], 'solutions', solution._id]" diff --git a/frontend/src/app/assignment/modules/course/students/students.component.ts b/frontend/src/app/assignment/modules/course/students/students.component.ts index b3ff5f2d5..84c324f0f 100644 --- a/frontend/src/app/assignment/modules/course/students/students.component.ts +++ b/frontend/src/app/assignment/modules/course/students/students.component.ts @@ -44,7 +44,7 @@ export class StudentsComponent implements OnInit { ).subscribe(assignments => { this.assignments = assignments; this.assignmentNames = this.courseService.getAssignmentNames(assignments); - this.assignmentPoints = assignments.map(a => a ? this.taskService.sumPositivePoints(a.tasks) : 0); + this.assignmentPoints = assignments.map(a => a ? a.passingPoints ?? this.taskService.sumPositivePoints(a.tasks) / 2 : 0); }); this.route.params.pipe( diff --git a/frontend/src/app/assignment/modules/edit-assignment/tasks/tasks.component.html b/frontend/src/app/assignment/modules/edit-assignment/tasks/tasks.component.html index 79620d294..3aac72714 100644 --- a/frontend/src/app/assignment/modules/edit-assignment/tasks/tasks.component.html +++ b/frontend/src/app/assignment/modules/edit-assignment/tasks/tasks.component.html @@ -1,3 +1,14 @@ +
+ +
+ + +
+
+ The minimum number of points required to pass the assignment. + Defaults to 50% of the maximum points. +
+
diff --git a/frontend/src/app/assignment/modules/edit-assignment/tasks/tasks.component.ts b/frontend/src/app/assignment/modules/edit-assignment/tasks/tasks.component.ts index 2678c98ac..05e955d44 100644 --- a/frontend/src/app/assignment/modules/edit-assignment/tasks/tasks.component.ts +++ b/frontend/src/app/assignment/modules/edit-assignment/tasks/tasks.component.ts @@ -1,6 +1,7 @@ import {Component, OnDestroy} from '@angular/core'; import {AssignmentContext} from '../../../services/assignment.context'; import {TaskMarkdownService} from '../task-markdown.service'; +import {TaskService} from "../../../services/task.service"; @Component({ selector: 'app-edit-assignment-tasks', @@ -12,6 +13,7 @@ export class TasksComponent implements OnDestroy { constructor( private taskMarkdownService: TaskMarkdownService, + private taskService: TaskService, public context: AssignmentContext, ) { } @@ -33,4 +35,9 @@ export class TasksComponent implements OnDestroy { } } + setPassingPoints() { + const tasks = this.markdown ? this.taskMarkdownService.parseTasks(this.markdown) : this.context.assignment.tasks; + this.context.assignment.passingPoints = this.taskService.sumPositivePoints(tasks) / 2; + this.context.saveDraft(); + } }