Skip to content

Commit

Permalink
feat: Grade distributions for the client
Browse files Browse the repository at this point in the history
  • Loading branch information
mathhulk committed Oct 24, 2024
1 parent d294f0c commit cecc0b6
Show file tree
Hide file tree
Showing 17 changed files with 122 additions and 76 deletions.
17 changes: 10 additions & 7 deletions apps/backend/src/modules/catalog/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,21 @@ export const getCatalog = async (
catalog[id] = {
...formatCourse(c),
classes: [],
gradeAverage: 0, // getAverage(gradesMap[key]),
gradeDistribution: {
average: null,
},
};
}

for (const c of classes) {
const id = getCsCourseId(c.course as CourseType);

if (!(id in catalog)) {
// throw new Error(`Class ${c.course?.subjectArea?.code} ${c.course?.catalogNumber?.formatted}`
// + ` has a course id ${id} that doesn't exist for the ${term.semester} ${term.year} term.`)
console.warn(
`Class ${c.course?.subjectArea?.code} ${c.course?.catalogNumber?.formatted}` +
` has a course id ${id} that doesn't exist for the ${semester} ${year} term.`
);

// TODO(production): log
continue;
}

Expand All @@ -124,10 +127,10 @@ export const getCatalog = async (
const id = getCsCourseId(s.class.course as CourseType);

if (!(id in catalog)) {
// throw new Error(`Section ${s.class.course?.subjectArea?.code} ${s.class.course?.catalogNumber?.formatted}`
// + ` has a course id ${id} that doesn't exist for the ${term.semester} ${term.year} term.`)
console.warn(
`Section ${s.class.course?.subjectArea?.code} ${s.class.course?.catalogNumber?.formatted} has a course id ${id} that doesn't exist for the ${semester} ${year} term.`
);

// TODO(production): log
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions apps/backend/src/modules/catalog/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { CatalogModule } from "./generated-types/module-types";

const resolvers: CatalogModule.Resolvers = {
Query: {
catalog: async (_, { term }, __, info) => {
catalog: async (_, { year, semester }, __, info) => {
// const cacheControl = cacheControlFromInfo(info);
// cacheControl.setCacheHint({ maxAge: 300 });

return await getCatalog(term.year, term.semester, info);
return await getCatalog(year, semester, info);
},
},
};
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/modules/catalog/typedefs/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { gql } from "graphql-tag";

export default gql`
type Query {
catalog(term: TermInput!): [Course!]!
catalog(year: Int!, semester: Semester!): [Course!]!
}
`;
9 changes: 7 additions & 2 deletions apps/backend/src/modules/course/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ClassModel, CourseModel } from "@repo/common";

import { formatClass } from "../class/formatter";
import { IntermediateCourse, formatCourse } from "./formatter";
import { CourseModule } from "./generated-types/module-types";

export const getCourse = async (subject: string, number: string) => {
const course = await CourseModel.findOne({
Expand Down Expand Up @@ -125,6 +126,10 @@ export const getCourses = async () => {

return courses.map((c) => ({
...formatCourse(c),
gradeAverage: 0, //getAverage(gradesMap[getCourseKey(c)]),
})) as IntermediateCourse[];
gradeDistribution: {
average: null,
},
})) as (Exclude<IntermediateCourse, "gradeDistribution"> & {
gradeDistribution: CourseModule.Course["gradeDistribution"];
})[];
};
22 changes: 13 additions & 9 deletions apps/frontend/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ type Query {
user: User
grade(
subject: String!
courseNum: String!
classNum: String
term: TermInput
): Grade
catalog(term: TermInput!): [Course!]!
courseNumber: String!
classNumber: String
year: Int
semester: Semester
givenName: String
familyName: String
): GradeDistribution!
catalog(year: Int!, semester: Semester!): [Course!]!
ping: String! @deprecated(reason: "test")
schedules: [Schedule]
schedule(id: ID!): Schedule
Expand Down Expand Up @@ -91,12 +94,12 @@ type Mutation {
updateSchedule(id: ID!, schedule: UpdateScheduleInput!): Schedule
}

type Grade {
type GradeDistribution {
average: Float
distribution: [GradeDistributionItem]
distribution: [Grade!]!
}

type GradeDistributionItem {
type Grade {
letter: String!
count: Int!
}
Expand Down Expand Up @@ -242,7 +245,7 @@ type Course {
requirements: String
description: String!
fromDate: String!
gradeAverage: Float
gradeDistribution: GradeDistribution!
gradingBasis: CourseGradingBasis!
finalExam: CourseFinalExam
academicCareer: AcademicCareer!
Expand Down Expand Up @@ -434,6 +437,7 @@ type Class {
primarySection: Section!
sections: [Section!]!
term: Term!
gradeDistribution: GradeDistribution!

"""
Attributes
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/src/app/Plan/Term/Course/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function Course({
subject,
number,
title,
gradeAverage,
gradeDistribution,
}: ICourse) {
return (
<div className={classNames(styles.root, "draggable")}>
Expand All @@ -22,7 +22,7 @@ export default function Course({
</p>
<p className={styles.description}>{title}</p>
<div className={styles.row}>
<AverageGrade gradeAverage={gradeAverage} />
<AverageGrade gradeDistribution={gradeDistribution} />
<div className={styles.badge}>
<Book />
Major
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ export default function Class({
{_class.title ?? _class.course.title}
</p>
<div className={styles.row}>
<AverageGrade gradeAverage={_class.course.gradeAverage} />
<AverageGrade
gradeDistribution={_class.course.gradeDistribution}
/>
<Capacity
enrollCount={_class.primarySection.enrollCount}
enrollMax={_class.primarySection.enrollMax}
Expand Down
48 changes: 26 additions & 22 deletions apps/frontend/src/components/AverageGrade/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,59 @@ import { useMemo } from "react";

import * as Tooltip from "@radix-ui/react-tooltip";

import { GradeDistribution } from "@/lib/api";

import styles from "./AverageGrade.module.scss";

interface AverageGradeProps {
gradeAverage: number | null;
gradeDistribution: GradeDistribution;
}

export default function AverageGrade({ gradeAverage }: AverageGradeProps) {
export default function AverageGrade({
gradeDistribution: { average },
}: AverageGradeProps) {
const text = useMemo(
() =>
!gradeAverage
!average
? "N/A"
: gradeAverage > 4
: average > 4
? "A+"
: gradeAverage > 3.7
: average > 3.7
? "A"
: gradeAverage > 3.5
: average > 3.5
? "A-"
: gradeAverage > 3
: average > 3
? "B+"
: gradeAverage > 2.7
: average > 2.7
? "B"
: gradeAverage > 2.5
: average > 2.5
? "B-"
: gradeAverage > 2
: average > 2
? "C+"
: gradeAverage > 1.7
: average > 1.7
? "C"
: gradeAverage > 1.5
: average > 1.5
? "C-"
: gradeAverage > 1
: average > 1
? "D+"
: gradeAverage > 0.7
: average > 0.7
? "D"
: gradeAverage
: average
? "D-"
: "F",
[gradeAverage]
[average]
);

const color = useMemo(
() =>
!gradeAverage
!average
? "var(--paragraph-color)"
: gradeAverage > 3.5
: average > 3.5
? "var(--emerald-500)"
: gradeAverage > 2.5
: average > 2.5
? "var(--amber-500)"
: "var(--rose-500)",
[gradeAverage]
[average]
);

return (
Expand All @@ -70,12 +74,12 @@ export default function AverageGrade({ gradeAverage }: AverageGradeProps) {
<div className={styles.content}>
<Tooltip.Arrow className={styles.arrow} />
<p className={styles.title}>Average grade</p>
{gradeAverage ? (
{average ? (
<p className={styles.description}>
Students have received{" "}
{["A", "F"].includes(text[0]) ? "an " : "a "}
<span style={{ color }}>
{text} ({gradeAverage.toLocaleString()})
{text} ({average.toLocaleString()})
</span>{" "}
in this course on average across all semesters this course has
been offered.
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/components/Class/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ export default function Class({
{_class.title || _class.course.title}
</p>
<div className={styles.group}>
<AverageGrade gradeAverage={_class.course.gradeAverage} />
<AverageGrade gradeDistribution={_class.course.gradeDistribution} />
<Capacity
enrollCount={_class.primarySection.enrollCount}
enrollMax={_class.primarySection.enrollMax}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const Course = forwardRef<HTMLDivElement, ClassProps & IClass>(
title: courseTitle,
subject,
number: courseNumber,
gradeAverage,
gradeDistribution,
},
title,
number,
Expand All @@ -46,7 +46,7 @@ const Course = forwardRef<HTMLDivElement, ClassProps & IClass>(
</p>
<p className={styles.description}>{title ?? courseTitle}</p>
<div className={styles.row}>
<AverageGrade gradeAverage={gradeAverage} />
<AverageGrade gradeDistribution={gradeDistribution} />
<Capacity
enrollCount={enrollCount}
enrollMax={enrollMax}
Expand Down
18 changes: 9 additions & 9 deletions apps/frontend/src/components/ClassBrowser/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,8 @@ export default function ClassBrowser({

const { data, loading } = useQuery<GetClassesResponse>(GET_CLASSES, {
variables: {
term: {
semester: currentSemester,
year: currentYear,
},
semester: currentSemester,
year: currentYear,
},
});

Expand All @@ -73,7 +71,7 @@ export default function ClassBrowser({
subject: course.subject,
number: course.number,
title: course.title,
gradeAverage: course.gradeAverage,
gradeDistribution: course.gradeDistribution,
academicCareer: course.academicCareer,
},
}) as IClass
Expand Down Expand Up @@ -190,13 +188,15 @@ export default function ClassBrowser({
// Clone the courses to avoid sorting in-place
filteredClasses = structuredClone(filteredClasses).sort((a, b) => {
if (sortBy === SortBy.AverageGrade) {
return b.course.gradeAverage === a.course.gradeAverage
return b.course.gradeDistribution.average ===
a.course.gradeDistribution.average
? 0
: b.course.gradeAverage === null
: b.course.gradeDistribution.average === null
? -1
: a.course.gradeAverage === null
: a.course.gradeDistribution.average === null
? 1
: b.course.gradeAverage - a.course.gradeAverage;
: b.course.gradeDistribution.average -
a.course.gradeDistribution.average;
}

if (sortBy === SortBy.Units) {
Expand Down
10 changes: 6 additions & 4 deletions apps/frontend/src/components/ClassBrowser/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,15 @@ addEventListener(
// Clone the courses to avoid sorting in-place
filteredClasses.sort((a, b) => {
if (sortBy === SortBy.AverageGrade) {
return b.course.gradeAverage === a.course.gradeAverage
return b.course.gradeDistribution.average ===
a.course.gradeDistribution.average
? 0
: b.course.gradeAverage === null
: b.course.gradeDistribution.average === null
? -1
: a.course.gradeAverage === null
: a.course.gradeDistribution.average === null
? 1
: b.course.gradeAverage - a.course.gradeAverage;
: b.course.gradeDistribution.average -
a.course.gradeDistribution.average;
}

if (sortBy === SortBy.Units) {
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/components/Course/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export default function Course({
</h1>
<p className={styles.description}>{course.title}</p>
<div className={styles.group}>
<AverageGrade gradeAverage={course.gradeAverage} />
<AverageGrade gradeDistribution={course.gradeDistribution} />
</div>
{dialog ? (
<Tabs.List className={styles.menu} defaultValue="overview">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface CourseProps {
}

const Course = forwardRef<HTMLDivElement, CourseProps & ICourse>(
({ title, subject, number, gradeAverage, index, onClick }, ref) => {
({ title, subject, number, gradeDistribution, index, onClick }, ref) => {
return (
<div
className={styles.root}
Expand All @@ -27,7 +27,7 @@ const Course = forwardRef<HTMLDivElement, CourseProps & ICourse>(
</p>
<p className={styles.description}>{title}</p>
<div className={styles.row}>
<AverageGrade gradeAverage={gradeAverage} />
<AverageGrade gradeDistribution={gradeDistribution} />
</div>
</div>
<div className={styles.column}>
Expand Down
8 changes: 4 additions & 4 deletions apps/frontend/src/components/CourseBrowser/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,13 @@ export default function CourseBrowser({
// Clone the courses to avoid sorting in-place
filteredClasses = structuredClone(filteredClasses).sort((a, b) => {
if (currentSortBy === SortBy.AverageGrade) {
return b.gradeAverage === a.gradeAverage
return b.gradeDistribution.average === a.gradeDistribution.average
? 0
: b.gradeAverage === null
: b.gradeDistribution.average === null
? -1
: a.gradeAverage === null
: a.gradeDistribution.average === null
? 1
: b.gradeAverage - a.gradeAverage;
: b.gradeDistribution.average - a.gradeDistribution.average;
}

// Classes are by default sorted by relevance and number
Expand Down
Loading

0 comments on commit cecc0b6

Please sign in to comment.