Skip to content

Commit

Permalink
fix: course sorting by enrollment
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh-Cena committed May 16, 2024
1 parent 9c07500 commit 3f2e6c5
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 111 deletions.
8 changes: 4 additions & 4 deletions frontend/src/components/Search/ResultsColumnSort.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ const isNumeric: { [key in SortKeys]: boolean } = {
course_code: false,
title: false,
friend: true,
average_rating: true,
overall: true,
average_professor_rating: true,
average_workload: true,
workload: true,
average_gut_rating: true,
last_enrollment: true,
times_by_day: true,
enrollment: true,
time: true,
locations_summary: false,
};

Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/Search/ResultsHeaders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ function ResultsHeaders({
average across all professors)
</span>
}
sortOption="average_rating"
sortOption="overall"
>
Overall
</HeaderCol>
Expand All @@ -113,7 +113,7 @@ function ResultsHeaders({
average across all professors)
</span>
}
sortOption="average_workload"
sortOption="workload"
>
Work
</HeaderCol>
Expand Down Expand Up @@ -151,7 +151,7 @@ function ResultsHeaders({
</span>
)
}
sortOption="last_enrollment"
sortOption="enrollment"
>
#
</HeaderCol>
Expand All @@ -167,7 +167,7 @@ function ResultsHeaders({
(sort order based on day and starting time)
</span>
}
sortOption="times_by_day"
sortOption="time"
>
Meets
</HeaderCol>
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/contexts/searchContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ const sortCriteria = {
course_code: 'Sort by Course Code',
title: 'Sort by Course Title',
friend: 'Sort by Friends',
average_rating: 'Sort by Course Rating',
overall: 'Sort by Course Rating',
average_professor_rating: 'Sort by Professor Rating',
average_workload: 'Sort by Workload',
workload: 'Sort by Workload',
average_gut_rating: 'Sort by Guts (Overall - Workload)',
last_enrollment: 'Sort by Last Enrollment',
times_by_day: 'Sort by Days & Times',
enrollment: 'Sort by Last Enrollment',
time: 'Sort by Days & Times',
locations_summary: 'Sort by Locations',
};

Expand Down
145 changes: 46 additions & 99 deletions frontend/src/utilities/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,43 @@ function toDayTimeScore(course: Pick<Courses, 'times_by_day'>): number | null {
return null;
}

function comparatorReturn(
aVal: number | string | null,
bVal: number | string | null,
type ComparableKey = SortKeys | 'season_code' | 'section';

function getAttributeValue(
l: CatalogListing,
key: ComparableKey,
numFriends: NumFriendsReturn,
) {
switch (key) {
case 'friend':
return numFriends[`${l.season_code}${l.crn}`]?.size ?? 0;
case 'overall':
return getOverallRatings(l.course, 'stat');
case 'workload':
return getWorkloadRatings(l.course, 'stat');
case 'enrollment':
return getEnrolled(l.course, 'stat');
case 'time':
return toDayTimeScore(l.course);
case 'course_code':
case 'season_code':
case 'section':
return l[key];
default:
// || is intentional: 0 also means nonexistence
return l.course[key] || null;
}
}

function compareByKey(
a: CatalogListing,
b: CatalogListing,
key: ComparableKey,
ordering: 'asc' | 'desc',
numFriends: NumFriendsReturn,
) {
const aVal = getAttributeValue(a, key, numFriends);
const bVal = getAttributeValue(b, key, numFriends);
if (aVal === null && bVal === null) return 0;
if (aVal === null) return 1;
if (bVal === null) return -1;
Expand All @@ -242,96 +274,6 @@ function comparatorReturn(
return ordering === 'asc' ? strCmp : -strCmp;
}

// We can only sort by primitive keys by default, unless we have special support
type ComparableKey =
| SortKeys
| 'season_code'
| 'section'
| keyof {
[K in keyof CatalogListing['course'] as CatalogListing['course'][K] extends
| string
| number
? K
: never]: K;
};

function compareByKey(
a: CatalogListing,
b: CatalogListing,
key: Exclude<ComparableKey, 'friend'>,
ordering: 'asc' | 'desc',
): number;
function compareByKey(
a: CatalogListing,
b: CatalogListing,
key: ComparableKey,
ordering: 'asc' | 'desc',
numFriends: NumFriendsReturn,
): number;
function compareByKey(
a: CatalogListing,
b: CatalogListing,
key: ComparableKey,
ordering: 'asc' | 'desc',
numFriends?: NumFriendsReturn,
) {
if (key === 'friend') {
// Concatenate season code and crn to form key
const friendsTakingA = numFriends![`${a.season_code}${a.crn}`]?.size ?? 0;
const friendsTakingB = numFriends![`${b.season_code}${b.crn}`]?.size ?? 0;
return comparatorReturn(friendsTakingA, friendsTakingB, ordering);
}
if (key === 'average_rating') {
return comparatorReturn(
getOverallRatings(a.course, 'stat'),
getOverallRatings(b.course, 'stat'),
ordering,
);
}
if (key === 'average_workload') {
return comparatorReturn(
getWorkloadRatings(a.course, 'stat'),
getWorkloadRatings(b.course, 'stat'),
ordering,
);
}
if (key === 'times_by_day') {
return comparatorReturn(
toDayTimeScore(a.course),
toDayTimeScore(b.course),
ordering,
);
}
if (key === 'course_code' || key === 'season_code' || key === 'section')
return comparatorReturn(a.course_code, b.course_code, ordering);
// If value is 0, return null
return comparatorReturn(
// || is intentional: 0 also means nonexistence
a.course[key] || null,
b.course[key] || null,
ordering,
);
}

/**
* Compares two listings by the specified key.
*/
function compare(
a: CatalogListing,
b: CatalogListing,
key: SortKeys,
ordering: 'asc' | 'desc',
numFriends: NumFriendsReturn,
): number {
return (
compareByKey(a, b, key, ordering, numFriends) ||
// Define a stable sort order for courses that compare equal
compareByKey(a, b, 'season_code', 'desc') ||
compareByKey(a, b, 'course_code', 'asc') ||
compareByKey(a, b, 'section', 'asc')
);
}

// Sort courses in catalog or expanded worksheet
export function sortCourses(
courses: CatalogListing[],
Expand All @@ -341,13 +283,18 @@ export function sortCourses(
},
numFriends: NumFriendsReturn,
): CatalogListing[] {
return [...courses].sort((a, b) =>
compare(a, b, ordering.key, ordering.type, numFriends),
return [...courses].sort(
(a, b) =>
compareByKey(a, b, ordering.key, ordering.type, numFriends) ||
// Define a stable sort order for courses that compare equal
compareByKey(a, b, 'season_code', 'desc', numFriends) ||
compareByKey(a, b, 'course_code', 'asc', numFriends) ||
compareByKey(a, b, 'section', 'asc', numFriends),
);
}

type CourseWithEnrolled = {
evaulation_statistic?: {
evaluation_statistic?: {
enrolled: number | null;
} | null;
last_enrollment?: number | null;
Expand All @@ -366,11 +313,11 @@ export function getEnrolled(
course: CourseWithEnrolled,
usage: 'stat' | 'display' | 'modal',
): string | number | null {
if (course.evaulation_statistic?.enrolled) {
if (course.evaluation_statistic?.enrolled) {
// Use enrollment for that season if course has happened
return usage === 'stat'
? course.evaulation_statistic.enrolled
: String(course.evaulation_statistic.enrolled);
? course.evaluation_statistic.enrolled
: String(course.evaluation_statistic.enrolled);
} else if (course.last_enrollment) {
// Use last enrollment if course hasn't happened
if (usage === 'stat') return course.last_enrollment;
Expand Down

0 comments on commit 3f2e6c5

Please sign in to comment.