feat(course): reset student course progress (#671)
* feat(course): add reset student course progress action Give course admins and tutors a destructive reset-progress flow on the student people detail page. The backend clears learner progress for one (courseId, memberId) pair in a single transaction while keeping the groupmember enrolled, and logs an audit summary. The dashboard shows a confirmation dialog with impact counts from analytics before deleting. Co-authored-by: Rotimi Best <[email protected]> * fix(i18n): add localized reset progress strings for all locales Co-authored-by: Rotimi Best <[email protected]> * fix(course): scope reset progress to course data only Stop deleting org-level community questions and answers during course progress reset. Fix course newsfeed deletion order so comments on a student's posts are removed before the posts themselves. Restrict reset to student members, move analytics loading to the person detail layout, and rename impact copy to course newsfeed only. Co-authored-by: Rotimi Best <[email protected]> * fix(course): address Greptile review on reset progress Make progressImpact opt-in via includeProgressImpact query param so analytics requests avoid extra DB work unless the reset dialog needs it. Count only student-authored course newsfeed posts and comments in the impact summary to avoid double-counting replies on the student's posts. Co-authored-by: Rotimi Best <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
co-authored by
Rotimi Best
Cursor Agent
parent
e43d8a2444
commit
a695c97eda
@@ -24,11 +24,15 @@ import {
|
||||
getProfileCourseProgress,
|
||||
getUserExercisesStats
|
||||
} from '@cio/db/queries/analytics/analytics';
|
||||
import { getStudentCourseProgressImpactCounts } from '@cio/db/queries/course/reset-progress';
|
||||
import { getCourseGroupId } from '@cio/db/queries/course/people';
|
||||
|
||||
import { ContentType, ROLE } from '@cio/utils/constants';
|
||||
import type { TCourse } from '@cio/db/types';
|
||||
import type { TCourseCreate } from '@cio/utils/validation/course';
|
||||
import { db } from '@cio/db/drizzle';
|
||||
import * as schema from '@cio/db/schema';
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
import { exerciseBelongsToCourse } from '@cio/db/queries/course/certification-exercise';
|
||||
import { updateExercise, updateExercisesSectionId } from '@cio/db/queries/exercise/exercise';
|
||||
import { getProfileById } from '@cio/db/queries/auth';
|
||||
@@ -549,7 +553,11 @@ export async function getCourseAnalytics(courseId: string) {
|
||||
* @param userId User ID (profile ID)
|
||||
* @returns User course analytics data
|
||||
*/
|
||||
export async function getUserCourseAnalytics(courseId: string, userId: string) {
|
||||
export async function getUserCourseAnalytics(
|
||||
courseId: string,
|
||||
userId: string,
|
||||
options: { includeProgressImpact?: boolean } = {}
|
||||
) {
|
||||
try {
|
||||
// Get user profile
|
||||
const profile = await getProfileById(userId);
|
||||
@@ -581,6 +589,28 @@ export async function getUserCourseAnalytics(courseId: string, userId: string) {
|
||||
const completedExercises = userExercisesStats.filter((exercise) => exercise.isCompleted).length;
|
||||
const totalExercises = courseProgress.exercises_count || 0;
|
||||
|
||||
let progressImpact = null;
|
||||
|
||||
if (options.includeProgressImpact) {
|
||||
const groupId = await getCourseGroupId(courseId);
|
||||
|
||||
if (groupId) {
|
||||
const [groupMember] = await db
|
||||
.select({ id: schema.groupmember.id })
|
||||
.from(schema.groupmember)
|
||||
.where(and(eq(schema.groupmember.groupId, groupId), eq(schema.groupmember.profileId, userId)))
|
||||
.limit(1);
|
||||
|
||||
if (groupMember) {
|
||||
progressImpact = await getStudentCourseProgressImpactCounts({
|
||||
courseId,
|
||||
groupMemberId: groupMember.id,
|
||||
profileId: userId
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: userId,
|
||||
@@ -593,7 +623,8 @@ export async function getUserCourseAnalytics(courseId: string, userId: string) {
|
||||
userExercisesStats,
|
||||
totalExercises,
|
||||
completedExercises,
|
||||
progressPercentage
|
||||
progressPercentage,
|
||||
progressImpact
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
|
||||
Reference in New Issue
Block a user