Skip to content

BE_20 - Engagement Analytics & Stats Page #75

Description

@notjackl3

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

  • Create GET /api/courses/[courseId]/analytics endpoint (instructor-only)
  • Aggregate per-participant contribution stats (questions asked, answers given, upvotes received) broken down by role (STUDENT / TA / PROFESSOR)
  • Aggregate weekly engagement time series (questions + answers per week) across the course
  • Build an instructor-facing stats page that visualizes participant leaderboards and the weekly trend
  • Respect anonymity and access rules (only instructors can view; aggregate counts 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

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

  • Only PROFESSOR and TA enrolled in the course can access analytics (403 for students/non-members)
  • Returns 401 if user not authenticated
  • Returns 404 if courseId doesn't exist
  • Summary totals (questions, answers, active participants, answered rate) computed correctly for the course
  • Participant breakdown includes questionsAsked, answersGiven, upvotesReceived per user
  • Each participant row is tagged with their CourseEnrollment.role (TA contributions distinguishable)
  • Weekly time series buckets questions and answers by week across the course timeline
  • Week buckets are stable/contiguous (empty weeks reported as 0, not skipped)
  • Analytics aggregate across all of the course's sessions (Question/Answer joined via Session.courseId)
  • Anonymous questions/answers are still counted in totals and attributed to their real author for instructor stats, but the endpoint exposes only aggregate counts (no per-post content)
  • Optional from/to date filtering works and validates ordering (from <= to)
  • Stats page renders the participant leaderboard (top askers / top answerers) with role tags
  • Stats page renders the weekly engagement chart over the course duration
  • Follow existing validation pattern (ValidationResult interface)
  • Error messages follow existing API error format
  • Aggregation uses Prisma groupBy/count (avoid loading all rows into memory)
  • Response time < 800ms for a typical course (1 semester of activity)

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.

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions