Skip to content

Commit

Permalink
Merge pull request #7084 from StoDevX/drew/course-terms-filter-debugging
Browse files Browse the repository at this point in the history
Fix course search filtering by term, level, and gereq
  • Loading branch information
drewvolz authored Oct 16, 2023
2 parents 1d0f088 + 0851c68 commit 23b5104
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 54 deletions.
45 changes: 41 additions & 4 deletions source/lib/course-search/urls.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ky, {Options} from 'ky'
import {CourseType, RawCourseType, TermInfoType, TermType} from './types'
import intersection from 'lodash/intersection'

const BASE_URL = 'https://stolaf.dev'
export const COURSE_DATA_PAGE = `${BASE_URL}/course-data/`
Expand All @@ -19,13 +20,49 @@ export let timeData = (options?: Options): Promise<string[]> =>
client.get('data-lists/valid_times.json', options).json()
export let coursesForTerm = async (
term: TermType,
levels: Array<CourseType['level']>,
gereqs: string[] = [],
options?: Options,
): Promise<Array<CourseType>> => {
let data = (await client
.get(`${term.path}`, options)
.json()) as RawCourseType[]
return data.map((course) => ({
spaceAvailable: course.enrolled < course.max,
...course,
})) as CourseType[]
return data
.map((course) => ({
spaceAvailable: course.enrolled < course.max,
...course,
}))
.filter((c) => {
return findMatches(c.level, levels, c.gereqs, gereqs)
}) as CourseType[]
}

const findMatches = (
findLevel: number,
levels: Array<number>,
findgereqs: Array<string> = [],
gereqs: Array<string>,
) => {
if (!levels.length && !gereqs.length) {
return true
}

if (levels.length && gereqs.length) {
return matchesLevels(findLevel, levels) && matchesGEs(findgereqs, gereqs)
} else if (levels.length) {
return matchesLevels(findLevel, levels)
} else if (gereqs.length) {
return matchesGEs(findgereqs, gereqs)
}

return true
}

const matchesLevels = (item: number, levels: Array<CourseType['level']>) => {
let levelRoundedDown = Math.floor(item / 100) * 100
return levels.includes(levelRoundedDown)
}

const matchesGEs = (items: string[] | undefined, gereqs: string[]) => {
return items ? intersection(gereqs, items).length > 0 : false
}
63 changes: 16 additions & 47 deletions source/views/sis/course-search/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
deptData,
geData,
infoJson,
timeData,
} from '../../../lib/course-search/urls'

const ONE_SECOND = 1000
Expand All @@ -20,10 +19,12 @@ const ONE_DAY = ONE_HOUR * 24

export const keys = {
terms: ['catalog', 'terms'] as const,
courses: (term: TermType) => ['catalog', 'courses', term] as const,
courses: (term: TermType, levels: Array<number>, gereqs: Array<string>) =>
['catalog', 'courses', term, levels, gereqs] as const,
gereqs: ['catalog', 'gereqs'] as const,
departments: ['catalog', 'departments'] as const,
times: ['catalog', 'times'] as const,
levels: ['catalog', 'levels'] as const,
}

export function useAvailableTerms(): UseQueryResult<TermType[], unknown> {
Expand All @@ -43,59 +44,35 @@ export function useAvailableTerms(): UseQueryResult<TermType[], unknown> {
})
}

export function useCourseDataForTerm(
term: TermType,
): UseQueryResult<CourseType[], unknown> {
return useQuery({
queryKey: keys.courses(term),
queryFn: ({queryKey: [_group, _courses, term], signal}) =>
coursesForTerm(term, {signal}),
staleTime: ONE_HOUR,
})
}

export function useCourseDataForTerms(
terms: TermType[],
export function useCourseData(
selectedTerms: Array<number> = [],
levels: Array<number> = [],
gereqs: Array<string> = [],
): UseQueryResult<CourseType[], unknown>[] {
let query = (
term: TermType,
): UseQueryOptions<
CourseType[],
unknown,
CourseType[],
ReturnType<(typeof keys)['courses']>
> => ({
queryKey: keys.courses(term),
queryFn: ({queryKey: [_group, _courses, term], signal}) =>
coursesForTerm(term, {signal}),
staleTime: ONE_HOUR,
})

return useQueries({
queries: terms.map(query),
})
}

export function useCourseData(): UseQueryResult<CourseType[], unknown>[] {
let {data: terms = []} = useAvailableTerms()
let filteredTerms = terms.filter((t) => selectedTerms.includes(t.term))

let query = (
term: TermType,
levels: Array<number> = [],
gereqs: Array<string> = [],
): UseQueryOptions<
CourseType[],
unknown,
CourseType[],
ReturnType<(typeof keys)['courses']>
> => ({
queryKey: keys.courses(term),
queryFn: ({queryKey: [_group, _courses, term], signal}) =>
coursesForTerm(term, {signal}),
queryKey: keys.courses(term, levels, gereqs),
queryFn: ({queryKey: [_group, _courses, term, levels, gereqs], signal}) =>
coursesForTerm(term, levels, gereqs, {signal}),
staleTime: ONE_HOUR,
enabled: terms.length > 0,
})

return useQueries({
queries: terms.map(query),
queries: filteredTerms.length
? filteredTerms.map((term) => query(term, levels, gereqs))
: terms.map((term) => query(term, levels, gereqs)),
})
}

Expand All @@ -114,11 +91,3 @@ export function useDepartments(): UseQueryResult<string[]> {
staleTime: ONE_DAY,
})
}

