From e4cff04049d25d6cbdd66b52d8a6fad7b36a8437 Mon Sep 17 00:00:00 2001 From: Valery <57412523+valerydluski@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:36:24 +0200 Subject: [PATCH] feat: add a new field certificateThreshold to the Course data model (#2453) --- client/specs/smoke.spec.ts | 15 +++++++ client/src/api/api.ts | 24 +++++++++++ .../StudentsWithMentorsCard.tsx | 4 +- client/src/pages/admin/courses.tsx | 40 +++++++++++++++---- nestjs/src/courses/dto/course.dto.ts | 4 ++ nestjs/src/courses/dto/create-course.dto.ts | 4 ++ nestjs/src/courses/dto/update-course.dto.ts | 4 ++ .../courses/interviews/interviews.service.ts | 1 - nestjs/src/spec.json | 22 ++++++---- server/src/migrations/1712137476312-Course.ts | 13 ++++++ server/src/migrations/index.ts | 2 + server/src/models/course.ts | 3 ++ 12 files changed, 118 insertions(+), 18 deletions(-) create mode 100644 server/src/migrations/1712137476312-Course.ts diff --git a/client/specs/smoke.spec.ts b/client/specs/smoke.spec.ts index d1adcd3368..832cf0dee8 100644 --- a/client/specs/smoke.spec.ts +++ b/client/specs/smoke.spec.ts @@ -16,4 +16,19 @@ test.describe('Home', () => { const href = await page.locator('css=a >> text="Score"').getAttribute('href'); expect(href?.includes('/course/score')).toBeTruthy(); }); + + test('should navigate to Courses page and verify threshold', async ({ page }) => { + await page.click('span[role="img"][aria-label="menu-unfold"]'); + + await page.click('text=Admin Area'); + + const coursesLink = page.locator('text=Courses'); + await expect(coursesLink).toBeVisible(); + await coursesLink.click(); + + await page.getByRole('button', { name: /add course/i }).click(); + + const certificateThreshold = page.locator('input#certificateThreshold'); + await expect(certificateThreshold).toHaveValue('70'); + }); }); diff --git a/client/src/api/api.ts b/client/src/api/api.ts index 9f96c9ee81..c0b512e567 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -775,6 +775,12 @@ export interface CourseDto { * @memberof CourseDto */ 'minStudentsPerMentor': number; + /** + * + * @type {number} + * @memberof CourseDto + */ + 'certificateThreshold': number; } /** * @@ -1586,6 +1592,12 @@ export interface CreateCourseDto { * @memberof CreateCourseDto */ 'minStudentsPerMentor'?: number; + /** + * + * @type {number} + * @memberof CreateCourseDto + */ + 'certificateThreshold': number; } /** * @@ -4224,6 +4236,12 @@ export interface ProfileCourseDto { * @memberof ProfileCourseDto */ 'minStudentsPerMentor': number; + /** + * + * @type {number} + * @memberof ProfileCourseDto + */ + 'certificateThreshold': number; } /** * @@ -5803,6 +5821,12 @@ export interface UpdateCourseDto { * @memberof UpdateCourseDto */ 'minStudentsPerMentor'?: number; + /** + * + * @type {number} + * @memberof UpdateCourseDto + */ + 'certificateThreshold': number; } /** * diff --git a/client/src/modules/AdminDashboard/components/StudentsWithMentorsCard/StudentsWithMentorsCard.tsx b/client/src/modules/AdminDashboard/components/StudentsWithMentorsCard/StudentsWithMentorsCard.tsx index 0c2576eb18..947de357e4 100644 --- a/client/src/modules/AdminDashboard/components/StudentsWithMentorsCard/StudentsWithMentorsCard.tsx +++ b/client/src/modules/AdminDashboard/components/StudentsWithMentorsCard/StudentsWithMentorsCard.tsx @@ -15,12 +15,12 @@ export const StudentsWithMentorsCard = ({ studentsStats }: Props) => { return ( - Students With Mentor: {studentsStats.studentsWithMentorCount} / {studentsStats.totalStudents} + Students With Mentor: {studentsStats.studentsWithMentorCount} / {studentsStats.activeStudentsCount}
diff --git a/client/src/pages/admin/courses.tsx b/client/src/pages/admin/courses.tsx index 02262703cb..0572b2be26 100644 --- a/client/src/pages/admin/courses.tsx +++ b/client/src/pages/admin/courses.tsx @@ -239,13 +239,37 @@ function Page() { - - - + + + + + + + + + + + + @@ -310,6 +334,7 @@ function createRecord(values: any) { personalMentoring: values.personalMentoring, logo: values.logo, minStudentsPerMentor: values.minStudentsPerMentor, + certificateThreshold: values.certificateThreshold, }; return record; } @@ -390,6 +415,7 @@ function getInitialValues(modalData: Partial) { return { ...modalData, minStudentsPerMentor: modalData.minStudentsPerMentor || 2, + certificateThreshold: modalData.certificateThreshold ?? 70, inviteOnly: !!modalData.inviteOnly, state: modalData.completed ? 'completed' : modalData.planned ? 'planned' : 'active', registrationEndDate: modalData.registrationEndDate ? dayjs.utc(modalData.registrationEndDate) : null, diff --git a/nestjs/src/courses/dto/course.dto.ts b/nestjs/src/courses/dto/course.dto.ts index e89ae0b52d..5b2d1a2957 100644 --- a/nestjs/src/courses/dto/course.dto.ts +++ b/nestjs/src/courses/dto/course.dto.ts @@ -26,6 +26,7 @@ export class CourseDto { this.logo = course.logo; this.discipline = course.discipline ? { id: course.discipline.id, name: course.discipline.name } : null; this.minStudentsPerMentor = course.minStudentsPerMentor; + this.certificateThreshold = course.certificateThreshold; } @ApiProperty() @@ -102,4 +103,7 @@ export class CourseDto { @ApiProperty() minStudentsPerMentor: number; + + @ApiProperty() + certificateThreshold: number; } diff --git a/nestjs/src/courses/dto/create-course.dto.ts b/nestjs/src/courses/dto/create-course.dto.ts index 0d1b69524e..14845fe232 100644 --- a/nestjs/src/courses/dto/create-course.dto.ts +++ b/nestjs/src/courses/dto/create-course.dto.ts @@ -80,4 +80,8 @@ export class CreateCourseDto { @IsOptional() @ApiProperty({ required: false }) minStudentsPerMentor?: number; + + @IsNumber() + @ApiProperty({ required: true }) + certificateThreshold: number; } diff --git a/nestjs/src/courses/dto/update-course.dto.ts b/nestjs/src/courses/dto/update-course.dto.ts index 4e19da8c03..85c6e353a1 100644 --- a/nestjs/src/courses/dto/update-course.dto.ts +++ b/nestjs/src/courses/dto/update-course.dto.ts @@ -91,4 +91,8 @@ export class UpdateCourseDto { @IsOptional() @ApiPropertyOptional() minStudentsPerMentor?: number; + + @IsNumber() + @ApiProperty({ required: true }) + certificateThreshold: number; } diff --git a/nestjs/src/courses/interviews/interviews.service.ts b/nestjs/src/courses/interviews/interviews.service.ts index 790797a0d8..a606002a5b 100644 --- a/nestjs/src/courses/interviews/interviews.service.ts +++ b/nestjs/src/courses/interviews/interviews.service.ts @@ -78,7 +78,6 @@ export class InterviewsService { .where('is.courseId = :courseId', { courseId }) .andWhere('is.courseTaskId = :courseTaskId', { courseTaskId }) .andWhere('student.isExpelled = false') - .andWhere('student.isExpelled = false') .andWhere('taskChecker.id IS NULL') .orderBy('student.totalScore', 'DESC') .getMany(); diff --git a/nestjs/src/spec.json b/nestjs/src/spec.json index 869e6f3b3e..c3c7f32df5 100644 --- a/nestjs/src/spec.json +++ b/nestjs/src/spec.json @@ -2620,7 +2620,8 @@ "personalMentoring": { "type": "boolean" }, "logo": { "type": "string" }, "discipline": { "nullable": true, "allOf": [{ "$ref": "#/components/schemas/IdNameDto" }] }, - "minStudentsPerMentor": { "type": "number" } + "minStudentsPerMentor": { "type": "number" }, + "certificateThreshold": { "type": "number" } }, "required": [ "id", @@ -2647,7 +2648,8 @@ "personalMentoring", "logo", "discipline", - "minStudentsPerMentor" + "minStudentsPerMentor", + "certificateThreshold" ] }, "CreateCourseDto": { @@ -2669,9 +2671,10 @@ "certificateIssuer": { "type": "string" }, "personalMentoring": { "type": "boolean" }, "logo": { "type": "string" }, - "minStudentsPerMentor": { "type": "number" } + "minStudentsPerMentor": { "type": "number" }, + "certificateThreshold": { "type": "number" } }, - "required": ["name", "startDate", "endDate", "fullName", "alias", "description"] + "required": ["name", "startDate", "endDate", "fullName", "alias", "description", "certificateThreshold"] }, "UpdateCourseDto": { "type": "object", @@ -2694,9 +2697,10 @@ "personalMentoring": { "type": "boolean" }, "logo": { "type": "string" }, "disciplineId": { "type": "number" }, - "minStudentsPerMentor": { "type": "number" } + "minStudentsPerMentor": { "type": "number" }, + "certificateThreshold": { "type": "number" } }, - "required": ["name", "fullName", "alias"] + "required": ["name", "fullName", "alias", "certificateThreshold"] }, "LeaveCourseRequestDto": { "type": "object", "properties": { "comment": { "type": "string" } } }, "StudentDto": { @@ -3861,7 +3865,8 @@ "personalMentoring": { "type": "boolean" }, "logo": { "type": "string" }, "discipline": { "nullable": true, "allOf": [{ "$ref": "#/components/schemas/IdNameDto" }] }, - "minStudentsPerMentor": { "type": "number" } + "minStudentsPerMentor": { "type": "number" }, + "certificateThreshold": { "type": "number" } }, "required": [ "id", @@ -3888,7 +3893,8 @@ "personalMentoring", "logo", "discipline", - "minStudentsPerMentor" + "minStudentsPerMentor", + "certificateThreshold" ] }, "UpdateUserDto": { diff --git a/server/src/migrations/1712137476312-Course.ts b/server/src/migrations/1712137476312-Course.ts new file mode 100644 index 0000000000..e94690f92e --- /dev/null +++ b/server/src/migrations/1712137476312-Course.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Course1712137476312 implements MigrationInterface { + name = 'Course1712137476312'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "course" ADD "certificateThreshold" integer DEFAULT '70'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "course" DROP COLUMN "certificateThreshold"`); + } +} diff --git a/server/src/migrations/index.ts b/server/src/migrations/index.ts index ba7b7dbe8a..23b2ce78a6 100644 --- a/server/src/migrations/index.ts +++ b/server/src/migrations/index.ts @@ -54,6 +54,7 @@ import { InterviewScore1686657350908 } from './1686657350908-InterviewScore'; import { CourseUsersActivist1693930286280 } from './1693930286280-CourseUsersActivist'; import { AddMinStudentPerMentorColumnToCourse1699808604000 } from './1699808604000-AddMinStudentPerMentorColumnToCourse'; import { Obfuscation1700391857109 } from './1700391857109-Obfuscation'; +import { Course1712137476312 } from './1712137476312-Course'; export const migrations = [ UserMigration1630340371992, @@ -112,4 +113,5 @@ export const migrations = [ CourseUsersActivist1693930286280, AddMinStudentPerMentorColumnToCourse1699808604000, Obfuscation1700391857109, + Course1712137476312, ]; diff --git a/server/src/models/course.ts b/server/src/models/course.ts index cc1f3bf2e4..4169ba1d0f 100644 --- a/server/src/models/course.ts +++ b/server/src/models/course.ts @@ -108,4 +108,7 @@ export class Course { @Column({ default: 2, nullable: true }) minStudentsPerMentor: number; + + @Column({ default: 70 }) + certificateThreshold: number; }