Skip to content

Commit

Permalink
feat: add new db powered by UT_Grade_Parser (#163)
Browse files Browse the repository at this point in the history
* feat: add new db powered by UT_Grade_Parser

* Merge branch 'main' of https://github.com/Longhorn-Developers/UT-Registration-Plus into feature/update-db

* feat: update db

* feat: update db handlers and types

Co-authored-by: Samuel Gunter <sgunter@utexas.edu>

* fix: type errors

* fix: add Other to grade dist

* fix: db with proper insertion order

* Merge branch 'main' of https://github.com/Longhorn-Developers/UT-Registration-Plus into feature/update-db

* chore: address PR comments

Co-Authored-By: Samuel Gunter <sgunter@utexas.edu>
  • Loading branch information
doprz and Samathingamajig authored Mar 24, 2024
1 parent ee2b7c4 commit 60d1f48
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 117 deletions.
Binary file added public/database/grade_distributions.db
Binary file not shown.
Binary file removed public/database/grades.db
Binary file not shown.
3 changes: 3 additions & 0 deletions src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ const manifest = defineManifest(async () => ({
matches: ['*://*/*'],
},
],
content_security_policy: {
extension_pages: "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'",
},
}));

export default manifest;
44 changes: 21 additions & 23 deletions src/shared/types/Distribution.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { PointOptionsObject } from 'highcharts';

import { Semester } from './Course';
/**
* Each of the possible letter grades that can be given in a course
*/
export type LetterGrade = 'A' | 'A-' | 'B' | 'B+' | 'B-' | 'C' | 'C+' | 'C-' | 'D' | 'D+' | 'D-' | 'F';
export type LetterGrade = 'A' | 'A-' | 'B' | 'B+' | 'B-' | 'C' | 'C+' | 'C-' | 'D' | 'D+' | 'D-' | 'F' | 'Other';

/**
* A distribution of grades for a course,
Expand All @@ -18,23 +15,24 @@ export type Distribution = {
* This is a object-ified version of a row in the SQL table that is used to store the distribution data.
*/
export type CourseSQLRow = {
sem?: string;
prof?: string;
dept?: string;
course_nbr?: string;
course_name?: string;
a1?: number;
a2?: number;
a3?: number;
b1?: number;
b2?: number;
b3?: number;
c1?: number;
c2?: number;
c3?: number;
d1?: number;
d2?: number;
d3?: number;
f?: number;
semesters?: string;
Semester: string;
Section: number;
Department: string;
Department_Code: string;
Course_Number: string;
Course_Title: string;
Course_Full_Title: string;
A: number;
A_Minus: number;
B_Plus: number;
B: number;
B_Minus: number;
C_Plus: number;
C: number;
C_Minus: number;
D_Plus: number;
D: number;
D_Minus: number;
F: number;
Other: number;
};
1 change: 1 addition & 0 deletions src/shared/types/ThemeColors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const extendedColors = {
d: '#DC2626',
dminus: '#B91C1C',
f: '#B91C1C',
other: '#6B7280',
},
} as const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
} from '@views/lib/database/queryDistribution';
import Highcharts from 'highcharts';
import HighchartsReact from 'highcharts-react-official';
import React from 'react';
import type { ChangeEvent } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';

