# API Статистики v2 - Документация для Frontend
## Обзор
Эндпоинт `/challenge/stats/v2` предоставляет расширенную статистику системы проверки заданий с детальными данными для построения таблиц и прогресс-баров.
## Эндпоинт
```
GET /challenge/stats/v2
```
### Аутентификация
Не требуется.
### Параметры запроса
| Параметр | Тип | Обязательный | Описание |
|----------|-----|--------------|----------|
| `chainId` | string | Нет | ID цепочки для фильтрации статистики. Если указан, возвращается статистика только по заданиям из этой цепочки |
#### Примеры использования
Получить полную статистику:
```
GET /challenge/stats/v2
```
Получить статистику только по одной цепочке:
```
GET /challenge/stats/v2?chainId=507f1f77bcf86cd799439031
```
## Структура ответа
```typescript
{
success: true,
body: {
// ========== БАЗОВАЯ СТАТИСТИКА (из v1) ==========
users: number, // Общее количество пользователей
tasks: number, // Общее количество заданий
chains: number, // Общее количество цепочек
submissions: {
total: number, // Всего попыток
accepted: number, // Принятых
rejected: number, // Отклоненных
pending: number, // Ожидающих проверки
inProgress: number // В процессе проверки
},
averageCheckTimeMs: number, // Среднее время проверки в мс
queue: {
queueLength: number,
waiting: number,
inProgress: number,
maxConcurrency: number,
currentlyProcessing: number
},
// ========== НОВЫЕ ДАННЫЕ V2 ==========
// Таблица заданий с детальной статистикой
tasksTable: Array<{
taskId: string,
title: string,
totalAttempts: number, // Всего попыток по всем пользователям
uniqueUsers: number, // Количество уникальных пользователей
acceptedCount: number, // Количество успешных прохождений
successRate: number, // Процент успешного прохождения (0-100)
averageAttemptsToSuccess: number // Среднее количество попыток до успеха
}>,
// Активные участники с прогрессом
activeParticipants: Array<{
userId: string,
nickname: string,
totalSubmissions: number, // Всего попыток пользователя
completedTasks: number, // Завершенных заданий
chainProgress: Array<{ // Прогресс по каждой цепочке
chainId: string,
chainName: string,
totalTasks: number, // Всего заданий в цепочке
completedTasks: number, // Завершено заданий
progressPercent: number // Процент прохождения (0-100)
}>
}>,
// Детальная информация по цепочкам
chainsDetailed: Array<{
chainId: string,
name: string,
totalTasks: number,
tasks: Array<{ // Список заданий в цепочке
taskId: string,
title: string,
description: string
}>,
participantProgress: Array<{ // Прогресс каждого участника
userId: string,
nickname: string,
taskProgress: Array<{ // Статус по каждому заданию
taskId: string,
taskTitle: string,
status: 'not_started' | 'pending' | 'in_progress' | 'needs_revision' | 'completed'
}>,
completedCount: number, // Завершено заданий
progressPercent: number // Процент прохождения (0-100)
}>
}>
}
}
```
## Пример ответа
```json
{
"success": true,
"body": {
"users": 34,
"tasks": 22,
"chains": 2,
"submissions": {
"total": 39,
"accepted": 16,
"rejected": 23,
"pending": 0,
"inProgress": 0
},
"averageCheckTimeMs": 2043,
"queue": {
"queueLength": 0,
"waiting": 0,
"inProgress": 0,
"maxConcurrency": 1,
"currentlyProcessing": 0
},
"tasksTable": [
{
"taskId": "507f1f77bcf86cd799439011",
"title": "Создание REST API",
"totalAttempts": 45,
"uniqueUsers": 12,
"acceptedCount": 8,
"successRate": 67,
"averageAttemptsToSuccess": 2.3
},
{
"taskId": "507f1f77bcf86cd799439012",
"title": "Работа с базой данных",
"totalAttempts": 38,
"uniqueUsers": 10,
"acceptedCount": 6,
"successRate": 60,
"averageAttemptsToSuccess": 3.1
}
],
"activeParticipants": [
{
"userId": "507f1f77bcf86cd799439021",
"nickname": "student1",
"totalSubmissions": 15,
"completedTasks": 5,
"chainProgress": [
{
"chainId": "507f1f77bcf86cd799439031",
"chainName": "Основы Backend",
"totalTasks": 10,
"completedTasks": 5,
"progressPercent": 50
},
{
"chainId": "507f1f77bcf86cd799439032",
"chainName": "Frontend разработка",
"totalTasks": 8,
"completedTasks": 0,
"progressPercent": 0
}
]
}
],
"chainsDetailed": [
{
"chainId": "507f1f77bcf86cd799439031",
"name": "Основы Backend",
"totalTasks": 10,
"tasks": [
{
"taskId": "507f1f77bcf86cd799439011",
"title": "Создание REST API",
"description": "Создайте простой REST API с использованием Express.js"
},
{
"taskId": "507f1f77bcf86cd799439012",
"title": "Работа с базой данных",
"description": "Интегрируйте MongoDB в ваше приложение"
}
],
"participantProgress": [
{
"userId": "507f1f77bcf86cd799439021",
"nickname": "student1",
"taskProgress": [
{
"taskId": "507f1f77bcf86cd799439011",
"taskTitle": "Создание REST API",
"status": "completed"
},
{
"taskId": "507f1f77bcf86cd799439012",
"taskTitle": "Работа с базой данных",
"status": "needs_revision"
}
],
"completedCount": 1,
"progressPercent": 10
}
]
}
]
}
}
```
## Фильтрация по цепочке
При передаче параметра `chainId`:
1. **tasksTable** - содержит только задания из указанной цепочки
2. **activeParticipants** - включает только участников, которые делали попытки по заданиям этой цепочки. В `chainProgress` будет информация только об указанной цепочке
3. **chainsDetailed** - содержит информацию только об указанной цепочке
### Пример фильтрованного ответа
```bash
curl http://localhost:3000/challenge/stats/v2?chainId=507f1f77bcf86cd799439031
```
```json
{
"success": true,
"body": {
"users": 34,
"tasks": 22,
"chains": 2,
"submissions": {
"total": 39,
"accepted": 16,
"rejected": 23,
"pending": 0,
"inProgress": 0
},
"averageCheckTimeMs": 2043,
"queue": { ... },
"tasksTable": [
// Только задания из цепочки 507f1f77bcf86cd799439031
{
"taskId": "507f1f77bcf86cd799439011",
"title": "Создание REST API",
"totalAttempts": 45,
"uniqueUsers": 12,
"acceptedCount": 8,
"successRate": 67,
"averageAttemptsToSuccess": 2.3
}
],
"activeParticipants": [
// Только участники с попытками в этой цепочке
{
"userId": "507f1f77bcf86cd799439021",
"nickname": "student1",
"totalSubmissions": 10,
"completedTasks": 3,
"chainProgress": [
// Только одна цепочка
{
"chainId": "507f1f77bcf86cd799439031",
"chainName": "Основы Backend",
"totalTasks": 10,
"completedTasks": 3,
"progressPercent": 30
}
]
}
],
"chainsDetailed": [
// Только указанная цепочка
{
"chainId": "507f1f77bcf86cd799439031",
"name": "Основы Backend",
"totalTasks": 10,
"tasks": [...],
"participantProgress": [...]
}
]
}
}
```
## Использование в UI компонентах
### 1. Таблица заданий
Используйте `tasksTable` для отображения статистики по каждому заданию:
```tsx
// React пример
function TasksTable({ tasksTable }) {
return (
Название задания
Попыток
Уникальных пользователей
Успешно завершено
% успеха
Среднее попыток до успеха
{tasksTable.map(task => (
{task.title}
{task.totalAttempts}
{task.uniqueUsers}
{task.acceptedCount}
{task.successRate}%
{task.averageAttemptsToSuccess.toFixed(1)}
))}
);
}
```
### 2. Прогресс-бары участников
Используйте `activeParticipants` для отображения прогресса каждого участника:
```tsx
// React пример
function ParticipantProgress({ activeParticipants }) {
return (
);
}
```
### 3. Детальный прогресс по цепочкам
Используйте `chainsDetailed` для отображения детального прогресса всех участников в рамках каждой цепочки:
```tsx
// React пример
function ChainDetailedView({ chainsDetailed }) {
return (
{chainsDetailed.map(chain => (
{chain.name}
Заданий в цепочке: {chain.totalTasks}
{/* Список заданий */}
Задания:
{chain.tasks.map(task => (
{task.title}
{task.description}
))}
{/* Прогресс участников */}
Прогресс участников:
Участник
{chain.tasks.map(task => (
{task.title}
))}
Прогресс
{chain.participantProgress.map(participant => (
{participant.nickname}
{participant.taskProgress.map(taskProg => (
))}
{participant.progressPercent}%
))}
))}
);
}
// Компонент для отображения статуса
function StatusBadge({ status }) {
const statusConfig = {
'not_started': { label: 'Не начато', color: 'gray' },
'pending': { label: 'Ожидает', color: 'yellow' },
'in_progress': { label: 'В процессе', color: 'blue' },
'needs_revision': { label: 'Доработка', color: 'orange' },
'completed': { label: 'Завершено', color: 'green' }
};
const config = statusConfig[status];
return (
{config.label}
);
}
```
## Рекомендации по использованию
### Производительность
- Эндпоинт выполняет множество агрегаций, поэтому может работать медленно при большом количестве данных
- Рекомендуется кэшировать результат на клиенте на 30-60 секунд
- Используйте loading индикаторы во время загрузки данных
```tsx
// Пример с кэшированием
function useStatsV2(chainId?: string) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const lastFetch = useRef(0);
const fetchStats = async (force = false) => {
const now = Date.now();
// Кэш на 60 секунд
if (!force && data && (now - lastFetch.current) < 60000) {
return data;
}
setLoading(true);
try {
const url = chainId
? `/challenge/stats/v2?chainId=${chainId}`
: '/challenge/stats/v2';
const response = await fetch(url);
const result = await response.json();
if (result.success) {
setData(result.body);
lastFetch.current = now;
}
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchStats();
}, [chainId]); // Перезагружаем при смене цепочки
return { data, loading, error, refetch: () => fetchStats(true) };
}
// Использование
function ChainStatistics() {
const [selectedChainId, setSelectedChainId] = useState();
const { data, loading } = useStatsV2(selectedChainId);
if (loading) return
Загрузка...
;
return (
);
}
```
### Фильтрация и сортировка
Все массивы данных можно фильтровать и сортировать на клиенте:
```tsx
// Сортировка таблицы заданий по проценту успеха
const sortedTasks = [...tasksTable].sort((a, b) => b.successRate - a.successRate);
// Фильтрация активных участников с прогрессом > 50%
const activeStudents = activeParticipants.filter(p =>
p.chainProgress.some(c => c.progressPercent > 50)
);
// Поиск участника по имени
const searchParticipant = (query) =>
activeParticipants.filter(p =>
p.nickname.toLowerCase().includes(query.toLowerCase())
);
```
### Визуализация данных
Для построения графиков и диаграмм используйте библиотеки типа:
- **Chart.js** / **Recharts** - для графиков прогресса
- **AG Grid** / **TanStack Table** - для таблиц с сортировкой
- **React Progress Bar** - для прогресс-баров
```tsx
// Пример с Chart.js
import { Bar } from 'react-chartjs-2';
function TasksSuccessChart({ tasksTable }) {
const data = {
labels: tasksTable.map(t => t.title),
datasets: [{
label: 'Процент успеха',
data: tasksTable.map(t => t.successRate),
backgroundColor: 'rgba(75, 192, 192, 0.6)',
}]
};
return ;
}
```
## Когда использовать фильтрацию по цепочке
### Используйте `chainId` когда:
1. **Страница отдельной цепочки** - пользователь просматривает детали конкретной цепочки
```tsx
function ChainDetailsPage({ chainId }) {
const { data } = useStatsV2(chainId);
return ;
}
```
2. **Улучшение производительности** - когда нужны данные только по одной цепочке, фильтрация на сервере работает быстрее
3. **Фокус на конкретной программе** - преподаватель хочет видеть прогресс по конкретному курсу/модулю
### НЕ используйте `chainId` когда:
1. **Общий дашборд** - нужна статистика по всем цепочкам
2. **Сравнение цепочек** - нужно показать метрики по всем цепочкам одновременно
3. **Общая аналитика** - нужны агрегированные данные по всей системе
## Различия между v1 и v2
| Параметр | v1 (/stats) | v2 (/stats/v2) | v2 с chainId |
|----------|-------------|----------------|--------------|
| Базовая статистика | ✅ | ✅ | ✅ |
| Таблица заданий | ❌ | ✅ (все) | ✅ (фильтр) |
| Прогресс участников | ❌ | ✅ (все) | ✅ (фильтр) |
| Детальный прогресс по цепочкам | ❌ | ✅ (все) | ✅ (одна) |
| Статистика по попыткам | Общая | Детальная | Детальная (фильтр) |
| Скорость работы | Быстро | Медленнее | Средняя |
## Обработка ошибок
```tsx
async function fetchStatsV2(chainId?: string) {
try {
const url = chainId
? `/challenge/stats/v2?chainId=${chainId}`
: '/challenge/stats/v2';
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error?.message || 'Unknown error');
}
return data.body;
} catch (error) {
console.error('Failed to fetch stats v2:', error);
// Специальная обработка для неверного chainId
if (error.message.includes('Chain not found')) {
showNotification('Цепочка не найдена', 'error');
// Перенаправить на страницу всех цепочек
window.location.href = '/chains';
} else {
showNotification('Не удалось загрузить статистику', 'error');
}
}
}
```
### Типичные ошибки
| Ошибка | Причина | Решение |
|--------|---------|---------|
| `Chain not found` | Передан несуществующий `chainId` | Проверить ID цепочки, показать ошибку пользователю |
| `500 Internal Server Error` | Проблема на сервере | Показать общее сообщение об ошибке, повторить запрос |
| Timeout | Слишком много данных | Использовать фильтрацию по `chainId` для уменьшения объема данных |
## Дополнительные возможности
### Экспорт данных
Данные можно экспортировать в CSV или Excel для анализа:
```typescript
function exportToCSV(tasksTable) {
const headers = ['Название', 'Попыток', 'Пользователей', 'Успешно', '% успеха', 'Средние попытки'];
const rows = tasksTable.map(task => [
task.title,
task.totalAttempts,
task.uniqueUsers,
task.acceptedCount,
task.successRate,
task.averageAttemptsToSuccess
]);
const csv = [headers, ...rows]
.map(row => row.join(','))
.join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'tasks-statistics.csv';
a.click();
}
```
## Заключение
Эндпоинт `/stats/v2` предоставляет все необходимые данные для построения информативных дашбордов с таблицами заданий и прогресс-барами участников. Комбинируйте различные части данных для создания удобных UI компонентов.
Для дополнительной информации см. также:
- [CHALLENGE_FRONTEND_GUIDE.md](./CHALLENGE_FRONTEND_GUIDE.md) - общее руководство по работе с Challenge API
- [CHALLENGE_REACT_EXAMPLE.md](./CHALLENGE_REACT_EXAMPLE.md) - примеры React компонентов