From a42bc64193a5fa646e6df6c1b778eef9bfc520ba Mon Sep 17 00:00:00 2001 From: Vladislav Kopnin Date: Thu, 29 Oct 2026 21:05:44 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=BA=D1=80=D0=B0=D1=8F=20=D0=B0=D1=80?= =?UTF-8?q?=D1=85=D0=B8=D0=B2=D0=B0,=20=D0=B8=D1=81=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=B8,=20=D0=BF=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B8=20=D0=BB=D0=B8=D0=BC=D0=B8=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=8B=D1=82=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/09-teacher-student-profile.md | 4 ++-- docs/evidence/baseline-2026-10-29.md | 2 +- .../main/kotlin/ru/ysu/lms/identity/roster/GroupService.kt | 6 +++--- .../ru/ysu/lms/progress/profile/StudentProfileService.kt | 2 +- .../ru/ysu/lms/progress/StudentProfileIntegrationTest.kt | 4 ++++ .../kotlin/ru/ysu/lms/web/admin/AdminGroupController.kt | 5 +++-- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/09-teacher-student-profile.md b/docs/09-teacher-student-profile.md index d5acb66..1223320 100644 --- a/docs/09-teacher-student-profile.md +++ b/docs/09-teacher-student-profile.md @@ -152,7 +152,7 @@ Authorization: Bearer - `author` может быть `null`, если автор не сохранён (старые данные). - `averageScore` — среднее по выставленным оценкам, `null` если оценок нет. - `submissions` — последние попытки по убыванию `seq`, по умолчанию не более - 50 (`submissionLimit`, максимум 500); `submissionsTotal` — всего попыток, + 50 (`submissionLimit`, максимум 500; `0` — все); `submissionsTotal` — всего попыток, `attemptsCount` — тоже полное число. Файлы — имена без пути. - `downloadUrl` относительный; web-BFF добавляет авторизацию. @@ -247,7 +247,7 @@ GET /api/v1/internal/courses/{courseId}/labs - публикации — `grade_publications`/`grade_publication_students` по группе и лабам. - Список лаб курса кэшируется на время запроса. - Для больших курсов — лимит попыток: `submissionLimit` (по умолчанию 50, - максимум 500) и `submissionsTotal` для индикатора «показаны последние N»; + максимум 500, `0` — все) и `submissionsTotal` для индикатора «показаны последние N»; `attemptsCount` не зависит от лимита. - Индексы: `submissions(student_id, lab_id, seq)` уже уникален; `grade_comments(student_id, lab_id)` уже есть; `lab_grades(lab_id)` добавлен; diff --git a/docs/evidence/baseline-2026-10-29.md b/docs/evidence/baseline-2026-10-29.md index 2647c5f..dc5b637 100644 --- a/docs/evidence/baseline-2026-10-29.md +++ b/docs/evidence/baseline-2026-10-29.md @@ -15,7 +15,7 @@ row-level XLSX-импорт и audit-события ростера. | Mutation `CourseAccessService` | 75% test strength (порог 70%) | | Mutation `PromotionPlanner` | 69% test strength (порог 60%) | | Browser E2E | 18/18 (Chromium/Firefox/WebKit) | -| Load smoke | 125 запросов, 0 ошибок, p95 0.942 с (порог < 1 с) | +| Load smoke | 125 запросов, 0 ошибок, p95 0.918 с (порог < 1 с) | ## Что добавлено в этой итерации diff --git a/services/identity-service/src/main/kotlin/ru/ysu/lms/identity/roster/GroupService.kt b/services/identity-service/src/main/kotlin/ru/ysu/lms/identity/roster/GroupService.kt index ea1669c..fe3c0b9 100644 --- a/services/identity-service/src/main/kotlin/ru/ysu/lms/identity/roster/GroupService.kt +++ b/services/identity-service/src/main/kotlin/ru/ysu/lms/identity/roster/GroupService.kt @@ -290,7 +290,7 @@ class GroupService( MembershipHistoryRow( id = event.id.toString(), studentId = event.studentId.toString(), - studentName = studentsById[event.studentId]?.fullName.orEmpty(), + studentName = studentsById[event.studentId]?.fullName?.takeIf { it.isNotBlank() } ?: event.studentId.toString(), fromGroupId = event.fromGroupId?.toString(), fromGroupName = event.fromGroupId?.let { groupsById[it]?.name }, toGroupId = event.toGroupId?.toString(), @@ -306,8 +306,8 @@ class GroupService( @Transactional fun restoreGroup(groupId: UUID): GroupDetail { val group = groups.findById(groupId).orElseThrow { NotFoundException("Группа не найдена") } - if (group.status == GroupStatus.ACTIVE) { - throw BadRequestException("Группа ${group.name} уже активна") + if (group.status != GroupStatus.ARCHIVED) { + throw BadRequestException("Группа ${group.name} не находится в архиве") } group.status = GroupStatus.ACTIVE groups.save(group) diff --git a/services/progress-service/src/main/kotlin/ru/ysu/lms/progress/profile/StudentProfileService.kt b/services/progress-service/src/main/kotlin/ru/ysu/lms/progress/profile/StudentProfileService.kt index d93446d..10b2b64 100644 --- a/services/progress-service/src/main/kotlin/ru/ysu/lms/progress/profile/StudentProfileService.kt +++ b/services/progress-service/src/main/kotlin/ru/ysu/lms/progress/profile/StudentProfileService.kt @@ -60,7 +60,7 @@ class StudentProfileService( submissionLimit: Int = DEFAULT_SUBMISSION_LIMIT, ): StudentCourseProfile { val role = normalizeRole(callerRole) - val effectiveLimit = submissionLimit.coerceIn(1, MAX_SUBMISSION_LIMIT) + val effectiveLimit = if (submissionLimit <= 0) Int.MAX_VALUE else submissionLimit.coerceIn(1, MAX_SUBMISSION_LIMIT) val course = academic.getCourse(courseId.toString(), token) val context = identity.getStudentContext(studentId.toString()) requireProfileAccess(course, context, studentId, callerId, role) diff --git a/services/progress-service/src/test/kotlin/ru/ysu/lms/progress/StudentProfileIntegrationTest.kt b/services/progress-service/src/test/kotlin/ru/ysu/lms/progress/StudentProfileIntegrationTest.kt index 846adc9..d5a1d73 100644 --- a/services/progress-service/src/test/kotlin/ru/ysu/lms/progress/StudentProfileIntegrationTest.kt +++ b/services/progress-service/src/test/kotlin/ru/ysu/lms/progress/StudentProfileIntegrationTest.kt @@ -288,6 +288,10 @@ class StudentProfileIntegrationTest { assertThat(lab.submissionsTotal).isEqualTo(3) assertThat(lab.attemptsCount).isEqualTo(3) assertThat(lab.submissions.map { it.seq }).containsExactly(3) + + val unlimited = profiles.profile(courseId, studentId, UUID.randomUUID(), "ADMIN", TEACHER_TOKEN, submissionLimit = 0) + val unlimitedLab = unlimited.labs.single() + assertThat(unlimitedLab.submissions.map { it.seq }).containsExactly(3, 2, 1) } private fun stubCourse( diff --git a/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminGroupController.kt b/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminGroupController.kt index 919e4b2..d352910 100644 --- a/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminGroupController.kt +++ b/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminGroupController.kt @@ -113,10 +113,11 @@ class AdminGroupController( model: Model, ): String { val safePage = page.coerceAtLeast(0) - val rows = api.listGroups(token(session), safePage, GROUP_PAGE_SIZE) + val loaded = api.listGroups(token(session), safePage, GROUP_PAGE_SIZE + 1) + val rows = loaded.take(GROUP_PAGE_SIZE) model.addAttribute("groups", rows) model.addAttribute("page", safePage) - model.addAttribute("hasNext", rows.size == GROUP_PAGE_SIZE) + model.addAttribute("hasNext", loaded.size > GROUP_PAGE_SIZE) return "admin/groups" }