diff --git a/apps/backend/src/modules/catalog/controller.ts b/apps/backend/src/modules/catalog/controller.ts index 31ebd42c0..be819c3f4 100644 --- a/apps/backend/src/modules/catalog/controller.ts +++ b/apps/backend/src/modules/catalog/controller.ts @@ -100,7 +100,9 @@ export const getCatalog = async ( catalog[id] = { ...formatCourse(c), classes: [], - gradeAverage: 0, // getAverage(gradesMap[key]), + gradeDistribution: { + average: null, + }, }; } @@ -108,10 +110,11 @@ export const getCatalog = async ( 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; } @@ -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; } diff --git a/apps/backend/src/modules/catalog/resolver.ts b/apps/backend/src/modules/catalog/resolver.ts index 78bdd3a3d..4dcec99d7 100644 --- a/apps/backend/src/modules/catalog/resolver.ts +++ b/apps/backend/src/modules/catalog/resolver.ts @@ -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); }, }, }; diff --git a/apps/backend/src/modules/catalog/typedefs/catalog.ts b/apps/backend/src/modules/catalog/typedefs/catalog.ts index c2a0ab4a5..68789cd86 100644 --- a/apps/backend/src/modules/catalog/typedefs/catalog.ts +++ b/apps/backend/src/modules/catalog/typedefs/catalog.ts @@ -2,6 +2,6 @@ import { gql } from "graphql-tag"; export default gql` type Query { - catalog(term: TermInput!): [Course!]! + catalog(year: Int!, semester: Semester!): [Course!]! } `; diff --git a/apps/backend/src/modules/course/controller.ts b/apps/backend/src/modules/course/controller.ts index edef5302a..470261a25 100644 --- a/apps/backend/src/modules/course/controller.ts +++ b/apps/backend/src/modules/course/controller.ts @@ -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({ @@ -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 & { + gradeDistribution: CourseModule.Course["gradeDistribution"]; + })[]; }; diff --git a/apps/frontend/schema.graphql b/apps/frontend/schema.graphql index 4875ca3f4..14b339e08 100644 --- a/apps/frontend/schema.graphql +++ b/apps/frontend/schema.graphql @@ -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 @@ -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! } @@ -242,7 +245,7 @@ type Course { requirements: String description: String! fromDate: String! - gradeAverage: Float + gradeDistribution: GradeDistribution! gradingBasis: CourseGradingBasis! finalExam: CourseFinalExam academicCareer: AcademicCareer! @@ -434,6 +437,7 @@ type Class { primarySection: Section! sections: [Section!]! term: Term! + gradeDistribution: GradeDistribution! """ Attributes diff --git a/apps/frontend/src/app/Plan/Term/Course/index.tsx b/apps/frontend/src/app/Plan/Term/Course/index.tsx index 973f8cb21..d73dd0f91 100644 --- a/apps/frontend/src/app/Plan/Term/Course/index.tsx +++ b/apps/frontend/src/app/Plan/Term/Course/index.tsx @@ -11,7 +11,7 @@ export default function Course({ subject, number, title, - gradeAverage, + gradeDistribution, }: ICourse) { return (
@@ -22,7 +22,7 @@ export default function Course({

{title}

- +
Major diff --git a/apps/frontend/src/app/Schedule/Editor/SideBar/Class/index.tsx b/apps/frontend/src/app/Schedule/Editor/SideBar/Class/index.tsx index 00c27a737..e9257985e 100644 --- a/apps/frontend/src/app/Schedule/Editor/SideBar/Class/index.tsx +++ b/apps/frontend/src/app/Schedule/Editor/SideBar/Class/index.tsx @@ -72,7 +72,9 @@ export default function Class({ {_class.title ?? _class.course.title}

- + - !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 ( @@ -70,12 +74,12 @@ export default function AverageGrade({ gradeAverage }: AverageGradeProps) {

Average grade

- {gradeAverage ? ( + {average ? (

Students have received{" "} {["A", "F"].includes(text[0]) ? "an " : "a "} - {text} ({gradeAverage.toLocaleString()}) + {text} ({average.toLocaleString()}) {" "} in this course on average across all semesters this course has been offered. diff --git a/apps/frontend/src/components/Class/index.tsx b/apps/frontend/src/components/Class/index.tsx index b59f1555a..ad35e7ebb 100644 --- a/apps/frontend/src/components/Class/index.tsx +++ b/apps/frontend/src/components/Class/index.tsx @@ -296,7 +296,7 @@ export default function Class({ {_class.title || _class.course.title}

- + ( title: courseTitle, subject, number: courseNumber, - gradeAverage, + gradeDistribution, }, title, number, @@ -46,7 +46,7 @@ const Course = forwardRef(

{title ?? courseTitle}

- + (GET_CLASSES, { variables: { - term: { - semester: currentSemester, - year: currentYear, - }, + semester: currentSemester, + year: currentYear, }, }); @@ -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 @@ -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) { diff --git a/apps/frontend/src/components/ClassBrowser/worker.ts b/apps/frontend/src/components/ClassBrowser/worker.ts index 24eef1678..b9e0f7c28 100644 --- a/apps/frontend/src/components/ClassBrowser/worker.ts +++ b/apps/frontend/src/components/ClassBrowser/worker.ts @@ -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) { diff --git a/apps/frontend/src/components/Course/index.tsx b/apps/frontend/src/components/Course/index.tsx index b79fd07c9..aa5e61621 100644 --- a/apps/frontend/src/components/Course/index.tsx +++ b/apps/frontend/src/components/Course/index.tsx @@ -300,7 +300,7 @@ export default function Course({

{course.title}

- +
{dialog ? ( diff --git a/apps/frontend/src/components/CourseBrowser/List/Course/index.tsx b/apps/frontend/src/components/CourseBrowser/List/Course/index.tsx index 2b437fb4b..f358a0fb3 100644 --- a/apps/frontend/src/components/CourseBrowser/List/Course/index.tsx +++ b/apps/frontend/src/components/CourseBrowser/List/Course/index.tsx @@ -13,7 +13,7 @@ interface CourseProps { } const Course = forwardRef( - ({ title, subject, number, gradeAverage, index, onClick }, ref) => { + ({ title, subject, number, gradeDistribution, index, onClick }, ref) => { return (
(

{title}

- +
diff --git a/apps/frontend/src/components/CourseBrowser/index.tsx b/apps/frontend/src/components/CourseBrowser/index.tsx index 25ae2dd1b..a8b11480b 100644 --- a/apps/frontend/src/components/CourseBrowser/index.tsx +++ b/apps/frontend/src/components/CourseBrowser/index.tsx @@ -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 diff --git a/apps/frontend/src/lib/api/classes.ts b/apps/frontend/src/lib/api/classes.ts index 8d84070ac..38f036e59 100644 --- a/apps/frontend/src/lib/api/classes.ts +++ b/apps/frontend/src/lib/api/classes.ts @@ -238,7 +238,9 @@ export const READ_CLASS = gql` course { title description - gradeAverage + gradeDistribution { + average + } academicCareer requirements } diff --git a/apps/frontend/src/lib/api/courses.ts b/apps/frontend/src/lib/api/courses.ts index 434680385..d9c81067c 100644 --- a/apps/frontend/src/lib/api/courses.ts +++ b/apps/frontend/src/lib/api/courses.ts @@ -3,6 +3,16 @@ import { gql } from "@apollo/client"; import { AcademicCareer, IClass, InstructionMethod } from "."; import { Semester } from "./terms"; +export interface Grade { + letter: string; + count: number; +} + +export interface GradeDistribution { + average: number | null; + distribution: Grade[]; +} + export interface ICourse { // Identifiers subject: string; @@ -12,13 +22,13 @@ export interface ICourse { classes: IClass[]; crossListing: ICourse[]; requiredCourses: ICourse[]; + gradeDistribution: GradeDistribution; // Attributes requirements: string | null; primaryInstructionMethod: InstructionMethod; description: string; fromDate: string; - gradeAverage: number | null; gradingBasis: string; finalExam: string | null; academicCareer: AcademicCareer; @@ -39,7 +49,13 @@ export const READ_COURSE = gql` title description academicCareer - gradeAverage + gradeDistribution { + average + distribution { + letter + count + } + } gradingBasis finalExam requirements @@ -66,7 +82,13 @@ export const GET_COURSES = gql` subject number title - gradeAverage + gradeDistribution { + average + distribution { + letter + count + } + } academicCareer finalExam gradingBasis @@ -81,12 +103,14 @@ export interface GetClassesResponse { } export const GET_CLASSES = gql` - query GetClasses($term: TermInput!) { - catalog(term: $term) { + query GetClasses($year: Int!, $semester: Semester!) { + catalog(year: $year, semester: $semester) { subject number title - gradeAverage + gradeDistribution { + average + } academicCareer classes { subject