interface GradeDistributionProps {
course: Course;
Expand All @@ -22,6 +23,7 @@ const DataStatus = {
NOT_FOUND: 'NOT_FOUND',
ERROR: 'ERROR',
} as const satisfies Record<string, string>;

type DataStatusType = (typeof DataStatus)[keyof typeof DataStatus];

const GRADE_COLORS = {
Expand All @@ -37,6 +39,7 @@ const GRADE_COLORS = {
D: extendedColors.gradeDistribution.d,
'D-': extendedColors.gradeDistribution.dminus,
F: extendedColors.gradeDistribution.f,
Other: extendedColors.gradeDistribution.other,
} as const satisfies Record<LetterGrade, string>;

/**
Expand All @@ -48,48 +51,55 @@ const GRADE_COLORS = {
* @returns {JSX.Element} The grade distribution chart component.
*/
export default function GradeDistribution({ course }: GradeDistributionProps): JSX.Element {
const [semester, setSemester] = React.useState('Aggregate');
const [distributions, setDistributions] = React.useState<Record<string, Distribution>>({});
const [status, setStatus] = React.useState<DataStatusType>(DataStatus.LOADING);
const ref = React.useRef<HighchartsReact.RefObject>(null);

// const chartData = React.useMemo(() => {
// if (status === DataStatus.FOUND && distributions[semester]) {
// return Object.entries(distributions[semester]).map(([grade, count]) => ({
// y: count,
// color: GRADE_COLORS[grade as LetterGrade],
// }));
// }
// return Array(12).fill(0);
// }, [distributions, semester, status]);
// const chartData: unknown[] = [];

// React.useEffect(() => {
// const fetchInitialData = async () => {
// try {
// const [aggregateDist, semesters] = await queryAggregateDistribution(course);
// const initialDistributions: Record<string, Distribution> = { Aggregate: aggregateDist };
// const semesterPromises = semesters.map(semester => querySemesterDistribution(course, semester));
// const semesterDistributions = await Promise.all(semesterPromises);
// semesters.forEach((semester, i) => {
// initialDistributions[`${semester.season} ${semester.year}`] = semesterDistributions[i];
// });
// setDistributions(initialDistributions);
// setStatus(DataStatus.FOUND);
// } catch (e) {
// console.error(e);
// if (e instanceof NoDataError) {
// setStatus(DataStatus.NOT_FOUND);
// } else {
// setStatus(DataStatus.ERROR);
// }
// }
// };

// fetchInitialData();
// }, [course]);

const handleSelectSemester = (event: React.ChangeEvent<HTMLSelectElement>) => {
const [semester, setSemester] = useState('Aggregate');
const [distributions, setDistributions] = useState<Record<string, Distribution>>({});
const [status, setStatus] = useState<DataStatusType>(DataStatus.LOADING);
const ref = useRef<HighchartsReact.RefObject>(null);

const chartData = useMemo(() => {
if (status === DataStatus.FOUND && distributions[semester]) {
return Object.entries(distributions[semester]!).map(([grade, count]) => ({
y: count,
color: GRADE_COLORS[grade as LetterGrade],
}));
}
return Array(13).fill(0);
}, [distributions, semester, status]);

useEffect(() => {
const fetchInitialData = async () => {
try {
const [aggregateDist, semesters] = await queryAggregateDistribution(course);
const initialDistributions: Record<string, Distribution> = { Aggregate: aggregateDist };
const semesterPromises = semesters.map(semester => querySemesterDistribution(course, semester));
const semesterDistributions = await Promise.allSettled(semesterPromises);
semesters.forEach((semester, i) => {
const distributionResult = semesterDistributions[i];

if (!distributionResult) {
throw new Error('Distribution result is undefined');
}

if (distributionResult.status === 'fulfilled') {
initialDistributions[`${semester.season} ${semester.year}`] = distributionResult.value;
}
});
setDistributions(initialDistributions);
setStatus(DataStatus.FOUND);
} catch (e) {
console.error(e);
if (e instanceof NoDataError) {
setStatus(DataStatus.NOT_FOUND);
} else {
setStatus(DataStatus.ERROR);
}
}
};

fetchInitialData();
}, [course]);

const handleSelectSemester = (event: ChangeEvent<HTMLSelectElement>) => {
setSemester(event.target.value);
};

Expand All @@ -116,7 +126,7 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
fontWeight: '400',
},
},
categories: ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F'],
categories: ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F', 'Other'],
tickInterval: 1,
tickWidth: 1.5,
tickLength: 10,
Expand Down Expand Up @@ -168,7 +178,7 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
{
type: 'column',
name: 'Grades',
// data: chartData,
data: chartData,
},
],
};
Expand Down Expand Up @@ -197,8 +207,8 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
<Text variant='small'>
Grade Distribution for {course.department} {course.number}
</Text>
{/* <select
className='flex items-center py-1 px-1 gap-1 border border rounded border-solid'
<select
className='border border rounded-[4px] border-solid px-[12px] py-[8px]'
onChange={handleSelectSemester}
>
{Object.keys(distributions)
Expand All @@ -209,19 +219,22 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
if (k2 === 'Aggregate') {
return 1;
}

const [season1, year1] = k1.split(' ');
const [, year2] = k2.split(' ');

if (year1 !== year2) {
return parseInt(year2, 10) - parseInt(year1, 10);
return parseInt(year2 as string, 10) - parseInt(year1 as string, 10);
}
return season1 === 'Fall' ? 1 : -1;

return season1 === 'Fall' ? -1 : 1;
})
.map(semester => (
<option key={semester} value={semester}>
{semester}
</option>
))}
</select> */}
</select>
</div>
<HighchartsReact ref={ref} highcharts={Highcharts} options={chartOptions} />
</>
Expand Down
2 changes: 1 addition & 1 deletion src/views/lib/database/initializeDB.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import DB_FILE_URL from '@public/database/grades.db?url';
import DB_FILE_URL from '@public/database/grade_distributions.db?url';
import initSqlJs from 'sql.js/dist/sql-wasm';
import WASM_FILE_URL from 'sql.js/dist/sql-wasm.wasm?url';
// import WASM_FILE_URL from '../../../../public/database/sql-wasm.wasm?url';
Expand Down
Loading

0 comments on commit 60d1f48

Please sign in to comment.