Skip to content

Commit 3fc447f

Browse files
committed
fix: Update dashboard data live instead of mock data for admin/instructor/student. Change the ai generate quiz form in the quiz editor page to be similar to the one in the course editor page.
1 parent b9f1c4c commit 3fc447f

File tree

9 files changed

+240
-46
lines changed

9 files changed

+240
-46
lines changed

apps/api/src/admin/admin.service.spec.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ describe('AdminService', () => {
2121

2222
const mockCoursesService: Partial<jest.Mocked<CoursesService>> = {
2323
count: jest.fn(),
24+
countPublished: jest.fn(),
2425
countEnrollments: jest.fn(),
26+
countPublishedEnrollments: jest.fn(),
2527
getCourseGrowthStats: jest.fn(),
2628
getEnrollmentGrowthStats: jest.fn(),
2729
getRevenueGrowthStats: jest.fn(),
@@ -62,8 +64,10 @@ describe('AdminService', () => {
6264
usersService.getGrowthStats.mockResolvedValue([]);
6365
usersService.getActiveInstructorsCount.mockResolvedValue(5);
6466

65-
coursesService.count.mockResolvedValue(courseCount);
66-
coursesService.countEnrollments.mockResolvedValue(enrollmentCount);
67+
coursesService.countPublished.mockResolvedValue(courseCount);
68+
coursesService.countPublishedEnrollments.mockResolvedValue(
69+
enrollmentCount,
70+
);
6771
coursesService.getCourseGrowthStats.mockResolvedValue([]);
6872
coursesService.getEnrollmentGrowthStats.mockResolvedValue([]);
6973
coursesService.getRevenueGrowthStats.mockResolvedValue([]);
@@ -74,8 +78,8 @@ describe('AdminService', () => {
7478
const result = await service.getSystemStats();
7579

7680
expect(usersService.count).toHaveBeenCalledTimes(1);
77-
expect(coursesService.count).toHaveBeenCalledTimes(1);
78-
expect(coursesService.countEnrollments).toHaveBeenCalledTimes(1);
81+
expect(coursesService.countPublished).toHaveBeenCalledTimes(1);
82+
expect(coursesService.countPublishedEnrollments).toHaveBeenCalledTimes(1);
7983

8084
expect(result).toEqual({
8185
totalUsers: userCount,
@@ -97,8 +101,8 @@ describe('AdminService', () => {
97101
usersService.getGrowthStats.mockResolvedValue([]);
98102
usersService.getActiveInstructorsCount.mockResolvedValue(0);
99103

100-
coursesService.count.mockResolvedValue(0);
101-
coursesService.countEnrollments.mockResolvedValue(0);
104+
coursesService.countPublished.mockResolvedValue(0);
105+
coursesService.countPublishedEnrollments.mockResolvedValue(0);
102106
coursesService.getCourseGrowthStats.mockResolvedValue([]);
103107
coursesService.getEnrollmentGrowthStats.mockResolvedValue([]);
104108
coursesService.getRevenueGrowthStats.mockResolvedValue([]);

apps/api/src/admin/admin.service.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ export class AdminService {
2626

2727
async getSystemStats(): Promise<SystemStats> {
2828
const totalUsers = await this.usersService.count();
29-
const totalCourses = await this.coursesService.count();
30-
const totalEnrollments = await this.coursesService.countEnrollments();
29+
const totalCourses = await this.coursesService.countPublished();
30+
const totalEnrollments =
31+
await this.coursesService.countPublishedEnrollments();
3132

3233
// Get trend data (last 30 days)
3334
const userGrowth = await this.usersService.getGrowthStats(30);

apps/api/src/courses/courses.service.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,14 @@ export class CoursesService {
459459
const { archived = false, status = 'all' } = options;
460460

461461
const enrollments = await this.enrollmentRepository.find({
462-
where: { userId, isArchived: archived },
462+
where: {
463+
userId,
464+
isArchived: archived,
465+
course: {
466+
isPublished: true,
467+
status: CourseStatus.PUBLISHED,
468+
},
469+
},
463470
relations: [
464471
'course',
465472
'course.instructor',
@@ -974,12 +981,27 @@ export class CoursesService {
974981
return await this.coursesRepository.count();
975982
}
976983

984+
async countPublished(): Promise<number> {
985+
return await this.coursesRepository.count({
986+
where: { isPublished: true, status: CourseStatus.PUBLISHED },
987+
});
988+
}
989+
977990
// --- Statistics ---
978991

979992
async countEnrollments(): Promise<number> {
980993
return await this.enrollmentRepository.count();
981994
}
982995

996+
async countPublishedEnrollments(): Promise<number> {
997+
return await this.enrollmentRepository
998+
.createQueryBuilder('enrollment')
999+
.leftJoin('enrollment.course', 'course')
1000+
.where('course.isPublished = :isPublished', { isPublished: true })
1001+
.andWhere('course.status = :status', { status: CourseStatus.PUBLISHED })
1002+
.getCount();
1003+
}
1004+
9831005
// --- Moderation ---
9841006

9851007
async submitForApproval(id: string, userId: string): Promise<Course> {
@@ -1068,7 +1090,9 @@ export class CoursesService {
10681090
.createQueryBuilder('course')
10691091
.select("TO_CHAR(course.createdAt, 'YYYY-MM-DD')", 'date')
10701092
.addSelect('COUNT(*)', 'count')
1093+
.addSelect('COUNT(*)', 'count')
10711094
.where("course.createdAt >= NOW() - INTERVAL '30 days'")
1095+
.andWhere('course.isPublished = :isPublished', { isPublished: true })
10721096
.groupBy("TO_CHAR(course.createdAt, 'YYYY-MM-DD')")
10731097
.orderBy('date', 'ASC')
10741098
.getRawMany();
@@ -1082,9 +1106,12 @@ export class CoursesService {
10821106
async getEnrollmentGrowthStats(_days = 30): Promise<CourseDailyStat[]> {
10831107
const data = await this.enrollmentRepository
10841108
.createQueryBuilder('enrollment')
1109+
.leftJoin('enrollment.course', 'course')
10851110
.select("TO_CHAR(enrollment.enrolledAt, 'YYYY-MM-DD')", 'date')
10861111
.addSelect('COUNT(*)', 'count')
10871112
.where("enrollment.enrolledAt >= NOW() - INTERVAL '30 days'")
1113+
.andWhere('course.isPublished = :isPublished', { isPublished: true })
1114+
.andWhere('course.status = :status', { status: CourseStatus.PUBLISHED })
10881115
.groupBy("TO_CHAR(enrollment.enrolledAt, 'YYYY-MM-DD')")
10891116
.orderBy('date', 'ASC')
10901117
.getRawMany();
@@ -1102,6 +1129,8 @@ export class CoursesService {
11021129
.select("TO_CHAR(enrollment.enrolledAt, 'YYYY-MM-DD')", 'date')
11031130
.addSelect('SUM(course.price)', 'count') // Reusing 'count' field for value to match interface
11041131
.where("enrollment.enrolledAt >= NOW() - INTERVAL '30 days'")
1132+
.andWhere('course.isPublished = :isPublished', { isPublished: true })
1133+
.andWhere('course.status = :status', { status: CourseStatus.PUBLISHED })
11051134
.groupBy("TO_CHAR(enrollment.enrolledAt, 'YYYY-MM-DD')")
11061135
.orderBy('date', 'ASC')
11071136
.getRawMany();
@@ -1131,6 +1160,8 @@ export class CoursesService {
11311160
await this.enrollmentRepository
11321161
.createQueryBuilder('enrollment')
11331162
.leftJoin('enrollment.course', 'course')
1163+
.where('course.isPublished = :isPublished', { isPublished: true })
1164+
.andWhere('course.status = :status', { status: CourseStatus.PUBLISHED })
11341165
.select('SUM(course.price)', 'total')
11351166
.getRawOne();
11361167

@@ -1144,6 +1175,8 @@ export class CoursesService {
11441175
.createQueryBuilder('course')
11451176
.select('course.level', 'name')
11461177
.addSelect('COUNT(*)', 'value')
1178+
.where('course.isPublished = :isPublished', { isPublished: true })
1179+
.andWhere('course.status = :status', { status: CourseStatus.PUBLISHED })
11471180
.groupBy('course.level')
11481181
.getRawMany();
11491182

apps/api/src/dashboard/dashboard.module.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ import { TypeOrmModule } from '@nestjs/typeorm';
33

44
import { Course } from '../courses/entities/course.entity';
55
import { Enrollment } from '../courses/entities/enrollment.entity';
6-
import { Lesson } from '../courses/entities/lesson.entity';
6+
import { QuizSubmission } from '../quizzes/entities/quiz-submission.entity';
77
import { User } from '../users/entities/user.entity';
88

99
import { DashboardController } from './dashboard.controller';
1010
import { DashboardService } from './dashboard.service';
1111

1212
@Module({
13-
imports: [TypeOrmModule.forFeature([User, Course, Enrollment, Lesson])],
13+
imports: [
14+
TypeOrmModule.forFeature([User, Course, Enrollment, QuizSubmission]),
15+
],
1416
controllers: [DashboardController],
1517
providers: [DashboardService],
1618
})

apps/api/src/dashboard/dashboard.service.spec.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
33

44
import { Course } from '../courses/entities/course.entity';
55
import { Enrollment } from '../courses/entities/enrollment.entity';
6+
import { QuizSubmission } from '../quizzes/entities/quiz-submission.entity';
67
import { User } from '../users/entities/user.entity';
78
import { UserRole } from '../users/enums/user-role.enum';
89

@@ -13,6 +14,7 @@ describe('DashboardService', () => {
1314
let mockUserRepository: Record<string, jest.Mock>;
1415
let mockCourseRepository: Record<string, jest.Mock>;
1516
let mockEnrollmentRepository: Record<string, jest.Mock>;
17+
let mockQuizSubmissionRepository: Record<string, jest.Mock>;
1618

1719
const mockStudent: User = {
1820
id: 'student-1',
@@ -42,25 +44,43 @@ describe('DashboardService', () => {
4244
mockUserRepository = {
4345
count: jest.fn(),
4446
find: jest.fn(),
47+
createQueryBuilder: jest.fn(() => ({
48+
where: jest.fn().mockReturnThis(),
49+
getCount: jest.fn().mockResolvedValue(10),
50+
})),
4551
};
4652

4753
mockCourseRepository = {
4854
count: jest.fn(),
4955
find: jest.fn(),
56+
createQueryBuilder: jest.fn(() => ({
57+
where: jest.fn().mockReturnThis(),
58+
andWhere: jest.fn().mockReturnThis(),
59+
getCount: jest.fn().mockResolvedValue(5),
60+
})),
5061
};
5162

5263
mockEnrollmentRepository = {
5364
count: jest.fn(),
5465
find: jest.fn(),
5566
createQueryBuilder: jest.fn(() => ({
5667
leftJoin: jest.fn().mockReturnThis(),
57-
where: jest.fn().mockReturnThis(),
5868
select: jest.fn().mockReturnThis(),
69+
where: jest.fn().mockReturnThis(),
70+
andWhere: jest.fn().mockReturnThis(),
5971
getCount: jest.fn().mockResolvedValue(5),
6072
getRawOne: jest.fn().mockResolvedValue({ total: '10' }),
6173
})),
6274
};
6375

76+
mockQuizSubmissionRepository = {
77+
createQueryBuilder: jest.fn(() => ({
78+
select: jest.fn().mockReturnThis(),
79+
where: jest.fn().mockReturnThis(),
80+
getRawOne: jest.fn().mockResolvedValue({ avgScore: '0' }),
81+
})),
82+
};
83+
6484
const module: TestingModule = await Test.createTestingModule({
6585
providers: [
6686
DashboardService,
@@ -76,6 +96,10 @@ describe('DashboardService', () => {
7696
provide: getRepositoryToken(Enrollment),
7797
useValue: mockEnrollmentRepository,
7898
},
99+
{
100+
provide: getRepositoryToken(QuizSubmission),
101+
useValue: mockQuizSubmissionRepository,
102+
},
79103
],
80104
}).compile();
81105

@@ -98,8 +122,10 @@ describe('DashboardService', () => {
98122
expect(result).toEqual({
99123
totalUsers: 100,
100124
totalCourses: 25,
101-
totalEnrollments: 500,
125+
totalEnrollments: 5, // queryBuilder mock returns 5
102126
activeStudents: 5,
127+
newUsersCount: 10,
128+
newCoursesCount: 5,
103129
});
104130
});
105131
});
@@ -113,7 +139,8 @@ describe('DashboardService', () => {
113139
expect(result).toEqual({
114140
coursesCreated: 5,
115141
totalStudents: 10,
116-
averageRating: 0,
142+
activeStudents: 10,
143+
newStudentsCount: 10,
117144
});
118145
expect(mockCourseRepository.count).toHaveBeenCalledWith({
119146
where: { instructor: { id: 'instructor-1' } },

0 commit comments

Comments
 (0)