Skip to content

Commit

Permalink
feat: implement minimal students per mentor field for course instance (
Browse files Browse the repository at this point in the history
…#2386)

* feat: implement collecting "Minimum students per Mentor" field with validation

* feat: use nestjs api for course creation

* fix: remove default value from input

* feat: add minStudentsPerMentor field to db

* feat: add new field to update course api

* feat: add new field to ProfileCourseDto

* feat: implement showing minimal students number during mentor confirmation

* refactor: fix linter issues

* feat: add type definition for api body, generated api

* fix: fix code review issues

* fix: fixed numbers at students number dropdown

* fix: fix required roles, removed unneccessary type cast

* fix: remove unneccessary code

* fix: remove unused imports

* feat: add default value to form

* refactor: fix eslint issue

* fix: set minStudentsPerMentor property optional

---------
  • Loading branch information
Alphajax authored Feb 10, 2024
1 parent 1e8330c commit e8cd119
Show file tree
Hide file tree
Showing 15 changed files with 215 additions and 51 deletions.
89 changes: 89 additions & 0 deletions client/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,12 @@ export interface CourseDto {
* @memberof CourseDto
*/
'discipline': IdNameDto | null;
/**
*
* @type {number}
* @memberof CourseDto
*/
'minStudentsPerMentor': number;
}
/**
*
Expand Down Expand Up @@ -1562,6 +1568,12 @@ export interface CreateCourseDto {
* @memberof CreateCourseDto
*/
'logo'?: string;
/**
*
* @type {number}
* @memberof CreateCourseDto
*/
'minStudentsPerMentor'?: number;
}
/**
*
Expand Down Expand Up @@ -4188,6 +4200,12 @@ export interface ProfileCourseDto {
* @memberof ProfileCourseDto
*/
'discipline': IdNameDto | null;
/**
*
* @type {number}
* @memberof ProfileCourseDto
*/
'minStudentsPerMentor': number;
}
/**
*
Expand Down Expand Up @@ -5761,6 +5779,12 @@ export interface UpdateCourseDto {
* @memberof UpdateCourseDto
*/
'disciplineId'?: number;
/**
*
* @type {number}
* @memberof UpdateCourseDto
*/
'minStudentsPerMentor'?: number;
}
/**
*
Expand Down Expand Up @@ -8769,6 +8793,41 @@ export const CoursesApiAxiosParamCreator = function (configuration?: Configurati



localVarHeaderParameter['Content-Type'] = 'application/json';

setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(createCourseDto, localVarRequestOptions, configuration)

return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {CreateCourseDto} createCourseDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createCourse: async (createCourseDto: CreateCourseDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'createCourseDto' is not null or undefined
assertParamExists('createCourse', 'createCourseDto', createCourseDto)
const localVarPath = `/courses`;
// 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: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;



localVarHeaderParameter['Content-Type'] = 'application/json';

setSearchParams(localVarUrlObj, localVarQueryParameter);
Expand Down Expand Up @@ -8973,6 +9032,16 @@ export const CoursesApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.copyCourse(courseId, createCourseDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {CreateCourseDto} createCourseDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async createCourse(createCourseDto: CreateCourseDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<CourseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.createCourse(createCourseDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {number} courseId
Expand Down Expand Up @@ -9044,6 +9113,15 @@ export const CoursesApiFactory = function (configuration?: Configuration, basePa
copyCourse(courseId: number, createCourseDto: CreateCourseDto, options?: any): AxiosPromise<CourseDto> {
return localVarFp.copyCourse(courseId, createCourseDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {CreateCourseDto} createCourseDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createCourse(createCourseDto: CreateCourseDto, options?: any): AxiosPromise<CourseDto> {
return localVarFp.createCourse(createCourseDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {number} courseId
Expand Down Expand Up @@ -9112,6 +9190,17 @@ export class CoursesApi extends BaseAPI {
return CoursesApiFp(this.configuration).copyCourse(courseId, createCourseDto, options).then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {CreateCourseDto} createCourseDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof CoursesApi
*/
public createCourse(createCourseDto: CreateCourseDto, options?: AxiosRequestConfig) {
return CoursesApiFp(this.configuration).createCourse(createCourseDto, options).then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {number} courseId
Expand Down
17 changes: 11 additions & 6 deletions client/src/components/MentorOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export type Options = {
preselectedStudents?: { id: number; githubId: string; name: string }[];
};

const STUDENTS_NUMBERS = [0, 1, 2, 3, 4, 5, 6];

export function MentorOptions({
course,
mentorData,
Expand All @@ -19,7 +21,7 @@ export function MentorOptions({
form: FormInstance;
mentorData: Options | null;
handleSubmit?: (values: Options) => Promise<void>;
course: { id: number; name: string };
course: { id: number; name: string; minStudentsPerMentor?: number };
showSubmitButton?: boolean;
}) {
return (
Expand All @@ -37,11 +39,14 @@ export function MentorOptions({
rules={[{ required: true, message: 'Please select students count' }]}
>
<Select style={{ width: 200 }} placeholder="Students count...">
<Select.Option value={2}>2</Select.Option>
<Select.Option value={3}>3</Select.Option>
<Select.Option value={4}>4</Select.Option>
<Select.Option value={5}>5</Select.Option>
<Select.Option value={6}>6</Select.Option>
{STUDENTS_NUMBERS.map(num => {
const studentsNumber = num + Number(course.minStudentsPerMentor);
return (
<Select.Option key={studentsNumber} value={studentsNumber}>
{studentsNumber}
</Select.Option>
);
})}
</Select>
</Form.Item>

Expand Down
12 changes: 11 additions & 1 deletion client/src/pages/admin/courses.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Form,
Image,
Input,
InputNumber,
Layout,
message,
Radio,
Expand Down Expand Up @@ -97,7 +98,7 @@ function Page() {
await courseService.createCourseCopy(record as CreateCourseDto, values.courseId);
setIsCopy(false);
} else {
await courseService.createCourse(record);
await courseService.createCourse(record as CreateCourseDto);
}
}
await loadData();
Expand Down Expand Up @@ -235,6 +236,14 @@ function Page() {
</Select>
</Form.Item>

<Form.Item
name="minStudentsPerMentor"
label="Minimum Students per Mentor"
rules={[{ min: 1, type: 'integer', message: 'Ensure that the input, if provided, is a positive integer.' }]}
>
<InputNumber step={1} defaultValue={2} />
</Form.Item>

<Form.Item name="state" label="State">
<Radio.Group>
<Radio value={null}>Active</Radio>
Expand Down Expand Up @@ -297,6 +306,7 @@ function createRecord(values: any) {
usePrivateRepositories: values.usePrivateRepositories,
personalMentoring: values.personalMentoring,
logo: values.logo,
minStudentsPerMentor: values.minStudentsPerMentor,
};
return record;
}
Expand Down
13 changes: 11 additions & 2 deletions client/src/pages/course/mentor/confirm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ function Page() {
return new CourseService(course.id);
}, [course]);

const mapMentorData = (mentor: MentorResponse, course: Course | null): MentorResponse => {
const courseMinStudentsPerMentorValue = course?.minStudentsPerMentor;
if (courseMinStudentsPerMentorValue && courseMinStudentsPerMentorValue > Number(mentor?.maxStudentsLimit)) {
mentor.maxStudentsLimit = courseMinStudentsPerMentorValue;
}
return mentor;
};

useAsync(async () => {
try {
setLoading(true);
Expand All @@ -44,8 +52,9 @@ function Page() {
const course = courses.find(c => c.alias.toLowerCase() === courseAlias) ?? null;
setCourse(course);
const mentor = await mentorRegistry.getMentor();
const preferredCourse = course?.id ? mentor.preferredCourses?.includes(course?.id) : null;
const preselectedCourses = course?.id ? mentor.preselectedCourses?.includes(course?.id) : null;
const mappedMentorData = mapMentorData(mentor, course);
const preferredCourse = course?.id ? mappedMentorData.preferredCourses?.includes(course?.id) : null;
const preselectedCourses = course?.id ? mappedMentorData.preselectedCourses?.includes(course?.id) : null;
setIsPreferredCourse(preferredCourse);
if (preselectedCourses === false) {
setNoAccess(true);
Expand Down
9 changes: 3 additions & 6 deletions client/src/services/courses.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { CourseDto as Course, CoursesApi, CreateCourseDto } from 'api';
import axios from 'axios';
import { getApiConfiguration } from 'utils/axios';

type CourseResponse = { data: Course };
export type CoursesResponse = { data: Course[] };

export class CoursesService {
Expand All @@ -12,9 +9,9 @@ export class CoursesService {
this.coursesApi = new CoursesApi(getApiConfiguration(this.token));
}

async createCourse(data: Partial<Course>) {
const result = await axios.post<CourseResponse>(`/api/course/`, data);
return result.data.data;
async createCourse(data: CreateCourseDto) {
const result = await this.coursesApi.createCourse(data);
return result.data;
}

async createCourseCopy(data: CreateCourseDto, id: number): Promise<Course> {
Expand Down
11 changes: 11 additions & 0 deletions nestjs/src/courses/courses.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ export class CoursesController {
return data.map(it => new CourseDto(it));
}

@Post('/')
@ApiOperation({ operationId: 'createCourse' })
@UseGuards(DefaultGuard, RoleGuard)
@RequiredRoles([Role.Admin])
@ApiBody({ type: CreateCourseDto })
@ApiOkResponse({ type: CourseDto })
public async createCourse(@Body() dto: CreateCourseDto) {
const created = await this.courseService.create(dto);
return new CourseDto(created);
}

@Get('/:courseId')
@ApiOperation({ operationId: 'getCourse' })
@ApiForbiddenResponse()
Expand Down
4 changes: 4 additions & 0 deletions nestjs/src/courses/dto/course.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class CourseDto {
this.personalMentoring = course.personalMentoring;
this.logo = course.logo;
this.discipline = course.discipline ? { id: course.discipline.id, name: course.discipline.name } : null;
this.minStudentsPerMentor = course.minStudentsPerMentor;
}

@ApiProperty()
Expand Down Expand Up @@ -98,4 +99,7 @@ export class CourseDto {

@ApiProperty({ nullable: true, type: IdNameDto })
discipline: IdNameDto | null;

@ApiProperty()
minStudentsPerMentor: number;
}
5 changes: 5 additions & 0 deletions nestjs/src/courses/dto/create-course.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,9 @@ export class CreateCourseDto {
@IsOptional()
@ApiProperty({ required: false })
logo?: string;

@IsNumber()
@IsOptional()
@ApiProperty({ required: false })
minStudentsPerMentor?: number;
}
5 changes: 5 additions & 0 deletions nestjs/src/courses/dto/update-course.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,9 @@ export class UpdateCourseDto {
@IsOptional()
@ApiPropertyOptional()
disciplineId?: number;

@IsNumber()
@IsOptional()
@ApiPropertyOptional()
minStudentsPerMentor?: number;
}
Loading

0 comments on commit e8cd119

Please sign in to comment.