Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
420 changes: 315 additions & 105 deletions client/src/api/api.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* tslint:disable */
/* eslint-disable */
/**
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CoursesStatsDto } from '@client/api';
import { CourseAggregateStatsDto } from 'api';
import { StudentsCountriesCard } from '@client/modules/CourseStatistics/components/StudentsCountriesCard';
import { StudentsStatsCard } from '@client/modules/CourseStatistics/components/StudentsStatsCard';
import { MentorsCountriesCard } from '@client/modules/CourseStatistics/components/MentorsCountriesCard/MentorsCountriesCard';
Expand All @@ -12,7 +12,7 @@ import Masonry from 'react-masonry-css';
import css from 'styled-jsx/css';

type StatCardsProps = {
coursesData?: CoursesStatsDto;
coursesData?: CourseAggregateStatsDto;
};

const gapSize = 24;
Expand Down
7 changes: 5 additions & 2 deletions client/src/modules/CourseStatistics/hooks/useCourseStats.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMessage } from '@client/hooks';
import { CoursesStatsDto, CourseStatsApi } from '@client/api';
import { CourseAggregateStatsDto, CourseStatsApi } from 'api';
import { useRequest } from 'ahooks';

const courseStatsApi = new CourseStatsApi();
Expand All @@ -9,7 +9,10 @@ type CourseStatsParams = {
year?: number;
};

async function fetchCourseStats({ ids = [], year = 0 }: CourseStatsParams): Promise<CoursesStatsDto | undefined> {
async function fetchCourseStats({
ids = [],
year = 0,
}: CourseStatsParams): Promise<CourseAggregateStatsDto | undefined> {
try {
const { data } = await courseStatsApi.getCoursesStats(ids, year);
return data;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,61 +1,50 @@
import { useRequest } from 'ahooks';
import { Button, Col, Modal, Row, Select, Spin } from 'antd';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { CheckService } from 'services/check';
import { CoursesTasksApi } from 'api';
import { useEffect, useState } from 'react';
import { CourseTaskDetails } from 'services/course';
import { BadReviewTable } from './BadReviewTable';

interface IBadReviewControllersProps {
type Props = {
courseTasks: CourseTaskDetails[];
courseId: number;
}
};

export interface IBadReview {
checkerScore: number;
comment?: string;
taskName: string;
checkerGithubId: string;
studentGithubId: string;
studentAvgScore?: number;
}
export type checkType = 'badcomment' | 'didnotcheck' | undefined;

export type checkType = 'Bad comment' | 'Did not check' | 'No type';
const courseTaskService = new CoursesTasksApi();

export function BadReviewControllers({ courseTasks, courseId }: IBadReviewControllersProps) {
export function BadReviewControllers({ courseTasks, courseId }: Props) {
const { Option } = Select;
const [taskId, setTaskId] = useState<number>();
const [data, setData] = useState<IBadReview[]>();
const [isLoading, setIsLoading] = useState<boolean>(true);
const [courseTaskId, setCourseTaskId] = useState<number>();
const [checkType, setCheckType] = useState<checkType>();
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
const checkService = useMemo(() => new CheckService(), []);

const getData = useCallback(async (): Promise<void> => {
if (taskId && checkType) {
setIsLoading(true);
const dataFromService = await checkService.getData(taskId, checkType, courseId);
setData(dataFromService);
setIsLoading(false);
}
}, [taskId, checkType, checkService, courseId]);

const showModal = () => {
setIsModalVisible(true);
};
const dataRequest = useRequest(
async () => {
if (checkType === 'badcomment') {
const { data } = await courseTaskService.getCourseTaskBadComments(courseId, courseTaskId as number);
return data;
}
if (checkType === 'didnotcheck') {
const { data } = await courseTaskService.getCourseTaskMaxScoreCheckers(courseId, courseTaskId as number);
return data;
}
return [];
},
{
ready: Boolean(courseTaskId) && Boolean(checkType),
},
);

const handleCancel = () => {
setIsModalVisible(false);
};
const handleCancel = () => setIsModalVisible(false);

const buttonHandler = (type: checkType) => {
setCheckType(type);
showModal();
setIsModalVisible(true);
};

useEffect(() => setCheckType('No type'), [taskId]);

useEffect(() => {
getData();
}, [checkType]);
useEffect(() => setCheckType(undefined), [courseTaskId]);

return (
<>
Expand All @@ -64,7 +53,7 @@ export function BadReviewControllers({ courseTasks, courseId }: IBadReviewContro
<Select
placeholder="Select task"
style={{ width: 200 }}
onChange={(value: number) => setTaskId(value)}
onChange={(value: number) => setCourseTaskId(value)}
showSearch
optionFilterProp="label"
>
Expand All @@ -76,25 +65,28 @@ export function BadReviewControllers({ courseTasks, courseId }: IBadReviewContro
</Select>
</Col>
<Col>
<Button type="primary" href={`/api/v2/courses/${courseId}/cross-checks/${taskId}/csv`} disabled={!taskId}>
<Button
type="primary"
href={`/api/v2/courses/${courseId}/cross-checks/${courseTaskId}/csv`}
disabled={!courseTaskId}
>
Download solutions urls
</Button>
</Col>
<Col>
<Button type="primary" danger onClick={() => buttonHandler('Bad comment')} disabled={!taskId}>
<Button type="primary" danger onClick={() => buttonHandler('badcomment')} disabled={!courseTaskId}>
Bad comment
</Button>
</Col>
<Col>
<Button type="primary" danger onClick={() => buttonHandler('Did not check')} disabled={!taskId}>
<Button type="primary" danger onClick={() => buttonHandler('didnotcheck')} disabled={!courseTaskId}>
Didn't check
</Button>
</Col>
</Row>
<Modal
title={`Bad checkers in ${checkType}`}
open={isModalVisible}
width={1250}
width={1200}
style={{ top: 20 }}
onCancel={handleCancel}
footer={[
Expand All @@ -103,7 +95,8 @@ export function BadReviewControllers({ courseTasks, courseId }: IBadReviewContro
</Button>,
]}
>
{isLoading || !data ? <Spin /> : <BadReviewTable data={data} type={checkType!} />}
{dataRequest.loading ? <Spin /> : null}
{dataRequest.data && !dataRequest.loading ? <BadReviewTable data={dataRequest.data} type={checkType} /> : null}
</Modal>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { message, Table, Typography } from 'antd';
import { GithubUserLink } from 'components/GithubUserLink';
import React from 'react';
import { checkType, IBadReview } from './BadReviewControllers';
import { BadCommentCheckerDto, MaxScoreCheckerDto } from 'api';

interface IBadReviewTableProps {
data: IBadReview[];
import { checkType } from './BadReviewControllers';

type Props = {
data: BadCommentCheckerDto[] | MaxScoreCheckerDto[];
type: checkType;
}
};

export const BadReviewTable = ({ data, type }: IBadReviewTableProps) => {
export const BadReviewTable = ({ data, type }: Props) => {
const { Text } = Typography;

const columns = [
Expand All @@ -18,11 +20,6 @@ export const BadReviewTable = ({ data, type }: IBadReviewTableProps) => {
key: 'checkerGithubId',
render: (id: string) => <GithubUserLink value={id} />,
},
{
title: 'Task',
dataIndex: 'taskName',
key: 'taskName',
},
{
title: 'Student',
dataIndex: 'studentGithubId',
Expand All @@ -49,17 +46,25 @@ export const BadReviewTable = ({ data, type }: IBadReviewTableProps) => {
let columnsType;

switch (type) {
case 'Bad comment':
case 'badcomment':
columnsType = columns.filter(c => c.dataIndex !== 'studentAvgScore');
break;
case 'Did not check':
case 'didnotcheck':
columnsType = columns.filter(c => c.dataIndex !== 'comment');
break;
default:
message.error('Something went wrong');
}

if (!columnsType) {
return null;
}

if (data.length === 0) {
return <Text>No data</Text>;
}

return (
<>{data.length ? <Table columns={columnsType} dataSource={data} scroll={{ x: true }} /> : <Text>No data</Text>}</>
<Table<BadCommentCheckerDto | MaxScoreCheckerDto> columns={columnsType} dataSource={data} scroll={{ x: true }} />
);
};
59 changes: 13 additions & 46 deletions client/src/services/check.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,20 @@
import globalAxios, { AxiosInstance } from 'axios';
import { message } from 'antd';
import { IBadReview, checkType } from 'modules/CrossCheckPairs/components/BadReview/BadReviewControllers';
import { CoursesTasksApi } from 'api';

type routesType = Exclude<checkType, 'No type'>;

const ROUTES: Record<routesType, string> = {
'Bad comment': 'badcomment',
'Did not check': 'maxscore',
};
const courseTaskService = new CoursesTasksApi();

export class CheckService {
private axios: AxiosInstance;
private cache: Record<number, Record<routesType, IBadReview[]>>;

constructor() {
this.axios = globalAxios.create({ baseURL: `/api/` });
this.cache = {};
}

async getData(taskId: number, type: checkType, courseId: number) {
let dataFromService: IBadReview[] = [];
try {
switch (type) {
case 'Bad comment':
case 'Did not check':
dataFromService = await this.getDataFromServer(taskId, type, courseId);
break;
case 'No type':
break;
default:
throw new Error('Unsupported type');
async getData(courseTaskId: number, type: 'badcomment' | 'didnotcheck' | undefined, courseId: number) {
switch (type) {
case 'badcomment': {
const response = await courseTaskService.getCourseTaskBadComments(courseId, courseTaskId);
return response.data;
}
} catch {
message.error('Something went wrong');
}
return dataFromService;
}

private async getDataFromServer(taskId: number, type: routesType, courseId: number) {
if (this.cache?.[taskId]?.[type]) return this.cache[taskId][type];
const result = await this.axios.get(`checks/${ROUTES[type]}/${courseId}/${taskId}`);
this.saveToCache(taskId, type, result.data.data);
return result.data.data;
}

private saveToCache(taskId: number, type: routesType, data: IBadReview[]) {
if (!this.cache[taskId]) {
this.cache[taskId] = {} as Record<routesType, IBadReview[]>;
case 'didnotcheck': {
const response = await courseTaskService.getCourseTaskMaxScoreCheckers(courseId, courseTaskId);
return response.data;
}
default:
return [];
}
this.cache[taskId][type] = data;
}
}
Loading
Loading