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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions