Skip to content

Отображение закрепления преподавателей за студентами #479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 20, 2025
Merged
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
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using HwProj.APIGateway.API.Models.Statistics;
using HwProj.AuthService.Client;
using HwProj.CoursesService.Client;
using HwProj.Models.AuthService.DTO;
using HwProj.Models.CoursesService.DTO;
using HwProj.Models.CoursesService.ViewModels;
using HwProj.Models.Roles;
using HwProj.SolutionsService.Client;
@@ -19,7 +23,7 @@ public class StatisticsController : AggregationController
private readonly ISolutionsServiceClient _solutionClient;
private readonly ICoursesServiceClient _coursesClient;

public StatisticsController(ISolutionsServiceClient solutionClient, IAuthServiceClient authServiceClient,
public StatisticsController(ISolutionsServiceClient solutionClient, IAuthServiceClient authServiceClient,
ICoursesServiceClient coursesServiceClient) :
base(authServiceClient)
{
@@ -56,32 +60,43 @@ public async Task<IActionResult> GetCourseStatistics(long courseId)
if (statistics == null) return Forbid();

var studentIds = statistics.Select(t => t.StudentId).ToArray();
var students = await AuthServiceClient.GetAccountsData(studentIds);
var getStudentsTask = AuthServiceClient.GetAccountsData(studentIds);

// Получаем пары <студент, закрепленные преподаватели (те, которые его явно в фильтре выбрали)>
var mentorsToStudents = await _coursesClient.GetMentorsToAssignedStudents(courseId);
var studentsToMentors = await GetStudentsToMentorsDictionary(mentorsToStudents);

var result = statistics.Zip(students, (stats, student) => new StatisticsCourseMatesModel
{
Id = student.UserId,
Name = student.Name,
Surname = student.Surname,
Homeworks = stats.Homeworks
}).OrderBy(t => t.Surname).ThenBy(t => t.Name);
var result = statistics.Zip(
await getStudentsTask,
(stats, student) =>
{
studentsToMentors.TryGetValue(student.UserId, out var reviewers);
return new StatisticsCourseMatesModel()
{
Id = student.UserId,
Name = student.Name,
Surname = student.Surname,
Reviewers = reviewers ?? Array.Empty<AccountDataDto>(),
Homeworks = stats.Homeworks
};
}).OrderBy(t => t.Surname).ThenBy(t => t.Name);

return Ok(result);
}

[HttpGet("{courseId}/charts")]
[ProducesResponseType(typeof(AdvancedCourseStatisticsViewModel), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetChartStatistics(long courseId)
{
var course = await _coursesClient.GetCourseById(courseId);
if (course == null)
if (course == null)
return Forbid();

var statistics = await _solutionClient.GetCourseStatistics(courseId, UserId);
var studentIds = statistics.Select(t => t.StudentId).ToArray();
var studentsData = await AuthServiceClient.GetAccountsData(studentIds);
var students = statistics.Zip(studentsData,

var students = statistics.Zip(studentsData,
(stats, student) => new StatisticsCourseMatesModel
{
Id = student.UserId,
@@ -105,8 +120,37 @@ public async Task<IActionResult> GetChartStatistics(long courseId)
AverageStudentSolutions = statisticsMeasure.AverageStudentSolutions,
BestStudentSolutions = statisticsMeasure.BestStudentSolutions
};

return Ok(result);
}

private async Task<Dictionary<string, AccountDataDto[]>> GetStudentsToMentorsDictionary(
MentorToAssignedStudentsDTO[] mentorsToStudents)
{
var mentorsIds = mentorsToStudents.Select(mts => mts.MentorId).ToArray();
var mentorsAccountData = await AuthServiceClient.GetAccountsData(mentorsIds);
var mentorIdToAccountData = mentorsAccountData
.ToDictionary(
accountData => accountData.UserId,
accountData => accountData
);

return mentorsToStudents
.SelectMany(m =>
m.SelectedStudentsIds.Select(studentId =>
new
{
StudentId = studentId,
Reviewer = mentorIdToAccountData[m.MentorId]
})
)
.GroupBy(sr => sr.StudentId)
.ToDictionary(
groups => groups.Key,
groups => groups.Select(sr => sr.Reviewer)
.Distinct()
.ToArray()
);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using HwProj.Models.AuthService.DTO;
using HwProj.Models.StatisticsService;

namespace HwProj.APIGateway.API.Models.Statistics
@@ -8,6 +10,7 @@ public class StatisticsCourseMatesModel
public string Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public AccountDataDto[] Reviewers { get; set; } = Array.Empty<AccountDataDto>();
public List<StatisticsCourseHomeworksModel> Homeworks { get; set; } = new List<StatisticsCourseHomeworksModel>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Collections.Generic;

namespace HwProj.Models.CoursesService.DTO
{
public class MentorToAssignedStudentsDTO
{
public string MentorId { get; set; }

public List<string> SelectedStudentsIds { get; set; } = new List<string>();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AutoMapper;
using HwProj.CoursesService.API.Filters;
@@ -9,6 +10,7 @@
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using System.Net;
using HwProj.AuthService.Client;
using HwProj.CoursesService.API.Repositories;
using HwProj.Models.AuthService.DTO;
using HwProj.Models.CoursesService.DTO;
@@ -24,16 +26,19 @@ namespace HwProj.CoursesService.API.Controllers
public class CoursesController : Controller
{
private readonly ICoursesService _coursesService;
private readonly ICourseFilterService _courseFilterService;
private readonly IHomeworksRepository _homeworksRepository;
private readonly IMapper _mapper;

public CoursesController(ICoursesService coursesService,
IHomeworksRepository homeworksRepository,
IMapper mapper)
IMapper mapper,
ICourseFilterService courseFilterService)
{
_coursesService = coursesService;
_homeworksRepository = homeworksRepository;
_mapper = mapper;
_courseFilterService = courseFilterService;
}

[HttpGet]
@@ -238,5 +243,14 @@ public async Task<IActionResult> GetAllTagsForCourse(long courseId)

return Ok(result);
}

[HttpGet("getMentorsToStudents/{courseId}")]
[ProducesResponseType(typeof(MentorToAssignedStudentsDTO), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetMentorsToAssignedStudents(long courseId)
{
var mentorIds = await _coursesService.GetCourseLecturers(courseId);
var mentorsToAssignedStudents = await _courseFilterService.GetAssignedStudentsIds(courseId, mentorIds);
return Ok(mentorsToAssignedStudents);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -23,6 +23,15 @@ public CourseFilterRepository(CourseContext context) : base(context)
return userToCourseFilter?.CourseFilter;
}

public async Task<List<UserToCourseFilter>> GetAsync(string[] userIds, long courseId)
{
return await Context.Set<UserToCourseFilter>()
.AsNoTracking()
.Where(u => userIds.Contains(u.UserId) && u.CourseId == courseId)
.Include(ucf => ucf.CourseFilter)
.ToListAsync();
}

public async Task<List<UserToCourseFilter>> GetAsync(string userId, long[] courseIds)
{
return await Context.Set<UserToCourseFilter>()
Original file line number Diff line number Diff line change
@@ -8,8 +8,8 @@ namespace HwProj.CoursesService.API.Repositories
public interface ICourseFilterRepository : ICrudRepository<CourseFilter, long>
{
Task<CourseFilter?> GetAsync(string userId, long courseId);
Task<List<UserToCourseFilter>> GetAsync(string[] userIds, long courseId);
Task<List<UserToCourseFilter>> GetAsync(string userId, long[] courseIds);

Task<long> AddAsync(CourseFilter courseFilter, string userId, long courseId);
}
}
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
using HwProj.CoursesService.API.Models;
using HwProj.CoursesService.API.Repositories;
using HwProj.Models.CoursesService;
using HwProj.Models.CoursesService.DTO;
using HwProj.Models.CoursesService.ViewModels;
using HwProj.Models.Result;

@@ -76,6 +77,20 @@ public async Task<CourseDTO> ApplyFilter(CourseDTO courseDto, string userId)
return ApplyFilterInternal(courseDto, courseFilter);
}

public async Task<MentorToAssignedStudentsDTO[]> GetAssignedStudentsIds(long courseId, string[] mentorsIds)
{
var usersCourseFilters = await _courseFilterRepository.GetAsync(mentorsIds, courseId);

return usersCourseFilters
.Where(u => u.CourseFilter.Filter.HomeworkIds.Count == 0)
.Select(u => new MentorToAssignedStudentsDTO
{
MentorId = u.UserId,
SelectedStudentsIds = u.CourseFilter.Filter.StudentIds
})
.ToArray();
}

private async Task<long> AddCourseFilter(Filter filter, long courseId, string userId)
{
var courseFilterId =
@@ -131,4 +146,4 @@ private CourseDTO ApplyFilterInternal(CourseDTO courseDto, CourseFilter? courseF
};
}
}
}
}
Original file line number Diff line number Diff line change
@@ -13,5 +13,6 @@ public interface ICourseFilterService
Task UpdateAsync(long courseFilterId, Filter filter);
Task<CourseDTO[]> ApplyFiltersToCourses(string userId, CourseDTO[] courses);
Task<CourseDTO> ApplyFilter(CourseDTO courseDto, string userId);
Task<MentorToAssignedStudentsDTO[]> GetAssignedStudentsIds(long courseId, string[] mentorsIds);
}
}
Original file line number Diff line number Diff line change
@@ -585,6 +585,17 @@ public async Task AddAnswerForQuestion(AddAnswerForQuestionDto answer)
await _httpClient.SendAsync(httpRequest);
}

public async Task<MentorToAssignedStudentsDTO[]> GetMentorsToAssignedStudents(long courseId)
{
using var httpRequest = new HttpRequestMessage(
HttpMethod.Get,
_coursesServiceUri + $"api/Courses/getMentorsToStudents/{courseId}");

httpRequest.TryAddUserId(_httpContextAccessor);
var response = await _httpClient.SendAsync(httpRequest);
return await response.DeserializeAsync<MentorToAssignedStudentsDTO[]>();
}

public async Task<bool> Ping()
{
try
@@ -598,4 +609,4 @@ public async Task<bool> Ping()
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@ public interface ICoursesServiceClient
Task AddQuestionForTask(AddTaskQuestionDto question);
Task<GetTaskQuestionDto[]> GetQuestionsForTask(long taskId);
Task AddAnswerForQuestion(AddAnswerForQuestionDto answer);
Task<MentorToAssignedStudentsDTO[]> GetMentorsToAssignedStudents(long courseId);
Task<bool> Ping();
}
}
6 changes: 6 additions & 0 deletions hwproj.front/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1862,6 +1862,12 @@ export interface StatisticsCourseMatesModel {
* @memberof StatisticsCourseMatesModel
*/
surname?: string;
/**
*
* @type {Array<AccountDataDto>}
* @memberof StatisticsCourseMatesModel
*/
reviewers?: Array<AccountDataDto>;
/**
*
* @type {Array<StatisticsCourseHomeworksModel>}
16 changes: 14 additions & 2 deletions hwproj.front/src/components/Courses/StudentStats.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, {useEffect, useState} from "react";
import {CourseViewModel, HomeworkViewModel, StatisticsCourseMatesModel, HomeworkTaskViewModel} from "../../api/";
import {CourseViewModel, HomeworkViewModel, StatisticsCourseMatesModel} from "../../api/";
import {useNavigate, useParams} from 'react-router-dom';
import {Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@material-ui/core";
import StudentStatsCell from "../Tasks/StudentStatsCell";
import {Alert, Button, Chip} from "@mui/material";
import {Alert, Button, Chip, Typography} from "@mui/material";
import {grey} from "@material-ui/core/colors";
import StudentStatsUtils from "../../services/StudentStatsUtils";
import ShowChartIcon from "@mui/icons-material/ShowChart";
@@ -226,6 +226,18 @@ const StudentStats: React.FC<IStudentStatsProps> = (props) => {
variant={"head"}
>
{cm.surname} {cm.name}
<Typography
style={{
color: "GrayText",
fontSize: "12px",
lineHeight: '1.2'
}}
>
{cm.reviewers && cm.reviewers
.filter(r => r.userId !== props.userId)
.map(r => `${r.name} ${r.surname}`)
.join(', ')}
</Typography>
</TableCell>
{hasHomeworks && <TableCell
align="center"