Summary
Implement an engagement analytics endpoint and a Piazza-style stats page for instructors, surfacing who is asking and answering questions (including TAs) and how engagement trends across the weeks of a course.
[Estimated hours: 7-9]
Objectives
Description
Instructors want a Piazza-like view of class engagement: mainly who has been asking and answering questions (with TA contributions called out), plus how engagement rises and falls over the ~12 weeks of a course. This complements the existing admin stats (/api/admin/stats) but is scoped to a single course and available to that course's instructors (not just site admins).
User Flow
Professor opens a course → "Stats" / "Analytics" tab
→ GET /api/courses/:courseId/analytics
→ Returns: summary totals, per-participant breakdown, weekly time series
→ Page renders:
- Top askers / top answerers (TA contributions highlighted)
- Weekly engagement line/bar chart over the course timeline
- Role split (student vs TA vs professor activity)
Technical Details
File Structure
src/app/api/courses/[courseId]/analytics/
└── route.ts # GET analytics handler (instructor-only)
src/lib/
├── courseAnalytics.ts # Aggregation logic
│ Functions:
│ - getCourseAnalytics(courseId, userId)
│ - getParticipantBreakdown(courseId) # questions/answers/upvotes per user+role
│ - getWeeklyEngagement(courseId) # bucket Q&A by ISO week
│ - canViewCourseAnalytics(userId, courseId)
└── analyticsValidation.ts # Validation for analytics queries
Functions:
- validateAnalyticsRange(from, to)
src/app/courses/[courseId]/analytics/ # (or extend existing course view)
├── page.tsx # Stats page
└── components/
├── ParticipantTable.tsx # Leaderboard: askers / answerers, role-tagged
└── EngagementChart.tsx # Weekly trend chart
Access & Auth
- Reuse the standard non-admin pattern:
getCurrentUser() → 401 if missing.
- Authorize via
CourseEnrollment.role for (userId, courseId): only PROFESSOR and TA may view (mirror how instructor controls are gated in the room view). Return 403 otherwise.
- Do NOT require site-admin whitelist — this is course-scoped, distinct from
src/lib/adminAuth.ts.
Aggregation Source Fields (Prisma)
Question: authorId, sessionId, createdAt, upvoteCount, status
Answer: authorId, questionId, createdAt, upvoteCount, isAccepted
Session: id, courseId, startTime, createdAt # join Question -> Session -> courseId
CourseEnrollment: userId, courseId, role # role tagging for each participant
Questions link to a course via Session.courseId (no direct Question.courseId), so aggregation must join Question/Answer → Session → Course.
API Response Example
GET /api/courses/[courseId]/analytics
Query parameters:
from (optional ISO date) / to (optional ISO date) — default: full course range
weekStart (optional: course start date used to bucket weeks; default: earliest session startTime/createdAt)
Response:
{
course: { id: string, code: string, name: string },
summary: {
totalQuestions: number,
totalAnswers: number,
activeParticipants: number,
answeredRate: number // answered+resolved / total questions
},
participants: [{
userId: string,
name: string,
role: "STUDENT" | "TA" | "PROFESSOR",
questionsAsked: number,
answersGiven: number,
upvotesReceived: number
}],
weekly: [{
week: number, // 1-based week index from weekStart
weekStart: string, // ISO date of that week's Monday
questions: number,
answers: number,
activeUsers: number
}]
}
Acceptance Criteria
Notes
- Models are sufficient as-is; no schema changes required. Consider adding indexes if needed:
Question @@index([sessionId, createdAt]), Answer @@index([authorId, createdAt]).
- Scope is per-course instructor analytics. A cross-course or per-session drilldown can be a follow-up ticket.
- "12 weeks" is illustrative — derive the week count from the actual course/session date range rather than hardcoding.
- The existing admin dashboard (
src/app/dashboard, /api/admin/stats) is global and admin-only; this is intentionally separate and course-scoped for instructors.
Summary
Implement an engagement analytics endpoint and a Piazza-style stats page for instructors, surfacing who is asking and answering questions (including TAs) and how engagement trends across the weeks of a course.
[Estimated hours: 7-9]
Objectives
/api/courses/[courseId]/analyticsendpoint (instructor-only)Description
Instructors want a Piazza-like view of class engagement: mainly who has been asking and answering questions (with TA contributions called out), plus how engagement rises and falls over the ~12 weeks of a course. This complements the existing admin stats (
/api/admin/stats) but is scoped to a single course and available to that course's instructors (not just site admins).User Flow
Technical Details
File Structure
Access & Auth
getCurrentUser()→ 401 if missing.CourseEnrollment.rolefor(userId, courseId): onlyPROFESSORandTAmay view (mirror how instructor controls are gated in the room view). Return 403 otherwise.src/lib/adminAuth.ts.Aggregation Source Fields (Prisma)
Questions link to a course via
Session.courseId(no directQuestion.courseId), so aggregation must join Question/Answer → Session → Course.API Response Example
GET
/api/courses/[courseId]/analyticsQuery parameters:
from(optional ISO date) /to(optional ISO date) — default: full course rangeweekStart(optional: course start date used to bucket weeks; default: earliest sessionstartTime/createdAt)Response:
Acceptance Criteria
CourseEnrollment.role(TA contributions distinguishable)from/todate filtering works and validates ordering (from <= to)Notes
Question @@index([sessionId, createdAt]),Answer @@index([authorId, createdAt]).src/app/dashboard,/api/admin/stats) is global and admin-only; this is intentionally separate and course-scoped for instructors.