export function useTimes(): UseQueryResult<string[]> {
return useQuery({
queryKey: keys.times,
queryFn: ({signal}) => timeData({signal}),
staleTime: ONE_DAY,
})
}
58 changes: 55 additions & 3 deletions source/views/sis/course-search/results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import {RouteProp, useNavigation, useRoute} from '@react-navigation/native'
import {ChangeTextEvent, RootStackParamList} from '../../../navigation/types'
import {NativeStackNavigationOptions} from '@react-navigation/native-stack'
import {useDebounce} from '@frogpond/use-debounce'
import {ListSeparator, ListSectionHeader} from '@frogpond/lists'
import {ListSeparator, ListSectionHeader, largeListProps} from '@frogpond/lists'
import * as c from '@frogpond/colors'
import {CourseRow} from './row'
import memoize from 'lodash/memoize'
import {parseTerm} from '../../../lib/course-search'
import {NoticeView} from '@frogpond/notice'
import {FilterToolbar} from '@frogpond/filter'
import {ListSpecType} from '@frogpond/filter/types'
import {applySearch, sortAndGroupResults} from './lib/execute-search'
import {useCourseData} from './query'
import {UseQueryResult} from '@tanstack/react-query'
Expand Down Expand Up @@ -58,6 +59,49 @@ function queriesToCourses(
.filter((data) => data !== undefined) as CourseType[]
}

const useSelectedFilter = (
filterKey: string,
filters: FilterType<CourseType>[],
) => {
return React.useMemo(
() => filters.find((f) => f.key === filterKey),
[filterKey, filters],
)
}

const useSelectedTerm = (filters: FilterType<CourseType>[]) => {
let termFilter = useSelectedFilter('term', filters)

if (termFilter?.enabled) {
let termFilterSpec = termFilter.spec as ListSpecType
return termFilterSpec.selected.map((spec) => Number(spec.title))
}

return []
}

const useSelectedLevel = (filters: FilterType<CourseType>[]) => {
let levelFilter = useSelectedFilter('level', filters)

if (levelFilter?.enabled) {
let levelFilterSpec = levelFilter.spec as ListSpecType
return levelFilterSpec.selected.map((spec) => Number(spec.title))
}

return []
}

const useSelectedGE = (filters: FilterType<CourseType>[]) => {
let geFilter = useSelectedFilter('gereqs', filters)

if (geFilter?.enabled) {
let geFilterSpec = geFilter.spec as ListSpecType
return geFilterSpec.selected.map((spec) => spec.title)
}

return []
}

export const CourseSearchResultsView = (): JSX.Element => {
let dispatch = useAppDispatch()
let navigation = useNavigation()
Expand All @@ -78,7 +122,15 @@ export const CourseSearchResultsView = (): JSX.Element => {
let [searchQuery, setSearchQuery] = React.useState(initialQuery)
let delayedQuery = useDebounce(searchQuery, 500)

let allCoursesByTerm = useCourseData()
let selectedTerms = useSelectedTerm(filters)
let selectedLevels = useSelectedLevel(filters)
let selectedGEs = useSelectedGE(filters)

let allCoursesByTerm = useCourseData(
selectedTerms,
selectedLevels,
selectedGEs,
)
let areCoursesLoading = allCoursesByTerm.some((r) => r.isLoading)
let areCoursesInError = allCoursesByTerm.some((r) => r.isError)

Expand Down Expand Up @@ -188,7 +240,7 @@ export const CourseSearchResultsView = (): JSX.Element => {
<ListSectionHeader title={parseTerm(title)} />
)}
sections={results}
windowSize={10}
{...largeListProps}
/>
)
}
Expand Down

0 comments on commit 23b5104

Please sign in to comment.