diff --git a/public/database/grade_distributions.db b/public/database/grade_distributions.db index 8378e1a2..410475de 100644 Binary files a/public/database/grade_distributions.db and b/public/database/grade_distributions.db differ diff --git a/src/shared/types/Distribution.ts b/src/shared/types/Distribution.ts index f9e87022..9d56d723 100644 --- a/src/shared/types/Distribution.ts +++ b/src/shared/types/Distribution.ts @@ -22,6 +22,8 @@ export type CourseSQLRow = { Course_Number: string; Course_Title: string; Course_Full_Title: string; + Instructor_First: string | null; + Instructor_Last: string | null; A: number; A_Minus: number; B_Plus: number; diff --git a/src/views/components/injected/CourseCatalogInjectedPopup/GradeDistribution.tsx b/src/views/components/injected/CourseCatalogInjectedPopup/GradeDistribution.tsx index b1cb8d4b..aa912196 100644 --- a/src/views/components/injected/CourseCatalogInjectedPopup/GradeDistribution.tsx +++ b/src/views/components/injected/CourseCatalogInjectedPopup/GradeDistribution.tsx @@ -52,13 +52,14 @@ const GRADE_COLORS = { */ export default function GradeDistribution({ course }: GradeDistributionProps): JSX.Element { const [semester, setSemester] = useState('Aggregate'); - const [distributions, setDistributions] = useState>({}); + type Distributions = Record; + const [distributions, setDistributions] = useState({}); const [status, setStatus] = useState(DataStatus.LOADING); const ref = useRef(null); const chartData = useMemo(() => { if (status === DataStatus.FOUND && distributions[semester]) { - return Object.entries(distributions[semester]!).map(([grade, count]) => ({ + return Object.entries(distributions[semester]!.data).map(([grade, count]) => ({ y: count, color: GRADE_COLORS[grade as LetterGrade], })); @@ -69,8 +70,11 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J useEffect(() => { const fetchInitialData = async () => { try { - const [aggregateDist, semesters] = await queryAggregateDistribution(course); - const initialDistributions: Record = { Aggregate: aggregateDist }; + const [aggregateDist, semesters, instructorIncludedAggregate] = + await queryAggregateDistribution(course); + const initialDistributions: Distributions = { + Aggregate: { data: aggregateDist, instructorIncluded: instructorIncludedAggregate }, + }; const semesterPromises = semesters.map(semester => querySemesterDistribution(course, semester)); const semesterDistributions = await Promise.allSettled(semesterPromises); semesters.forEach((semester, i) => { @@ -81,7 +85,11 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J } if (distributionResult.status === 'fulfilled') { - initialDistributions[`${semester.season} ${semester.year}`] = distributionResult.value; + const [distribution, instructorIncluded] = distributionResult.value; + initialDistributions[`${semester.season} ${semester.year}`] = { + data: distribution, + instructorIncluded, + }; } }); setDistributions(initialDistributions); @@ -236,6 +244,14 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J ))} + {distributions[semester] && !distributions[semester]!.instructorIncluded && ( +
+ + Instructor-specific data is not available for this course + {semester !== 'Aggregate' && ` for ${semester}`}, showing course-wide data instead + +
+ )} )} diff --git a/src/views/lib/database/queryDistribution.ts b/src/views/lib/database/queryDistribution.ts index 2ce8f14a..6e0cdcb0 100644 --- a/src/views/lib/database/queryDistribution.ts +++ b/src/views/lib/database/queryDistribution.ts @@ -3,27 +3,26 @@ import type { CourseSQLRow, Distribution } from '@shared/types/Distribution'; import { initializeDB } from './initializeDB'; -// TODO: in the future let's maybe refactor this to be reactive to the items in the db rather than being explicit -const allTables = [ - 'grade_distributions_2019_2020', - 'grade_distributions_2020_2021', - 'grade_distributions_2021_2022', - 'grade_distributions_2022_2023', -] as const; - /** * fetches the aggregate distribution of grades for a given course from the course db, and the semesters that we have data for * @param course the course to fetch the distribution for * @returns a Distribution object containing the distribution of grades for the course, and * an array of semesters that we have the distribution for */ -export async function queryAggregateDistribution(course: Course): Promise<[Distribution, Semester[]]> { +export async function queryAggregateDistribution(course: Course): Promise<[Distribution, Semester[], boolean]> { const db = await initializeDB(); - const query = generateQuery(course, null); + const query = generateQuery(course, null, true); - const res = db.exec(query)?.[0]; + let res = db.exec(query)?.[0]; + let instructorIncluded = true; if (!res?.columns?.length) { - throw new NoDataError(course); + instructorIncluded = false; + const queryWithoutInstructor = generateQuery(course, null, false); + res = db.exec(queryWithoutInstructor)?.[0]; + + if (!res?.columns?.length) { + throw new NoDataError(course); + } } const row: Required = {} as Required; @@ -79,7 +78,7 @@ export async function queryAggregateDistribution(course: Course): Promise<[Distr semesters.push({ year: parseInt(year, 10), season: season as Semester['season'] }); }); - return [distribution, semesters]; + return [distribution, semesters, instructorIncluded]; } /** @@ -88,18 +87,19 @@ export async function queryAggregateDistribution(course: Course): Promise<[Distr * @param semester the semester to fetch the distribution for OR null if we want the aggregate distribution * @returns a SQL query string */ -function generateQuery(course: Course, semester: Semester | null): string { - // const profName = course.instructors[0]?.fullName; - // eslint-disable-next-line no-nested-ternary - const yearDelta = semester ? (semester.season === 'Fall' ? 0 : -1) : 0; +function generateQuery(course: Course, semester: Semester | null, includeInstructor: boolean): string { + const profName = course.instructors[0]?.lastName; const query = ` - select * from ${semester ? `grade_distributions_${semester.year + yearDelta}_${semester.year + yearDelta + 1}` : `(select * from ${allTables.join(' union all select * from ')})`} + select * from grade_distributions where Department_Code = '${course.department}' and Course_Number = '${course.number}' + ${includeInstructor ? `and Instructor_Last = '${profName}' collate nocase` : ''} ${semester ? `and Semester = '${semester.season} ${semester.year}'` : ''} `; + console.log(includeInstructor, { query }); + return query; } @@ -109,13 +109,19 @@ function generateQuery(course: Course, semester: Semester | null): string { * @param semester the semester to fetch the distribution for * @returns a Distribution object containing the distribution of grades for the course */ -export async function querySemesterDistribution(course: Course, semester: Semester): Promise { +export async function querySemesterDistribution(course: Course, semester: Semester): Promise<[Distribution, boolean]> { const db = await initializeDB(); - const query = generateQuery(course, semester); + const query = generateQuery(course, semester, true); - const res = db.exec(query)?.[0]; + let res = db.exec(query)?.[0]; + let instructorIncluded = true; if (!res?.columns?.length) { - throw new NoDataError(course); + instructorIncluded = false; + const queryWithoutInstructor = generateQuery(course, semester, false); + res = db.exec(queryWithoutInstructor)?.[0]; + if (!res?.columns?.length) { + throw new NoDataError(course); + } } const row: Required = {} as Required; @@ -142,21 +148,24 @@ export async function querySemesterDistribution(course: Course, semester: Semest } } - return { - A: row.A, - 'A-': row.A_Minus, - 'B+': row.B_Plus, - B: row.B, - 'B-': row.B_Minus, - 'C+': row.C_Plus, - C: row.C, - 'C-': row.C_Minus, - 'D+': row.D_Plus, - D: row.D, - 'D-': row.D_Minus, - F: row.F, - Other: row.Other, - }; + return [ + { + A: row.A, + 'A-': row.A_Minus, + 'B+': row.B_Plus, + B: row.B, + 'B-': row.B_Minus, + 'C+': row.C_Plus, + C: row.C, + 'C-': row.C_Minus, + 'D+': row.D_Plus, + D: row.D, + 'D-': row.D_Minus, + F: row.F, + Other: row.Other, + }, + instructorIncluded, + ]; } /**