From e98f903adbaebbdc1b9774154f19e7c3ca9791c2 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Tue, 28 May 2024 20:48:41 +0300 Subject: [PATCH 01/22] feat: implement empty auto-test page --- client/src/pages/admin/auto-test.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 client/src/pages/admin/auto-test.tsx diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx new file mode 100644 index 000000000..01530e756 --- /dev/null +++ b/client/src/pages/admin/auto-test.tsx @@ -0,0 +1,16 @@ +import { SessionProvider } from "modules/Course/contexts"; +import { CourseRole } from "services/models"; + +function Page() { + return <> + auto-test + +} +export default function () { + return ( + + + + ); + } + \ No newline at end of file From 8a4c79628f4a3cabe994a53efb22c48245bf2469 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Tue, 28 May 2024 20:59:20 +0300 Subject: [PATCH 02/22] feat: implement showing new page at admin area dropdown --- client/src/components/Sider/data/menuItems.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/src/components/Sider/data/menuItems.tsx b/client/src/components/Sider/data/menuItems.tsx index b14d7f3f0..d700b47db 100644 --- a/client/src/components/Sider/data/menuItems.tsx +++ b/client/src/components/Sider/data/menuItems.tsx @@ -9,6 +9,8 @@ import ProfileFilled from '@ant-design/icons/ProfileFilled'; import TeamOutlined from '@ant-design/icons/TeamOutlined'; import UserOutlined from '@ant-design/icons/UserOutlined'; import FileTextOutlined from '@ant-design/icons/FileTextOutlined'; +import ExclamationCircleOutlined from '@ant-design/icons/ExclamationCircleOutlined'; + import { DiscordOutlined } from 'components/Icons/DiscordOutlined'; import { Session } from 'components/withSession'; import { @@ -136,6 +138,13 @@ const adminMenuItems: AdminMenuItemsData[] = [ href: '/admin/prompts', access: session => isAdmin(session), }, + { + name: 'Auto tests', + key: 'auto-test', + icon: , + href: '/admin/auto-test', + access: session => isAdmin(session), + } ]; export function getAdminMenuItems(session: Session): MenuItemsRenderData[] { From d9c857f4d03bac00835d5d023dd581c5ff4c7581 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Wed, 29 May 2024 19:35:46 +0300 Subject: [PATCH 03/22] feat: implement page using admin page layout --- client/src/pages/admin/auto-test.tsx | 29 ++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx index 01530e756..5be202576 100644 --- a/client/src/pages/admin/auto-test.tsx +++ b/client/src/pages/admin/auto-test.tsx @@ -1,16 +1,21 @@ -import { SessionProvider } from "modules/Course/contexts"; -import { CourseRole } from "services/models"; +import { AdminPageLayout } from 'components/PageLayout'; +import { ActiveCourseProvider, SessionProvider, useActiveCourseContext } from 'modules/Course/contexts'; +import { CourseRole } from 'services/models'; function Page() { - return <> - auto-test - + const { courses } = useActiveCourseContext(); + return ( + + auto-test + + ); } export default function () { - return ( - - - - ); - } - \ No newline at end of file + return ( + + + + + + ); +} From df8da3a4714f53205fd6a0f2300ae3e0e41161ba Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Tue, 4 Jun 2024 19:15:20 +0300 Subject: [PATCH 04/22] feat: imeplement getAllRSSchoolAppTests api --- client/src/api/api.ts | 210 ++++++++++++++++++ nestjs/src/app.module.ts | 2 + nestjs/src/auto-test/auto-test.controller.ts | 20 ++ nestjs/src/auto-test/auto-test.module.ts | 12 + nestjs/src/auto-test/auto-test.service.ts | 24 ++ .../src/auto-test/dto/auto-test-task.dto.ts | 82 +++++++ nestjs/src/spec.json | 72 ++++++ 7 files changed, 422 insertions(+) create mode 100644 nestjs/src/auto-test/auto-test.controller.ts create mode 100644 nestjs/src/auto-test/auto-test.module.ts create mode 100644 nestjs/src/auto-test/auto-test.service.ts create mode 100644 nestjs/src/auto-test/dto/auto-test-task.dto.ts diff --git a/client/src/api/api.ts b/client/src/api/api.ts index fc54b3ea2..18da4be9c 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -288,6 +288,122 @@ export interface AuthConnectionDto { */ 'externalId': string; } +/** + * + * @export + * @interface AutoTestTaskDto + */ +export interface AutoTestTaskDto { + /** + * + * @type {string} + * @memberof AutoTestTaskDto + */ + 'type': AutoTestTaskDtoTypeEnum; + /** + * + * @type {string} + * @memberof AutoTestTaskDto + */ + 'name': string; + /** + * + * @type {number} + * @memberof AutoTestTaskDto + */ + 'id': number; + /** + * + * @type {string} + * @memberof AutoTestTaskDto + */ + 'descriptionUrl': string; + /** + * + * @type {string} + * @memberof AutoTestTaskDto + */ + 'description': string; + /** + * + * @type {string} + * @memberof AutoTestTaskDto + */ + 'githubRepoName': string; + /** + * + * @type {string} + * @memberof AutoTestTaskDto + */ + 'sourceGithubRepoUrl': string; + /** + * + * @type {IdNameDto} + * @memberof AutoTestTaskDto + */ + 'discipline': IdNameDto; + /** + * + * @type {boolean} + * @memberof AutoTestTaskDto + */ + 'githubPrRequired': boolean; + /** + * + * @type {string} + * @memberof AutoTestTaskDto + */ + 'createdDate': string; + /** + * + * @type {string} + * @memberof AutoTestTaskDto + */ + 'updatedDate': string; + /** + * + * @type {Array} + * @memberof AutoTestTaskDto + */ + 'tags': Array; + /** + * + * @type {Array} + * @memberof AutoTestTaskDto + */ + 'skills': Array; + /** + * + * @type {object} + * @memberof AutoTestTaskDto + */ + 'attributes': object; + /** + * + * @type {Array} + * @memberof AutoTestTaskDto + */ + 'courses': Array; +} + +export const AutoTestTaskDtoTypeEnum = { + Jstask: 'jstask', + Kotlintask: 'kotlintask', + Objctask: 'objctask', + Htmltask: 'htmltask', + Ipynb: 'ipynb', + Selfeducation: 'selfeducation', + Codewars: 'codewars', + Test: 'test', + Codejam: 'codejam', + Interview: 'interview', + StageInterview: 'stage-interview', + Cvhtml: 'cv:html', + Cvmarkdown: 'cv:markdown' +} as const; + +export type AutoTestTaskDtoTypeEnum = typeof AutoTestTaskDtoTypeEnum[keyof typeof AutoTestTaskDtoTypeEnum]; + /** * * @export @@ -7736,6 +7852,100 @@ export class AuthApi extends BaseAPI { } +/** + * AutoTestsApi - axios parameter creator + * @export + */ +export const AutoTestsApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAllRSSchoolAppTests: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/auto-test`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * AutoTestsApi - functional programming interface + * @export + */ +export const AutoTestsApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = AutoTestsApiAxiosParamCreator(configuration) + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getAllRSSchoolAppTests(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllRSSchoolAppTests(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * AutoTestsApi - factory interface + * @export + */ +export const AutoTestsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = AutoTestsApiFp(configuration) + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAllRSSchoolAppTests(options?: any): AxiosPromise> { + return localVarFp.getAllRSSchoolAppTests(options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * AutoTestsApi - object-oriented interface + * @export + * @class AutoTestsApi + * @extends {BaseAPI} + */ +export class AutoTestsApi extends BaseAPI { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AutoTestsApi + */ + public getAllRSSchoolAppTests(options?: AxiosRequestConfig) { + return AutoTestsApiFp(this.configuration).getAllRSSchoolAppTests(options).then((request) => request(this.axios, this.basePath)); + } +} + + /** * CertificateApi - axios parameter creator * @export diff --git a/nestjs/src/app.module.ts b/nestjs/src/app.module.ts index 9c6099b3c..e5e99f960 100644 --- a/nestjs/src/app.module.ts +++ b/nestjs/src/app.module.ts @@ -29,6 +29,7 @@ import { CloudApiModule } from './cloud-api/cloud-api.module'; import { EventsModule } from './events/events.module'; import { TasksModule } from './tasks/tasks.module'; import { PromptsModule } from './prompts/prompts.module'; +import { AutoTestModule } from './auto-test/auto-test.module'; @Module({ imports: [ @@ -62,6 +63,7 @@ import { PromptsModule } from './prompts/prompts.module'; EventsModule, TasksModule, PromptsModule, + AutoTestModule, ], controllers: [], providers: [Logger, ConfigService], diff --git a/nestjs/src/auto-test/auto-test.controller.ts b/nestjs/src/auto-test/auto-test.controller.ts new file mode 100644 index 000000000..df1615432 --- /dev/null +++ b/nestjs/src/auto-test/auto-test.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get } from '@nestjs/common'; +import { AutoTestService } from './auto-test.service'; +import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { CourseRole } from '@entities/session'; +import { RequiredRoles, Role } from 'src/auth'; +import { AutoTestTaskDto } from './dto/auto-test-task.dto'; + +@Controller('auto-test') +@ApiTags('auto-tests') +export class AutoTestController { + constructor(private readonly service: AutoTestService) {} + + @Get() + @RequiredRoles([Role.Admin, CourseRole.Manager]) + @ApiOperation({ operationId: 'getAllRSSchoolAppTests' }) + @ApiOkResponse({ type: [AutoTestTaskDto] }) + async getAllRSSchoolAppTests() { + return (await this.service.getAll()).map(autoTest => new AutoTestTaskDto(autoTest)); + } +} diff --git a/nestjs/src/auto-test/auto-test.module.ts b/nestjs/src/auto-test/auto-test.module.ts new file mode 100644 index 000000000..33c4f3935 --- /dev/null +++ b/nestjs/src/auto-test/auto-test.module.ts @@ -0,0 +1,12 @@ +import { Task } from '@entities/index'; +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { AutoTestController } from './auto-test.controller'; +import { AutoTestService } from './auto-test.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Task])], + controllers: [AutoTestController], + providers: [AutoTestService], +}) +export class AutoTestModule {} diff --git a/nestjs/src/auto-test/auto-test.service.ts b/nestjs/src/auto-test/auto-test.service.ts new file mode 100644 index 000000000..c12764f2d --- /dev/null +++ b/nestjs/src/auto-test/auto-test.service.ts @@ -0,0 +1,24 @@ +import { Task, TaskType } from '@entities/task'; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +@Injectable() +export class AutoTestService { + constructor(@InjectRepository(Task) private repository: Repository) {} + + public async getAll() { + return this.repository.find({ + where: { + type: TaskType.SelfEducation, + }, + relations: { + discipline: true, + courseTasks: { course: true }, + }, + order: { + updatedDate: 'DESC', + }, + }); + } +} diff --git a/nestjs/src/auto-test/dto/auto-test-task.dto.ts b/nestjs/src/auto-test/dto/auto-test-task.dto.ts new file mode 100644 index 000000000..d0bd9261d --- /dev/null +++ b/nestjs/src/auto-test/dto/auto-test-task.dto.ts @@ -0,0 +1,82 @@ +import { Task, TaskType } from '@entities/task'; +import { ApiProperty } from '@nestjs/swagger'; +import { uniqBy } from 'lodash'; +import { IdNameDto } from 'src/core/dto'; +import { UsedCourseDto } from 'src/courses/dto/used-course.dto'; + +export class AutoTestTaskDto { + constructor(task: Task) { + this.id = task.id; + this.name = task.name; + this.type = task.type; + this.descriptionUrl = task.descriptionUrl; + this.description = task.description; + this.githubRepoName = task.githubRepoName; + this.sourceGithubRepoUrl = task.sourceGithubRepoUrl; + this.discipline = task.discipline ? new IdNameDto(task.discipline) : null; + this.courses = task.courseTasks + ? uniqBy( + task.courseTasks + .filter(task => !task.disabled) + .map(({ course }) => new UsedCourseDto({ name: course.name, isActive: !course.completed })), + course => course.name, + ).sort((a, b) => { + if (a.isActive === b.isActive) { + return a.name.localeCompare(b.name); + } + return Number(b.isActive) - Number(a.isActive); + }) + : []; + this.githubPrRequired = task.githubPrRequired; + this.tags = task.tags; + this.skills = task.skills; + this.attributes = task.attributes; + this.createdDate = task.createdDate; + this.updatedDate = task.updatedDate; + } + + @ApiProperty({ enum: TaskType }) + public type: TaskType; + + @ApiProperty() + public name: string; + + @ApiProperty() + public id: number; + + @ApiProperty() + public descriptionUrl: string; + + @ApiProperty() + public description: string; + + @ApiProperty() + public githubRepoName: string; + + @ApiProperty() + public sourceGithubRepoUrl: string; + + @ApiProperty({ type: IdNameDto }) + public discipline: IdNameDto | null; + + @ApiProperty() + public githubPrRequired: boolean; + + @ApiProperty() + public createdDate: string; + + @ApiProperty() + public updatedDate: string; + + @ApiProperty() + public tags: string[]; + + @ApiProperty() + public skills: string[]; + + @ApiProperty() + public attributes: Record; + + @ApiProperty({ type: [UsedCourseDto] }) + public courses: UsedCourseDto[]; +} diff --git a/nestjs/src/spec.json b/nestjs/src/spec.json index 9b9812bf3..24d0594ac 100644 --- a/nestjs/src/spec.json +++ b/nestjs/src/spec.json @@ -2484,6 +2484,24 @@ }, "tags": ["prompts"] } + }, + "/auto-test": { + "get": { + "operationId": "getAllRSSchoolAppTests", + "summary": "", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { "type": "array", "items": { "$ref": "#/components/schemas/AutoTestTaskDto" } } + } + } + } + }, + "tags": ["auto-tests"] + } } }, "info": { "title": "", "description": "", "version": "1.0.0", "contact": {} }, @@ -4808,6 +4826,60 @@ "text": { "type": "string" } }, "required": ["temperature", "type", "text"] + }, + "AutoTestTaskDto": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "jstask", + "kotlintask", + "objctask", + "htmltask", + "ipynb", + "selfeducation", + "codewars", + "test", + "codejam", + "interview", + "stage-interview", + "cv:html", + "cv:markdown" + ] + }, + "name": { "type": "string" }, + "id": { "type": "number" }, + "descriptionUrl": { "type": "string" }, + "description": { "type": "string" }, + "githubRepoName": { "type": "string" }, + "sourceGithubRepoUrl": { "type": "string" }, + "discipline": { "$ref": "#/components/schemas/IdNameDto" }, + "githubPrRequired": { "type": "boolean" }, + "createdDate": { "type": "string" }, + "updatedDate": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } }, + "skills": { "type": "array", "items": { "type": "string" } }, + "attributes": { "type": "object" }, + "courses": { "type": "array", "items": { "$ref": "#/components/schemas/UsedCourseDto" } } + }, + "required": [ + "type", + "name", + "id", + "descriptionUrl", + "description", + "githubRepoName", + "sourceGithubRepoUrl", + "discipline", + "githubPrRequired", + "createdDate", + "updatedDate", + "tags", + "skills", + "attributes", + "courses" + ] } } } From 025fc4d3557b37951d8780d8d90478c9d76fa86d Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Sun, 9 Jun 2024 13:48:10 +0300 Subject: [PATCH 05/22] feat: implement previewing short info about auto-test tasks --- .../AutoTestTaskCard/AutoTestTaskCard.tsx | 65 +++++++++++++++++++ client/src/pages/admin/auto-test.tsx | 32 ++++++++- .../src/auto-test/dto/auto-test-task.dto.ts | 2 +- nestjs/src/auto-test/dto/task-solution.dto.ts | 26 ++++++++ nestjs/src/spec.json | 2 +- 5 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx create mode 100644 nestjs/src/auto-test/dto/task-solution.dto.ts diff --git a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx new file mode 100644 index 000000000..737c62e81 --- /dev/null +++ b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx @@ -0,0 +1,65 @@ +import { Button, Card, Col, Divider, Row, Typography } from 'antd'; +import { TaskCardColumn } from '..'; +import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'; +import { AutoTestTaskDto } from 'api'; + +const { Paragraph } = Typography; + +export interface AutoTestTaskCardProps { + courseTask: AutoTestTaskDto +} + +function AutoTestTaskCard({ courseTask }: AutoTestTaskCardProps) { + const columns = [ + { + label: 'Max attempts number', + value: courseTask.attributes?.public?.maxAttemptsNumber ?? <>–, + }, + { + label: 'Number of Questions', + value: courseTask.attributes?.public?.numberOfQuestions ?? <>–, + }, + { + label: 'Strict attempts mode', + value: courseTask.attributes?.public?.strictAttemptsMode ? ( + + ) : ( + + ), + }, + { + label: 'Threshold percentage', + value: courseTask.attributes?.public?.tresholdPercentage ?? <>–, + }, + ]; + + return ( + + + + + {' ' + courseTask.name} + + + + + + + + + {columns.map(item => ( + + + + ))} + + + ); +} + +export default AutoTestTaskCard; diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx index 5be202576..b1d0f3f5f 100644 --- a/client/src/pages/admin/auto-test.tsx +++ b/client/src/pages/admin/auto-test.tsx @@ -1,12 +1,42 @@ +import { Col, ColProps, Row } from 'antd'; +import { AutoTestTaskDto, AutoTestsApi } from 'api'; import { AdminPageLayout } from 'components/PageLayout'; +import { TaskCard } from 'modules/AutoTest/components'; +import AutoTestTaskCard from 'modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard'; import { ActiveCourseProvider, SessionProvider, useActiveCourseContext } from 'modules/Course/contexts'; +import { useEffect, useState } from 'react'; import { CourseRole } from 'services/models'; +const style = { + background: '#0092ff', + padding: '8px 0', + }; + + const RESPONSIVE_COLUMNS: ColProps = { + sm: 24, + md: 12, + lg: 8, + xl: 8, + xxl: 6, + }; + function Page() { const { courses } = useActiveCourseContext(); + const [tests, setTests] = useState([]); + useEffect(() => { + const api = new AutoTestsApi(); + api.getAllRSSchoolAppTests().then(tests => setTests(tests.data)); + }, []); return ( - auto-test + + {tests.map(courseTask => ( + + + + ))} + + {tests.length ?
{JSON.stringify(tests, null, 4)}
:

no any tests

}
); } diff --git a/nestjs/src/auto-test/dto/auto-test-task.dto.ts b/nestjs/src/auto-test/dto/auto-test-task.dto.ts index d0bd9261d..fb89fb2da 100644 --- a/nestjs/src/auto-test/dto/auto-test-task.dto.ts +++ b/nestjs/src/auto-test/dto/auto-test-task.dto.ts @@ -74,7 +74,7 @@ export class AutoTestTaskDto { @ApiProperty() public skills: string[]; - @ApiProperty() + @ApiProperty({ type: JSON }) public attributes: Record; @ApiProperty({ type: [UsedCourseDto] }) diff --git a/nestjs/src/auto-test/dto/task-solution.dto.ts b/nestjs/src/auto-test/dto/task-solution.dto.ts new file mode 100644 index 000000000..b632f92c7 --- /dev/null +++ b/nestjs/src/auto-test/dto/task-solution.dto.ts @@ -0,0 +1,26 @@ +import { TaskSolution } from '@entities/taskSolution'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; + +export class TaskSolutionDto { + constructor(taskSolution: TaskSolution) { + this.id = taskSolution.id; + this.url = taskSolution.url; + this.courseTaskId = taskSolution.courseTaskId; + } + + @ApiProperty() + @IsNotEmpty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNotEmpty() + @IsNumber() + courseTaskId: number; + + @ApiProperty() + @IsNotEmpty() + @IsString() + url: string; +} diff --git a/nestjs/src/spec.json b/nestjs/src/spec.json index 24d0594ac..527e64fda 100644 --- a/nestjs/src/spec.json +++ b/nestjs/src/spec.json @@ -4860,7 +4860,7 @@ "updatedDate": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } }, "skills": { "type": "array", "items": { "type": "string" } }, - "attributes": { "type": "object" }, + "attributes": { "type": "object", "properties": {} }, "courses": { "type": "array", "items": { "$ref": "#/components/schemas/UsedCourseDto" } } }, "required": [ From 33cc1f5a6d6743c156235f1d8b0ef96df0387bb2 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Mon, 10 Jun 2024 20:02:15 +0300 Subject: [PATCH 06/22] refactor: remove unnecessary code --- client/src/pages/admin/auto-test.tsx | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx index b1d0f3f5f..6e06655d7 100644 --- a/client/src/pages/admin/auto-test.tsx +++ b/client/src/pages/admin/auto-test.tsx @@ -1,25 +1,19 @@ import { Col, ColProps, Row } from 'antd'; import { AutoTestTaskDto, AutoTestsApi } from 'api'; import { AdminPageLayout } from 'components/PageLayout'; -import { TaskCard } from 'modules/AutoTest/components'; import AutoTestTaskCard from 'modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard'; import { ActiveCourseProvider, SessionProvider, useActiveCourseContext } from 'modules/Course/contexts'; import { useEffect, useState } from 'react'; import { CourseRole } from 'services/models'; -const style = { - background: '#0092ff', - padding: '8px 0', - }; +const RESPONSIVE_COLUMNS: ColProps = { + sm: 24, + md: 12, + lg: 8, + xl: 8, + xxl: 6, +}; - const RESPONSIVE_COLUMNS: ColProps = { - sm: 24, - md: 12, - lg: 8, - xl: 8, - xxl: 6, - }; - function Page() { const { courses } = useActiveCourseContext(); const [tests, setTests] = useState([]); @@ -32,10 +26,10 @@ function Page() { {tests.map(courseTask => ( - + ))} - + {tests.length ?
{JSON.stringify(tests, null, 4)}
:

no any tests

} ); From 423866dd633d77520deb235f9db047dd4f286964 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Mon, 10 Jun 2024 20:14:23 +0300 Subject: [PATCH 07/22] fix: fix type error --- client/src/api/api.ts | 4 ++-- nestjs/src/auto-test/dto/auto-test-task.dto.ts | 2 +- nestjs/src/spec.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/api/api.ts b/client/src/api/api.ts index 18da4be9c..3979cec0d 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -374,10 +374,10 @@ export interface AutoTestTaskDto { 'skills': Array; /** * - * @type {object} + * @type {{ [key: string]: Any; }} * @memberof AutoTestTaskDto */ - 'attributes': object; + 'attributes': { [key: string]: Any; }; /** * * @type {Array} diff --git a/nestjs/src/auto-test/dto/auto-test-task.dto.ts b/nestjs/src/auto-test/dto/auto-test-task.dto.ts index fb89fb2da..136aec919 100644 --- a/nestjs/src/auto-test/dto/auto-test-task.dto.ts +++ b/nestjs/src/auto-test/dto/auto-test-task.dto.ts @@ -74,7 +74,7 @@ export class AutoTestTaskDto { @ApiProperty() public skills: string[]; - @ApiProperty({ type: JSON }) + @ApiProperty({ type: 'object', additionalProperties: { type: 'any' } }) public attributes: Record; @ApiProperty({ type: [UsedCourseDto] }) diff --git a/nestjs/src/spec.json b/nestjs/src/spec.json index 527e64fda..cd1ee6c36 100644 --- a/nestjs/src/spec.json +++ b/nestjs/src/spec.json @@ -4860,7 +4860,7 @@ "updatedDate": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } }, "skills": { "type": "array", "items": { "type": "string" } }, - "attributes": { "type": "object", "properties": {} }, + "attributes": { "type": "object", "additionalProperties": { "type": "any" } }, "courses": { "type": "array", "items": { "$ref": "#/components/schemas/UsedCourseDto" } } }, "required": [ From a1079f357ef9b21586eb7a3af134284424eeffcc Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Tue, 11 Jun 2024 19:41:15 +0300 Subject: [PATCH 08/22] feat: implement test questions preview --- .../AutoTestTaskCard/AutoTestTaskCard.tsx | 9 +- client/src/pages/admin/auto-test.tsx | 86 ++++++++++++++++++- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx index 737c62e81..29538e4d7 100644 --- a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx +++ b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx @@ -6,10 +6,11 @@ import { AutoTestTaskDto } from 'api'; const { Paragraph } = Typography; export interface AutoTestTaskCardProps { - courseTask: AutoTestTaskDto + courseTask: AutoTestTaskDto; + handleSelectTask: (task: AutoTestTaskDto) => void; } -function AutoTestTaskCard({ courseTask }: AutoTestTaskCardProps) { +function AutoTestTaskCard({ courseTask, handleSelectTask }: AutoTestTaskCardProps) { const columns = [ { label: 'Max attempts number', @@ -47,7 +48,9 @@ function AutoTestTaskCard({ courseTask }: AutoTestTaskCardProps) { - + diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx index 6e06655d7..a5b794f23 100644 --- a/client/src/pages/admin/auto-test.tsx +++ b/client/src/pages/admin/auto-test.tsx @@ -1,4 +1,4 @@ -import { Col, ColProps, Row } from 'antd'; +import { Col, ColProps, Descriptions, Modal, Row, Switch, Tag, Typography, Divider, List, Checkbox } from 'antd'; import { AutoTestTaskDto, AutoTestsApi } from 'api'; import { AdminPageLayout } from 'components/PageLayout'; import AutoTestTaskCard from 'modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard'; @@ -17,6 +17,21 @@ const RESPONSIVE_COLUMNS: ColProps = { function Page() { const { courses } = useActiveCourseContext(); const [tests, setTests] = useState([]); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedTask, setSelectedTask] = useState(); + + const handleOk = () => { + setIsModalOpen(false); + }; + const handleCancel = () => { + setIsModalOpen(false); + }; + + const handleSelectTask = (task: AutoTestTaskDto) => { + setSelectedTask(task); + setIsModalOpen(true); + }; + useEffect(() => { const api = new AutoTestsApi(); api.getAllRSSchoolAppTests().then(tests => setTests(tests.data)); @@ -26,11 +41,76 @@ function Page() { {tests.map(courseTask => ( - + ))} - {tests.length ?
{JSON.stringify(tests, null, 4)}
:

no any tests

} + + + {selectedTask?.id} + {selectedTask?.type} + + {selectedTask?.descriptionUrl} + + {selectedTask?.discipline?.name} + + {selectedTask?.courses.map(course => ( + + {course.name} + + ))} + + + + + + {selectedTask?.tags.map(tag => ( + + {tag} + + ))} + + + + {selectedTask?.attributes?.public?.questions && ( + <> + + + + {selectedTask?.attributes?.public?.maxAttemptsNumber} + + + {selectedTask?.attributes?.public?.numberOfQuestions} + + + + + + {selectedTask?.attributes?.public?.tresholdPercentage} + + + , index) => ( + + ( + + {answer} + + ))} + /> + + )} + /> + + )} + ); } From eec3f9df06457ae2ea305ea837d8712984d30571 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Wed, 12 Jun 2024 19:26:16 +0300 Subject: [PATCH 09/22] fix: visual fixes --- .../AutoTestTaskCard/AutoTestTaskCard.tsx | 9 +--- client/src/pages/admin/auto-test.tsx | 50 ++++++++++++------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx index 29538e4d7..7894ecb6f 100644 --- a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx +++ b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx @@ -1,6 +1,5 @@ -import { Button, Card, Col, Divider, Row, Typography } from 'antd'; +import { Button, Card, Col, Divider, Row, Typography, Switch } from 'antd'; import { TaskCardColumn } from '..'; -import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'; import { AutoTestTaskDto } from 'api'; const { Paragraph } = Typography; @@ -22,11 +21,7 @@ function AutoTestTaskCard({ courseTask, handleSelectTask }: AutoTestTaskCardProp }, { label: 'Strict attempts mode', - value: courseTask.attributes?.public?.strictAttemptsMode ? ( - - ) : ( - - ), + value: , }, { label: 'Threshold percentage', diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx index a5b794f23..b83977786 100644 --- a/client/src/pages/admin/auto-test.tsx +++ b/client/src/pages/admin/auto-test.tsx @@ -1,6 +1,7 @@ import { Col, ColProps, Descriptions, Modal, Row, Switch, Tag, Typography, Divider, List, Checkbox } from 'antd'; import { AutoTestTaskDto, AutoTestsApi } from 'api'; import { AdminPageLayout } from 'components/PageLayout'; +import { TASK_TYPES_MAP } from 'data/taskTypes'; import AutoTestTaskCard from 'modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard'; import { ActiveCourseProvider, SessionProvider, useActiveCourseContext } from 'modules/Course/contexts'; import { useEffect, useState } from 'react'; @@ -48,28 +49,39 @@ function Page() { {selectedTask?.id} - {selectedTask?.type} - - {selectedTask?.descriptionUrl} - - {selectedTask?.discipline?.name} - - {selectedTask?.courses.map(course => ( - - {course.name} - - ))} - + {selectedTask?.type && ( + {TASK_TYPES_MAP[selectedTask?.type]} + )} + {selectedTask?.descriptionUrl && ( + + {selectedTask?.descriptionUrl} + + )} + {selectedTask?.discipline?.name && ( + {selectedTask?.discipline?.name} + )} + {selectedTask?.courses?.length && ( + + {selectedTask?.courses.map(course => ( + + {course.name} + + ))} + + )} + - - {selectedTask?.tags.map(tag => ( - - {tag} - - ))} - + {selectedTask?.tags && ( + + {selectedTask?.tags.map(tag => ( + + {tag} + + ))} + + )} {selectedTask?.attributes?.public?.questions && ( From 089fa5bd5a00898357d6259dd587f50ebaed506e Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Thu, 13 Jun 2024 20:01:01 +0300 Subject: [PATCH 10/22] refactor: refactor data loading logic --- client/src/pages/admin/auto-test.tsx | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx index b83977786..02cc016a4 100644 --- a/client/src/pages/admin/auto-test.tsx +++ b/client/src/pages/admin/auto-test.tsx @@ -1,10 +1,11 @@ -import { Col, ColProps, Descriptions, Modal, Row, Switch, Tag, Typography, Divider, List, Checkbox } from 'antd'; +import { Col, ColProps, Descriptions, Modal, Row, Switch, Tag, Typography, Divider, List, Checkbox, message } from 'antd'; import { AutoTestTaskDto, AutoTestsApi } from 'api'; import { AdminPageLayout } from 'components/PageLayout'; import { TASK_TYPES_MAP } from 'data/taskTypes'; import AutoTestTaskCard from 'modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard'; import { ActiveCourseProvider, SessionProvider, useActiveCourseContext } from 'modules/Course/contexts'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; +import { useAsync } from 'react-use'; import { CourseRole } from 'services/models'; const RESPONSIVE_COLUMNS: ColProps = { @@ -15,11 +16,14 @@ const RESPONSIVE_COLUMNS: ColProps = { xxl: 6, }; +const api = new AutoTestsApi(); + function Page() { const { courses } = useActiveCourseContext(); const [tests, setTests] = useState([]); const [isModalOpen, setIsModalOpen] = useState(false); const [selectedTask, setSelectedTask] = useState(); + const [isLoading, setIsLoading] = useState(false); const handleOk = () => { setIsModalOpen(false); @@ -33,12 +37,19 @@ function Page() { setIsModalOpen(true); }; - useEffect(() => { - const api = new AutoTestsApi(); - api.getAllRSSchoolAppTests().then(tests => setTests(tests.data)); - }, []); + useAsync(async () => { + try { + setIsLoading(true) + const resp = await api.getAllRSSchoolAppTests() + setTests(resp.data); + setIsLoading(false) + } catch(e) { + message.error('Something went wrong. Please try again later.'); + } + }) + return ( - + {tests.map(courseTask => ( From 747acb7ccfe47ee7ec890da37906af49ed7337e2 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Thu, 13 Jun 2024 20:10:39 +0300 Subject: [PATCH 11/22] refactor: fix eslint issues --- .../src/components/Sider/data/menuItems.tsx | 2 +- client/src/pages/admin/auto-test.tsx | 25 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/client/src/components/Sider/data/menuItems.tsx b/client/src/components/Sider/data/menuItems.tsx index d700b47db..d11a090ec 100644 --- a/client/src/components/Sider/data/menuItems.tsx +++ b/client/src/components/Sider/data/menuItems.tsx @@ -144,7 +144,7 @@ const adminMenuItems: AdminMenuItemsData[] = [ icon: , href: '/admin/auto-test', access: session => isAdmin(session), - } + }, ]; export function getAdminMenuItems(session: Session): MenuItemsRenderData[] { diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx index 02cc016a4..49b39da2e 100644 --- a/client/src/pages/admin/auto-test.tsx +++ b/client/src/pages/admin/auto-test.tsx @@ -1,4 +1,17 @@ -import { Col, ColProps, Descriptions, Modal, Row, Switch, Tag, Typography, Divider, List, Checkbox, message } from 'antd'; +import { + Col, + ColProps, + Descriptions, + Modal, + Row, + Switch, + Tag, + Typography, + Divider, + List, + Checkbox, + message, +} from 'antd'; import { AutoTestTaskDto, AutoTestsApi } from 'api'; import { AdminPageLayout } from 'components/PageLayout'; import { TASK_TYPES_MAP } from 'data/taskTypes'; @@ -39,14 +52,14 @@ function Page() { useAsync(async () => { try { - setIsLoading(true) - const resp = await api.getAllRSSchoolAppTests() + setIsLoading(true); + const resp = await api.getAllRSSchoolAppTests(); setTests(resp.data); - setIsLoading(false) - } catch(e) { + setIsLoading(false); + } catch (e) { message.error('Something went wrong. Please try again later.'); } - }) + }); return ( From 4a09fad1dff72bc297e3fcb1511b41cd9eedf1f1 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Thu, 13 Jun 2024 20:24:33 +0300 Subject: [PATCH 12/22] fix: openapi build issue --- client/src/api/api.ts | 4 ++-- nestjs/src/auto-test/dto/auto-test-task.dto.ts | 4 ++-- nestjs/src/spec.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/api/api.ts b/client/src/api/api.ts index 3979cec0d..7d3f8b0e7 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -374,10 +374,10 @@ export interface AutoTestTaskDto { 'skills': Array; /** * - * @type {{ [key: string]: Any; }} + * @type {{ [key: string]: Unknown; }} * @memberof AutoTestTaskDto */ - 'attributes': { [key: string]: Any; }; + 'attributes': { [key: string]: Unknown; }; /** * * @type {Array} diff --git a/nestjs/src/auto-test/dto/auto-test-task.dto.ts b/nestjs/src/auto-test/dto/auto-test-task.dto.ts index 136aec919..06ba15fc4 100644 --- a/nestjs/src/auto-test/dto/auto-test-task.dto.ts +++ b/nestjs/src/auto-test/dto/auto-test-task.dto.ts @@ -74,8 +74,8 @@ export class AutoTestTaskDto { @ApiProperty() public skills: string[]; - @ApiProperty({ type: 'object', additionalProperties: { type: 'any' } }) - public attributes: Record; + @ApiProperty({ type: 'object', additionalProperties: { type: 'unknown' } }) + public attributes: Record; @ApiProperty({ type: [UsedCourseDto] }) public courses: UsedCourseDto[]; diff --git a/nestjs/src/spec.json b/nestjs/src/spec.json index cd1ee6c36..1892257a3 100644 --- a/nestjs/src/spec.json +++ b/nestjs/src/spec.json @@ -4860,7 +4860,7 @@ "updatedDate": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } }, "skills": { "type": "array", "items": { "type": "string" } }, - "attributes": { "type": "object", "additionalProperties": { "type": "any" } }, + "attributes": { "type": "object", "additionalProperties": { "type": "unknown" } }, "courses": { "type": "array", "items": { "$ref": "#/components/schemas/UsedCourseDto" } } }, "required": [ From 9dded121a9aa9a858ac4b057bad719db3bc3c362 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Tue, 18 Jun 2024 20:34:44 +0300 Subject: [PATCH 13/22] fix: fix type error for attributes field --- client/src/api/api.ts | 4 ++-- .../components/AutoTestTaskCard/AutoTestTaskCard.tsx | 7 +++++-- client/src/pages/admin/auto-test.tsx | 6 +++--- nestjs/src/auto-test/dto/auto-test-task.dto.ts | 4 ++-- nestjs/src/spec.json | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/client/src/api/api.ts b/client/src/api/api.ts index 7d3f8b0e7..18da4be9c 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -374,10 +374,10 @@ export interface AutoTestTaskDto { 'skills': Array; /** * - * @type {{ [key: string]: Unknown; }} + * @type {object} * @memberof AutoTestTaskDto */ - 'attributes': { [key: string]: Unknown; }; + 'attributes': object; /** * * @type {Array} diff --git a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx index 7894ecb6f..f5ffe7333 100644 --- a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx +++ b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx @@ -4,9 +4,12 @@ import { AutoTestTaskDto } from 'api'; const { Paragraph } = Typography; +export interface AutoTestTask extends AutoTestTaskDto { + attributes: Record; +} export interface AutoTestTaskCardProps { - courseTask: AutoTestTaskDto; - handleSelectTask: (task: AutoTestTaskDto) => void; + courseTask: AutoTestTask; + handleSelectTask: (task: AutoTestTask) => void; } function AutoTestTaskCard({ courseTask, handleSelectTask }: AutoTestTaskCardProps) { diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx index 49b39da2e..140f0ec37 100644 --- a/client/src/pages/admin/auto-test.tsx +++ b/client/src/pages/admin/auto-test.tsx @@ -15,7 +15,7 @@ import { import { AutoTestTaskDto, AutoTestsApi } from 'api'; import { AdminPageLayout } from 'components/PageLayout'; import { TASK_TYPES_MAP } from 'data/taskTypes'; -import AutoTestTaskCard from 'modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard'; +import AutoTestTaskCard, { AutoTestTask } from 'modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard'; import { ActiveCourseProvider, SessionProvider, useActiveCourseContext } from 'modules/Course/contexts'; import { useState } from 'react'; import { useAsync } from 'react-use'; @@ -33,9 +33,9 @@ const api = new AutoTestsApi(); function Page() { const { courses } = useActiveCourseContext(); - const [tests, setTests] = useState([]); + const [tests, setTests] = useState([]); const [isModalOpen, setIsModalOpen] = useState(false); - const [selectedTask, setSelectedTask] = useState(); + const [selectedTask, setSelectedTask] = useState(); const [isLoading, setIsLoading] = useState(false); const handleOk = () => { diff --git a/nestjs/src/auto-test/dto/auto-test-task.dto.ts b/nestjs/src/auto-test/dto/auto-test-task.dto.ts index 06ba15fc4..d0bd9261d 100644 --- a/nestjs/src/auto-test/dto/auto-test-task.dto.ts +++ b/nestjs/src/auto-test/dto/auto-test-task.dto.ts @@ -74,8 +74,8 @@ export class AutoTestTaskDto { @ApiProperty() public skills: string[]; - @ApiProperty({ type: 'object', additionalProperties: { type: 'unknown' } }) - public attributes: Record; + @ApiProperty() + public attributes: Record; @ApiProperty({ type: [UsedCourseDto] }) public courses: UsedCourseDto[]; diff --git a/nestjs/src/spec.json b/nestjs/src/spec.json index 1892257a3..24d0594ac 100644 --- a/nestjs/src/spec.json +++ b/nestjs/src/spec.json @@ -4860,7 +4860,7 @@ "updatedDate": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } }, "skills": { "type": "array", "items": { "type": "string" } }, - "attributes": { "type": "object", "additionalProperties": { "type": "unknown" } }, + "attributes": { "type": "object" }, "courses": { "type": "array", "items": { "$ref": "#/components/schemas/UsedCourseDto" } } }, "required": [ From abcbc28027a0ce3a09298db2dd79efe1b82963ea Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Sun, 7 Jul 2024 17:13:50 +0300 Subject: [PATCH 14/22] feact: implement previewing task on a new page --- client/src/api/api.ts | 120 ++++++++++++++- .../AutoTestTaskCard/AutoTestTaskCard.tsx | 25 ++-- client/src/pages/admin/auto-test-task.tsx | 138 ++++++++++++++++++ client/src/pages/admin/auto-test.tsx | 119 +-------------- nestjs/src/auto-test/auto-test.controller.ts | 23 ++- nestjs/src/auto-test/auto-test.service.ts | 17 +++ .../dto/minimized-auto-test-task.dto.ts | 31 ++++ nestjs/src/spec.json | 37 ++++- 8 files changed, 370 insertions(+), 140 deletions(-) create mode 100644 client/src/pages/admin/auto-test-task.tsx create mode 100644 nestjs/src/auto-test/dto/minimized-auto-test-task.dto.ts diff --git a/client/src/api/api.ts b/client/src/api/api.ts index 18da4be9c..aaea0c457 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -3916,6 +3916,49 @@ export interface MentorStudentDto { */ 'repoUrl': string | null; } +/** + * + * @export + * @interface MinimizedAutoTestTaskDto + */ +export interface MinimizedAutoTestTaskDto { + /** + * + * @type {number} + * @memberof MinimizedAutoTestTaskDto + */ + 'maxAttemptsNumber': number; + /** + * + * @type {number} + * @memberof MinimizedAutoTestTaskDto + */ + 'numberOfQuestions': number; + /** + * + * @type {string} + * @memberof MinimizedAutoTestTaskDto + */ + 'name': string; + /** + * + * @type {number} + * @memberof MinimizedAutoTestTaskDto + */ + 'id': number; + /** + * + * @type {boolean} + * @memberof MinimizedAutoTestTaskDto + */ + 'strictAttemptsMode': boolean; + /** + * + * @type {number} + * @memberof MinimizedAutoTestTaskDto + */ + 'thresholdPercentage': number; +} /** * * @export @@ -7863,7 +7906,7 @@ export const AutoTestsApiAxiosParamCreator = function (configuration?: Configura * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllRSSchoolAppTests: async (options: AxiosRequestConfig = {}): Promise => { + getAllMinimizedRSSchoolAppTests: async (options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/auto-test`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -7878,6 +7921,39 @@ export const AutoTestsApiAxiosParamCreator = function (configuration?: Configura + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {number} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getRSSchoolAppTest: async (id: number, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('getRSSchoolAppTest', 'id', id) + const localVarPath = `/auto-test/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -7902,8 +7978,18 @@ export const AutoTestsApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllRSSchoolAppTests(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAllRSSchoolAppTests(options); + async getAllMinimizedRSSchoolAppTests(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllMinimizedRSSchoolAppTests(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {number} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getRSSchoolAppTest(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getRSSchoolAppTest(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -7921,8 +8007,17 @@ export const AutoTestsApiFactory = function (configuration?: Configuration, base * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllRSSchoolAppTests(options?: any): AxiosPromise> { - return localVarFp.getAllRSSchoolAppTests(options).then((request) => request(axios, basePath)); + getAllMinimizedRSSchoolAppTests(options?: any): AxiosPromise> { + return localVarFp.getAllMinimizedRSSchoolAppTests(options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {number} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getRSSchoolAppTest(id: number, options?: any): AxiosPromise { + return localVarFp.getRSSchoolAppTest(id, options).then((request) => request(axios, basePath)); }, }; }; @@ -7940,8 +8035,19 @@ export class AutoTestsApi extends BaseAPI { * @throws {RequiredError} * @memberof AutoTestsApi */ - public getAllRSSchoolAppTests(options?: AxiosRequestConfig) { - return AutoTestsApiFp(this.configuration).getAllRSSchoolAppTests(options).then((request) => request(this.axios, this.basePath)); + public getAllMinimizedRSSchoolAppTests(options?: AxiosRequestConfig) { + return AutoTestsApiFp(this.configuration).getAllMinimizedRSSchoolAppTests(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {number} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AutoTestsApi + */ + public getRSSchoolAppTest(id: number, options?: AxiosRequestConfig) { + return AutoTestsApiFp(this.configuration).getRSSchoolAppTest(id, options).then((request) => request(this.axios, this.basePath)); } } diff --git a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx index f5ffe7333..5c4d75079 100644 --- a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx +++ b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx @@ -1,34 +1,31 @@ import { Button, Card, Col, Divider, Row, Typography, Switch } from 'antd'; import { TaskCardColumn } from '..'; -import { AutoTestTaskDto } from 'api'; +import { MinimizedAutoTestTaskDto } from 'api'; +import Link from 'next/link'; const { Paragraph } = Typography; -export interface AutoTestTask extends AutoTestTaskDto { - attributes: Record; -} export interface AutoTestTaskCardProps { - courseTask: AutoTestTask; - handleSelectTask: (task: AutoTestTask) => void; + courseTask: MinimizedAutoTestTaskDto } -function AutoTestTaskCard({ courseTask, handleSelectTask }: AutoTestTaskCardProps) { +function AutoTestTaskCard({ courseTask }: AutoTestTaskCardProps) { const columns = [ { label: 'Max attempts number', - value: courseTask.attributes?.public?.maxAttemptsNumber ?? <>–, + value: courseTask.maxAttemptsNumber ?? <>–, }, { label: 'Number of Questions', - value: courseTask.attributes?.public?.numberOfQuestions ?? <>–, + value: courseTask.numberOfQuestions ?? <>–, }, { label: 'Strict attempts mode', - value: , + value: , }, { label: 'Threshold percentage', - value: courseTask.attributes?.public?.tresholdPercentage ?? <>–, + value: courseTask.thresholdPercentage ?? <>–, }, ]; @@ -46,9 +43,9 @@ function AutoTestTaskCard({ courseTask, handleSelectTask }: AutoTestTaskCardProp - + + + diff --git a/client/src/pages/admin/auto-test-task.tsx b/client/src/pages/admin/auto-test-task.tsx new file mode 100644 index 000000000..beed675e6 --- /dev/null +++ b/client/src/pages/admin/auto-test-task.tsx @@ -0,0 +1,138 @@ +import { Checkbox, Descriptions, Divider, List, Result, Switch, Tag, Typography, message } from 'antd'; +import { AutoTestTaskDto, AutoTestsApi } from 'api'; +import { AdminPageLayout } from 'components/PageLayout'; +import { TASK_TYPES_MAP } from 'data/taskTypes'; +import { ActiveCourseProvider, SessionProvider, useActiveCourseContext } from 'modules/Course/contexts'; +import { useRouter } from 'next/router'; +import { useState } from 'react'; +import { useAsync } from 'react-use'; +import { CourseRole } from 'services/models'; + +const api = new AutoTestsApi(); + +export interface AutoTestTask extends AutoTestTaskDto { + attributes: Record; +} + +function Page() { + const router = useRouter(); + const [isLoading, setIsLoading] = useState(false); + const [selectedTask, setSelectedTask] = useState(); + const taskId = router?.query ? router.query?.taskId ?? null : null; + const { courses } = useActiveCourseContext(); + + useAsync(async () => { + try { + setIsLoading(true); + if (!taskId) { + throw new Error(); + } + const { data } = await api.getRSSchoolAppTest(Number(taskId)); + if (data) { + setSelectedTask(data); + } else { + throw new Error(); + } + } catch (e) { + message.error('Something went wrong. Please try again later.'); + } finally { + setIsLoading(false); + } + }); + + return ( + + {!selectedTask && !isLoading ? ( + + ) : ( + <> + + {selectedTask?.id} + {selectedTask?.type && ( + {TASK_TYPES_MAP[selectedTask?.type]} + )} + {selectedTask?.descriptionUrl && ( + + {selectedTask?.descriptionUrl} + + )} + {selectedTask?.discipline?.name && ( + {selectedTask?.discipline?.name} + )} + {selectedTask?.courses?.length && ( + + {selectedTask?.courses.map(course => ( + + {course.name} + + ))} + + )} + + + + + {selectedTask?.tags && ( + + {selectedTask?.tags.map(tag => ( + + {tag} + + ))} + + )} + + + {selectedTask?.attributes?.public?.questions && ( + <> + + + + {selectedTask?.attributes?.public?.maxAttemptsNumber} + + + {selectedTask?.attributes?.public?.numberOfQuestions} + + + + + + {selectedTask?.attributes?.public?.tresholdPercentage} + + + , index) => ( + + ( + + {answer} + + ))} + /> + + )} + /> + + )} + + )} + + ); +} + +export default function () { + return ( + + + + + + ); +} diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx index 140f0ec37..a013c7cf0 100644 --- a/client/src/pages/admin/auto-test.tsx +++ b/client/src/pages/admin/auto-test.tsx @@ -1,21 +1,7 @@ -import { - Col, - ColProps, - Descriptions, - Modal, - Row, - Switch, - Tag, - Typography, - Divider, - List, - Checkbox, - message, -} from 'antd'; -import { AutoTestTaskDto, AutoTestsApi } from 'api'; +import { Col, ColProps, Row, message } from 'antd'; +import { AutoTestsApi, MinimizedAutoTestTaskDto } from 'api'; import { AdminPageLayout } from 'components/PageLayout'; -import { TASK_TYPES_MAP } from 'data/taskTypes'; -import AutoTestTaskCard, { AutoTestTask } from 'modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard'; +import AutoTestTaskCard from 'modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard'; import { ActiveCourseProvider, SessionProvider, useActiveCourseContext } from 'modules/Course/contexts'; import { useState } from 'react'; import { useAsync } from 'react-use'; @@ -33,27 +19,13 @@ const api = new AutoTestsApi(); function Page() { const { courses } = useActiveCourseContext(); - const [tests, setTests] = useState([]); - const [isModalOpen, setIsModalOpen] = useState(false); - const [selectedTask, setSelectedTask] = useState(); + const [tests, setTests] = useState([]); const [isLoading, setIsLoading] = useState(false); - const handleOk = () => { - setIsModalOpen(false); - }; - const handleCancel = () => { - setIsModalOpen(false); - }; - - const handleSelectTask = (task: AutoTestTaskDto) => { - setSelectedTask(task); - setIsModalOpen(true); - }; - useAsync(async () => { try { setIsLoading(true); - const resp = await api.getAllRSSchoolAppTests(); + const resp = await api.getAllMinimizedRSSchoolAppTests(); setTests(resp.data); setIsLoading(false); } catch (e) { @@ -62,91 +34,14 @@ function Page() { }); return ( - + {tests.map(courseTask => ( - + ))} - - - {selectedTask?.id} - {selectedTask?.type && ( - {TASK_TYPES_MAP[selectedTask?.type]} - )} - {selectedTask?.descriptionUrl && ( - - {selectedTask?.descriptionUrl} - - )} - {selectedTask?.discipline?.name && ( - {selectedTask?.discipline?.name} - )} - {selectedTask?.courses?.length && ( - - {selectedTask?.courses.map(course => ( - - {course.name} - - ))} - - )} - - - - - {selectedTask?.tags && ( - - {selectedTask?.tags.map(tag => ( - - {tag} - - ))} - - )} - - - {selectedTask?.attributes?.public?.questions && ( - <> - - - - {selectedTask?.attributes?.public?.maxAttemptsNumber} - - - {selectedTask?.attributes?.public?.numberOfQuestions} - - - - - - {selectedTask?.attributes?.public?.tresholdPercentage} - - - , index) => ( - - ( - - {answer} - - ))} - /> - - )} - /> - - )} - ); } diff --git a/nestjs/src/auto-test/auto-test.controller.ts b/nestjs/src/auto-test/auto-test.controller.ts index df1615432..03ac3b5ff 100644 --- a/nestjs/src/auto-test/auto-test.controller.ts +++ b/nestjs/src/auto-test/auto-test.controller.ts @@ -1,9 +1,10 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, NotFoundException, Param, ParseIntPipe } from '@nestjs/common'; import { AutoTestService } from './auto-test.service'; import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; import { CourseRole } from '@entities/session'; import { RequiredRoles, Role } from 'src/auth'; import { AutoTestTaskDto } from './dto/auto-test-task.dto'; +import { MinimizedAutoTestTaskDto } from './dto/minimized-auto-test-task.dto'; @Controller('auto-test') @ApiTags('auto-tests') @@ -12,9 +13,21 @@ export class AutoTestController { @Get() @RequiredRoles([Role.Admin, CourseRole.Manager]) - @ApiOperation({ operationId: 'getAllRSSchoolAppTests' }) - @ApiOkResponse({ type: [AutoTestTaskDto] }) - async getAllRSSchoolAppTests() { - return (await this.service.getAll()).map(autoTest => new AutoTestTaskDto(autoTest)); + @ApiOperation({ operationId: 'getAllMinimizedRSSchoolAppTests' }) + @ApiOkResponse({ type: [MinimizedAutoTestTaskDto] }) + async getAllMinimizedRSSchoolAppTests() { + return (await this.service.getAll()).map(autoTest => new MinimizedAutoTestTaskDto(autoTest)); + } + + @Get('/:id') + @RequiredRoles([Role.Admin, CourseRole.Manager]) + @ApiOperation({ operationId: 'getRSSchoolAppTest' }) + @ApiOkResponse({ type: AutoTestTaskDto }) + async getAutoTestTask(@Param('id', ParseIntPipe) id: number) { + const task = await this.service.getOne(id); + if (!task) { + throw new NotFoundException("Couldn't find task with id = " + id); + } + return new AutoTestTaskDto(task); } } diff --git a/nestjs/src/auto-test/auto-test.service.ts b/nestjs/src/auto-test/auto-test.service.ts index c12764f2d..4eabe7430 100644 --- a/nestjs/src/auto-test/auto-test.service.ts +++ b/nestjs/src/auto-test/auto-test.service.ts @@ -9,6 +9,7 @@ export class AutoTestService { public async getAll() { return this.repository.find({ + select: ['id', 'name', 'attributes'], where: { type: TaskType.SelfEducation, }, @@ -21,4 +22,20 @@ export class AutoTestService { }, }); } + + public async getOne(id: number) { + return this.repository.findOne({ + where: { + type: TaskType.SelfEducation, + id, + }, + relations: { + discipline: true, + courseTasks: { course: true }, + }, + order: { + updatedDate: 'DESC', + }, + }); + } } diff --git a/nestjs/src/auto-test/dto/minimized-auto-test-task.dto.ts b/nestjs/src/auto-test/dto/minimized-auto-test-task.dto.ts new file mode 100644 index 000000000..5c8920d1b --- /dev/null +++ b/nestjs/src/auto-test/dto/minimized-auto-test-task.dto.ts @@ -0,0 +1,31 @@ +import { Task } from '@entities/task'; +import { ApiProperty } from '@nestjs/swagger'; + +export class MinimizedAutoTestTaskDto { + constructor(task: Task) { + this.id = task.id; + this.name = task.name; + this.maxAttemptsNumber = Number(task?.attributes?.public?.maxAttemptsNumber); + this.numberOfQuestions = Number(task?.attributes?.public?.numberOfQuestions); + this.strictAttemptsMode = !!task?.attributes?.public?.strictAttemptsMode; + this.thresholdPercentage = Number(task?.attributes?.public?.tresholdPercentage); + } + + @ApiProperty() + public id: number; + + @ApiProperty() + public name: string; + + @ApiProperty() + public maxAttemptsNumber: number; + + @ApiProperty() + public numberOfQuestions: number; + + @ApiProperty() + public strictAttemptsMode: boolean; + + @ApiProperty() + public thresholdPercentage: number; +} diff --git a/nestjs/src/spec.json b/nestjs/src/spec.json index 24d0594ac..3c2a4f037 100644 --- a/nestjs/src/spec.json +++ b/nestjs/src/spec.json @@ -2487,7 +2487,7 @@ }, "/auto-test": { "get": { - "operationId": "getAllRSSchoolAppTests", + "operationId": "getAllMinimizedRSSchoolAppTests", "summary": "", "parameters": [], "responses": { @@ -2495,13 +2495,27 @@ "description": "", "content": { "application/json": { - "schema": { "type": "array", "items": { "$ref": "#/components/schemas/AutoTestTaskDto" } } + "schema": { "type": "array", "items": { "$ref": "#/components/schemas/MinimizedAutoTestTaskDto" } } } } } }, "tags": ["auto-tests"] } + }, + "/auto-test/{id}": { + "get": { + "operationId": "getRSSchoolAppTest", + "summary": "", + "parameters": [{ "name": "id", "required": true, "in": "path", "schema": { "type": "number" } }], + "responses": { + "200": { + "description": "", + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AutoTestTaskDto" } } } + } + }, + "tags": ["auto-tests"] + } } }, "info": { "title": "", "description": "", "version": "1.0.0", "contact": {} }, @@ -4827,6 +4841,25 @@ }, "required": ["temperature", "type", "text"] }, + "MinimizedAutoTestTaskDto": { + "type": "object", + "properties": { + "maxAttemptsNumber": { "type": "number" }, + "numberOfQuestions": { "type": "number" }, + "name": { "type": "string" }, + "id": { "type": "number" }, + "strictAttemptsMode": { "type": "boolean" }, + "thresholdPercentage": { "type": "number" } + }, + "required": [ + "maxAttemptsNumber", + "numberOfQuestions", + "name", + "id", + "strictAttemptsMode", + "thresholdPercentage" + ] + }, "AutoTestTaskDto": { "type": "object", "properties": { From 988c8cd5634eea22e62b5a8c9f18e601fe0d12d6 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Sun, 7 Jul 2024 17:16:40 +0300 Subject: [PATCH 15/22] refactor: fix eslint isuue --- .../AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx index 5c4d75079..98603eb83 100644 --- a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx +++ b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx @@ -6,7 +6,7 @@ import Link from 'next/link'; const { Paragraph } = Typography; export interface AutoTestTaskCardProps { - courseTask: MinimizedAutoTestTaskDto + courseTask: MinimizedAutoTestTaskDto; } function AutoTestTaskCard({ courseTask }: AutoTestTaskCardProps) { From 8a806048e14e6e17e1b4fa38f620164b30084d2c Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Wed, 10 Jul 2024 21:09:29 +0300 Subject: [PATCH 16/22] refactor: rename functions and variables, add fields validation --- nestjs/src/auto-test/auto-test.controller.ts | 12 ++++++------ ...est-task.dto.ts => basic-auto-test-task.dto.ts} | 14 ++++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) rename nestjs/src/auto-test/dto/{minimized-auto-test-task.dto.ts => basic-auto-test-task.dto.ts} (52%) diff --git a/nestjs/src/auto-test/auto-test.controller.ts b/nestjs/src/auto-test/auto-test.controller.ts index 03ac3b5ff..e35f90327 100644 --- a/nestjs/src/auto-test/auto-test.controller.ts +++ b/nestjs/src/auto-test/auto-test.controller.ts @@ -4,7 +4,7 @@ import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; import { CourseRole } from '@entities/session'; import { RequiredRoles, Role } from 'src/auth'; import { AutoTestTaskDto } from './dto/auto-test-task.dto'; -import { MinimizedAutoTestTaskDto } from './dto/minimized-auto-test-task.dto'; +import { BasicAutoTestTaskDto } from './dto/basic-auto-test-task.dto'; @Controller('auto-test') @ApiTags('auto-tests') @@ -13,15 +13,15 @@ export class AutoTestController { @Get() @RequiredRoles([Role.Admin, CourseRole.Manager]) - @ApiOperation({ operationId: 'getAllMinimizedRSSchoolAppTests' }) - @ApiOkResponse({ type: [MinimizedAutoTestTaskDto] }) - async getAllMinimizedRSSchoolAppTests() { - return (await this.service.getAll()).map(autoTest => new MinimizedAutoTestTaskDto(autoTest)); + @ApiOperation({ operationId: 'getBasicAutoTests' }) + @ApiOkResponse({ type: [BasicAutoTestTaskDto] }) + async getBasicAutoTests() { + return (await this.service.getAll()).map(autoTest => new BasicAutoTestTaskDto(autoTest)); } @Get('/:id') @RequiredRoles([Role.Admin, CourseRole.Manager]) - @ApiOperation({ operationId: 'getRSSchoolAppTest' }) + @ApiOperation({ operationId: 'getAutoTest' }) @ApiOkResponse({ type: AutoTestTaskDto }) async getAutoTestTask(@Param('id', ParseIntPipe) id: number) { const task = await this.service.getOne(id); diff --git a/nestjs/src/auto-test/dto/minimized-auto-test-task.dto.ts b/nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts similarity index 52% rename from nestjs/src/auto-test/dto/minimized-auto-test-task.dto.ts rename to nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts index 5c8920d1b..b9e164929 100644 --- a/nestjs/src/auto-test/dto/minimized-auto-test-task.dto.ts +++ b/nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts @@ -1,14 +1,20 @@ import { Task } from '@entities/task'; import { ApiProperty } from '@nestjs/swagger'; -export class MinimizedAutoTestTaskDto { +export class BasicAutoTestTaskDto { constructor(task: Task) { this.id = task.id; this.name = task.name; - this.maxAttemptsNumber = Number(task?.attributes?.public?.maxAttemptsNumber); - this.numberOfQuestions = Number(task?.attributes?.public?.numberOfQuestions); + this.maxAttemptsNumber = isNaN(task?.attributes?.public?.maxAttemptsNumber) + ? 0 + : Number(task?.attributes?.public?.maxAttemptsNumber); + this.numberOfQuestions = isNaN(task?.attributes?.public?.numberOfQuestions) + ? 0 + : Number(task?.attributes?.public?.numberOfQuestions); this.strictAttemptsMode = !!task?.attributes?.public?.strictAttemptsMode; - this.thresholdPercentage = Number(task?.attributes?.public?.tresholdPercentage); + this.thresholdPercentage = isNaN(task?.attributes?.public?.tresholdPercentage) + ? 0 + : Number(task?.attributes?.public?.tresholdPercentage); } @ApiProperty() From 4ea35f6b2597b7566907e8cacfb10d2a6dcb4dfc Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Wed, 10 Jul 2024 21:48:26 +0300 Subject: [PATCH 17/22] refactor: rename funcions and variables at front end part --- client/src/api/api.ts | 132 +++++++++++----------- client/src/pages/admin/auto-test-task.tsx | 2 +- client/src/pages/admin/auto-test.tsx | 6 +- nestjs/src/spec.json | 22 ++-- 4 files changed, 81 insertions(+), 81 deletions(-) diff --git a/client/src/api/api.ts b/client/src/api/api.ts index 68e74d366..7c541eaaa 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -549,6 +549,49 @@ export const BadgeDtoIdEnum = { export type BadgeDtoIdEnum = typeof BadgeDtoIdEnum[keyof typeof BadgeDtoIdEnum]; +/** + * + * @export + * @interface BasicAutoTestTaskDto + */ +export interface BasicAutoTestTaskDto { + /** + * + * @type {number} + * @memberof BasicAutoTestTaskDto + */ + 'id': number; + /** + * + * @type {string} + * @memberof BasicAutoTestTaskDto + */ + 'name': string; + /** + * + * @type {object} + * @memberof BasicAutoTestTaskDto + */ + 'maxAttemptsNumber': object | null; + /** + * + * @type {object} + * @memberof BasicAutoTestTaskDto + */ + 'numberOfQuestions': object | null; + /** + * + * @type {object} + * @memberof BasicAutoTestTaskDto + */ + 'strictAttemptsMode': object | null; + /** + * + * @type {object} + * @memberof BasicAutoTestTaskDto + */ + 'thresholdPercentage': object | null; +} /** * * @export @@ -4021,49 +4064,6 @@ export interface MentorStudentDto { */ 'repoUrl': string | null; } -/** - * - * @export - * @interface MinimizedAutoTestTaskDto - */ -export interface MinimizedAutoTestTaskDto { - /** - * - * @type {number} - * @memberof MinimizedAutoTestTaskDto - */ - 'maxAttemptsNumber': number; - /** - * - * @type {number} - * @memberof MinimizedAutoTestTaskDto - */ - 'numberOfQuestions': number; - /** - * - * @type {string} - * @memberof MinimizedAutoTestTaskDto - */ - 'name': string; - /** - * - * @type {number} - * @memberof MinimizedAutoTestTaskDto - */ - 'id': number; - /** - * - * @type {boolean} - * @memberof MinimizedAutoTestTaskDto - */ - 'strictAttemptsMode': boolean; - /** - * - * @type {number} - * @memberof MinimizedAutoTestTaskDto - */ - 'thresholdPercentage': number; -} /** * * @export @@ -8185,11 +8185,15 @@ export const AutoTestsApiAxiosParamCreator = function (configuration?: Configura return { /** * + * @param {number} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllMinimizedRSSchoolAppTests: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/auto-test`; + getAutoTest: async (id: number, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('getAutoTest', 'id', id) + const localVarPath = `/auto-test/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -8214,15 +8218,11 @@ export const AutoTestsApiAxiosParamCreator = function (configuration?: Configura }, /** * - * @param {number} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getRSSchoolAppTest: async (id: number, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'id' is not null or undefined - assertParamExists('getRSSchoolAppTest', 'id', id) - const localVarPath = `/auto-test/{id}` - .replace(`{${"id"}}`, encodeURIComponent(String(id))); + getBasicAutoTests: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/auto-test`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -8257,21 +8257,21 @@ export const AutoTestsApiFp = function(configuration?: Configuration) { return { /** * + * @param {number} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllMinimizedRSSchoolAppTests(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAllMinimizedRSSchoolAppTests(options); + async getAutoTest(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAutoTest(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** * - * @param {number} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getRSSchoolAppTest(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getRSSchoolAppTest(id, options); + async getBasicAutoTests(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getBasicAutoTests(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -8286,20 +8286,20 @@ export const AutoTestsApiFactory = function (configuration?: Configuration, base return { /** * + * @param {number} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllMinimizedRSSchoolAppTests(options?: any): AxiosPromise> { - return localVarFp.getAllMinimizedRSSchoolAppTests(options).then((request) => request(axios, basePath)); + getAutoTest(id: number, options?: any): AxiosPromise { + return localVarFp.getAutoTest(id, options).then((request) => request(axios, basePath)); }, /** * - * @param {number} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getRSSchoolAppTest(id: number, options?: any): AxiosPromise { - return localVarFp.getRSSchoolAppTest(id, options).then((request) => request(axios, basePath)); + getBasicAutoTests(options?: any): AxiosPromise> { + return localVarFp.getBasicAutoTests(options).then((request) => request(axios, basePath)); }, }; }; @@ -8313,23 +8313,23 @@ export const AutoTestsApiFactory = function (configuration?: Configuration, base export class AutoTestsApi extends BaseAPI { /** * + * @param {number} id * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AutoTestsApi */ - public getAllMinimizedRSSchoolAppTests(options?: AxiosRequestConfig) { - return AutoTestsApiFp(this.configuration).getAllMinimizedRSSchoolAppTests(options).then((request) => request(this.axios, this.basePath)); + public getAutoTest(id: number, options?: AxiosRequestConfig) { + return AutoTestsApiFp(this.configuration).getAutoTest(id, options).then((request) => request(this.axios, this.basePath)); } /** * - * @param {number} id * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AutoTestsApi */ - public getRSSchoolAppTest(id: number, options?: AxiosRequestConfig) { - return AutoTestsApiFp(this.configuration).getRSSchoolAppTest(id, options).then((request) => request(this.axios, this.basePath)); + public getBasicAutoTests(options?: AxiosRequestConfig) { + return AutoTestsApiFp(this.configuration).getBasicAutoTests(options).then((request) => request(this.axios, this.basePath)); } } diff --git a/client/src/pages/admin/auto-test-task.tsx b/client/src/pages/admin/auto-test-task.tsx index beed675e6..8ece46644 100644 --- a/client/src/pages/admin/auto-test-task.tsx +++ b/client/src/pages/admin/auto-test-task.tsx @@ -27,7 +27,7 @@ function Page() { if (!taskId) { throw new Error(); } - const { data } = await api.getRSSchoolAppTest(Number(taskId)); + const { data } = await api.getAutoTest(Number(taskId)); if (data) { setSelectedTask(data); } else { diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx index a013c7cf0..52dcdda4c 100644 --- a/client/src/pages/admin/auto-test.tsx +++ b/client/src/pages/admin/auto-test.tsx @@ -1,5 +1,5 @@ import { Col, ColProps, Row, message } from 'antd'; -import { AutoTestsApi, MinimizedAutoTestTaskDto } from 'api'; +import { AutoTestsApi, BasicAutoTestTaskDto } from 'api'; import { AdminPageLayout } from 'components/PageLayout'; import AutoTestTaskCard from 'modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard'; import { ActiveCourseProvider, SessionProvider, useActiveCourseContext } from 'modules/Course/contexts'; @@ -19,13 +19,13 @@ const api = new AutoTestsApi(); function Page() { const { courses } = useActiveCourseContext(); - const [tests, setTests] = useState([]); + const [tests, setTests] = useState([]); const [isLoading, setIsLoading] = useState(false); useAsync(async () => { try { setIsLoading(true); - const resp = await api.getAllMinimizedRSSchoolAppTests(); + const resp = await api.getBasicAutoTests(); setTests(resp.data); setIsLoading(false); } catch (e) { diff --git a/nestjs/src/spec.json b/nestjs/src/spec.json index fa65a5fba..beed3628c 100644 --- a/nestjs/src/spec.json +++ b/nestjs/src/spec.json @@ -2570,7 +2570,7 @@ }, "/auto-test": { "get": { - "operationId": "getAllMinimizedRSSchoolAppTests", + "operationId": "getBasicAutoTests", "summary": "", "parameters": [], "responses": { @@ -2578,7 +2578,7 @@ "description": "", "content": { "application/json": { - "schema": { "type": "array", "items": { "$ref": "#/components/schemas/MinimizedAutoTestTaskDto" } } + "schema": { "type": "array", "items": { "$ref": "#/components/schemas/BasicAutoTestTaskDto" } } } } } @@ -2588,7 +2588,7 @@ }, "/auto-test/{id}": { "get": { - "operationId": "getRSSchoolAppTest", + "operationId": "getAutoTest", "summary": "", "parameters": [{ "name": "id", "required": true, "in": "path", "schema": { "type": "number" } }], "responses": { @@ -5045,21 +5045,21 @@ }, "required": ["temperature", "type", "text"] }, - "MinimizedAutoTestTaskDto": { + "BasicAutoTestTaskDto": { "type": "object", "properties": { - "maxAttemptsNumber": { "type": "number" }, - "numberOfQuestions": { "type": "number" }, - "name": { "type": "string" }, "id": { "type": "number" }, - "strictAttemptsMode": { "type": "boolean" }, - "thresholdPercentage": { "type": "number" } + "name": { "type": "string" }, + "maxAttemptsNumber": { "type": "object", "nullable": true }, + "numberOfQuestions": { "type": "object", "nullable": true }, + "strictAttemptsMode": { "type": "object", "nullable": true }, + "thresholdPercentage": { "type": "object", "nullable": true } }, "required": [ + "id", + "name", "maxAttemptsNumber", "numberOfQuestions", - "name", - "id", "strictAttemptsMode", "thresholdPercentage" ] From f2baca8422e611579476c26f721485c0c7c01a30 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Wed, 10 Jul 2024 21:49:05 +0300 Subject: [PATCH 18/22] feat: allow numberable fields to be null --- .../auto-test/dto/basic-auto-test-task.dto.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts b/nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts index b9e164929..f35a28b32 100644 --- a/nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts +++ b/nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts @@ -6,14 +6,14 @@ export class BasicAutoTestTaskDto { this.id = task.id; this.name = task.name; this.maxAttemptsNumber = isNaN(task?.attributes?.public?.maxAttemptsNumber) - ? 0 + ? null : Number(task?.attributes?.public?.maxAttemptsNumber); this.numberOfQuestions = isNaN(task?.attributes?.public?.numberOfQuestions) - ? 0 + ? null : Number(task?.attributes?.public?.numberOfQuestions); this.strictAttemptsMode = !!task?.attributes?.public?.strictAttemptsMode; this.thresholdPercentage = isNaN(task?.attributes?.public?.tresholdPercentage) - ? 0 + ? null : Number(task?.attributes?.public?.tresholdPercentage); } @@ -23,15 +23,15 @@ export class BasicAutoTestTaskDto { @ApiProperty() public name: string; - @ApiProperty() - public maxAttemptsNumber: number; + @ApiProperty({nullable: true}) + public maxAttemptsNumber: number | null; - @ApiProperty() - public numberOfQuestions: number; + @ApiProperty({nullable: true}) + public numberOfQuestions: number | null; - @ApiProperty() - public strictAttemptsMode: boolean; + @ApiProperty({nullable: true}) + public strictAttemptsMode: boolean | null; - @ApiProperty() - public thresholdPercentage: number; + @ApiProperty({nullable: true}) + public thresholdPercentage: number | null; } From 88aa80d37c2229218b6e478dff05a314ce1d0945 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Wed, 10 Jul 2024 21:55:30 +0300 Subject: [PATCH 19/22] refactor: fix eslint issue --- nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts b/nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts index f35a28b32..60bd8e411 100644 --- a/nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts +++ b/nestjs/src/auto-test/dto/basic-auto-test-task.dto.ts @@ -23,15 +23,15 @@ export class BasicAutoTestTaskDto { @ApiProperty() public name: string; - @ApiProperty({nullable: true}) + @ApiProperty({ nullable: true }) public maxAttemptsNumber: number | null; - @ApiProperty({nullable: true}) + @ApiProperty({ nullable: true }) public numberOfQuestions: number | null; - @ApiProperty({nullable: true}) + @ApiProperty({ nullable: true }) public strictAttemptsMode: boolean | null; - @ApiProperty({nullable: true}) + @ApiProperty({ nullable: true }) public thresholdPercentage: number | null; } From db4fe9975e6b3136726a5cdea2fc6c4ec1bbfc00 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Wed, 10 Jul 2024 22:20:03 +0300 Subject: [PATCH 20/22] fix: fix typescript issue --- .../components/AutoTestTaskCard/AutoTestTaskCard.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx index 98603eb83..48cb697fb 100644 --- a/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx +++ b/client/src/modules/AutoTest/components/AutoTestTaskCard/AutoTestTaskCard.tsx @@ -1,31 +1,31 @@ import { Button, Card, Col, Divider, Row, Typography, Switch } from 'antd'; import { TaskCardColumn } from '..'; -import { MinimizedAutoTestTaskDto } from 'api'; +import { BasicAutoTestTaskDto } from 'api'; import Link from 'next/link'; const { Paragraph } = Typography; export interface AutoTestTaskCardProps { - courseTask: MinimizedAutoTestTaskDto; + courseTask: BasicAutoTestTaskDto; } function AutoTestTaskCard({ courseTask }: AutoTestTaskCardProps) { const columns = [ { label: 'Max attempts number', - value: courseTask.maxAttemptsNumber ?? <>–, + value: courseTask.maxAttemptsNumber ? <>{courseTask.maxAttemptsNumber} : <>–, }, { label: 'Number of Questions', - value: courseTask.numberOfQuestions ?? <>–, + value: courseTask.numberOfQuestions ? <>{courseTask.numberOfQuestions} : <>–, }, { label: 'Strict attempts mode', - value: , + value: , }, { label: 'Threshold percentage', - value: courseTask.thresholdPercentage ?? <>–, + value: courseTask.thresholdPercentage ? <>{courseTask.thresholdPercentage} : <>–, }, ]; From 9e6f40033d818716c9e717dc80a086e350d619a9 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Mon, 15 Jul 2024 21:58:24 +0300 Subject: [PATCH 21/22] fix: fix code review issues --- client/src/pages/admin/auto-test-task.tsx | 8 +++++--- client/src/pages/admin/auto-test.tsx | 2 +- nestjs/src/auto-test/auto-test.controller.ts | 2 +- nestjs/src/auto-test/auto-test.service.ts | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/client/src/pages/admin/auto-test-task.tsx b/client/src/pages/admin/auto-test-task.tsx index 8ece46644..2ddc090f5 100644 --- a/client/src/pages/admin/auto-test-task.tsx +++ b/client/src/pages/admin/auto-test-task.tsx @@ -53,13 +53,15 @@ function Page() { )} {selectedTask?.descriptionUrl && ( - {selectedTask?.descriptionUrl} + + {selectedTask?.descriptionUrl} + )} {selectedTask?.discipline?.name && ( {selectedTask?.discipline?.name} )} - {selectedTask?.courses?.length && ( + {selectedTask?.courses?.length && selectedTask?.courses?.length > 0 && ( {selectedTask?.courses.map(course => ( @@ -130,7 +132,7 @@ function Page() { export default function () { return ( - + diff --git a/client/src/pages/admin/auto-test.tsx b/client/src/pages/admin/auto-test.tsx index 52dcdda4c..7004aef2d 100644 --- a/client/src/pages/admin/auto-test.tsx +++ b/client/src/pages/admin/auto-test.tsx @@ -48,7 +48,7 @@ function Page() { export default function () { return ( - + diff --git a/nestjs/src/auto-test/auto-test.controller.ts b/nestjs/src/auto-test/auto-test.controller.ts index e35f90327..7f7f94f71 100644 --- a/nestjs/src/auto-test/auto-test.controller.ts +++ b/nestjs/src/auto-test/auto-test.controller.ts @@ -24,7 +24,7 @@ export class AutoTestController { @ApiOperation({ operationId: 'getAutoTest' }) @ApiOkResponse({ type: AutoTestTaskDto }) async getAutoTestTask(@Param('id', ParseIntPipe) id: number) { - const task = await this.service.getOne(id); + const task = await this.service.findById(id); if (!task) { throw new NotFoundException("Couldn't find task with id = " + id); } diff --git a/nestjs/src/auto-test/auto-test.service.ts b/nestjs/src/auto-test/auto-test.service.ts index 4eabe7430..8de3c3fa5 100644 --- a/nestjs/src/auto-test/auto-test.service.ts +++ b/nestjs/src/auto-test/auto-test.service.ts @@ -23,7 +23,7 @@ export class AutoTestService { }); } - public async getOne(id: number) { + public async findById(id: number) { return this.repository.findOne({ where: { type: TaskType.SelfEducation, From 9c02b6e83c8156580717efec64aa43079450f219 Mon Sep 17 00:00:00 2001 From: Dzmitry Chervanenka Date: Mon, 15 Jul 2024 22:12:44 +0300 Subject: [PATCH 22/22] fix: fixed user validation for auto tests page --- client/src/components/Sider/data/menuItems.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Sider/data/menuItems.tsx b/client/src/components/Sider/data/menuItems.tsx index 311b23d89..848fe7a49 100644 --- a/client/src/components/Sider/data/menuItems.tsx +++ b/client/src/components/Sider/data/menuItems.tsx @@ -144,7 +144,7 @@ const adminMenuItems: AdminMenuItemsData[] = [ key: 'auto-test', icon: , href: '/admin/auto-test', - access: session => isAdmin(session), + access: session => isAdmin(session) || isAnyCourseManager(session), }, { name: 'Students',