Skip to content

Commit

Permalink
feat(frontend): cache course descriptions (#1004)
Browse files Browse the repository at this point in the history
* cache `GET /courses/*` requests

* put types in `src/types`

* rename the cache ref variables
  • Loading branch information
martanman authored Mar 22, 2023
1 parent 1f4d3ac commit cf454b1
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Typography } from 'antd';
import axios from 'axios';
import { Course, CoursePathFrom, CoursesUnlockedWhenTaken } from 'types/api';
import { CourseTimetable, EnrolmentCapacityData } from 'types/courseCapacity';
import { CourseDescInfoResCache } from 'types/courseDescription';
import { CourseList } from 'types/courses';
import getEnrolmentCapacity from 'utils/getEnrolmentCapacity';
import prepareUserPayload from 'utils/prepareUserPayload';
Expand All @@ -25,12 +26,14 @@ type CourseDescriptionPanelProps = {
className?: string;
courseCode: string;
onCourseClick?: (code: string) => void;
courseDescInfoCache: React.MutableRefObject<CourseDescInfoResCache>;
};

const CourseDescriptionPanel = ({
className,
courseCode,
onCourseClick
onCourseClick,
courseDescInfoCache
}: CourseDescriptionPanelProps) => {
const { degree, planner } = useSelector((state: RootState) => state);

Expand Down Expand Up @@ -60,22 +63,39 @@ const CourseDescriptionPanel = ({
useEffect(() => {
const getCourseInfo = async () => {
try {
const results = await Promise.allSettled([
axios.get<Course>(`/courses/getCourse/${courseCode}`),
axios.get<CoursePathFrom>(`/courses/getPathFrom/${courseCode}`),
axios.post<CoursesUnlockedWhenTaken>(
`/courses/coursesUnlockedWhenTaken/${courseCode}`,
JSON.stringify(prepareUserPayload(degree, planner))
),
axios.get<CourseTimetable>(`${TIMETABLE_API_URL}/${courseCode}`)
]);

const [courseRes, pathFromRes, unlockedRes, courseCapRes] = results;

setCourse(unwrap(courseRes)?.data);
setCoursesPathFrom(unwrap(pathFromRes)?.data.courses);
setCoursesUnlocked(unwrap(unlockedRes)?.data);
setCourseCapacity(getEnrolmentCapacity(unwrap(courseCapRes)?.data));
// if it's not saved already then fetch it
if (!courseDescInfoCache.current[courseCode]) {
const results = await Promise.allSettled([
axios.get<Course>(`/courses/getCourse/${courseCode}`),
axios.get<CoursePathFrom>(`/courses/getPathFrom/${courseCode}`),
axios.post<CoursesUnlockedWhenTaken>(
`/courses/coursesUnlockedWhenTaken/${courseCode}`,
JSON.stringify(prepareUserPayload(degree, planner))
),
axios.get<CourseTimetable>(`${TIMETABLE_API_URL}/${courseCode}`)
]);

const [courseRes, pathFromRes, unlockedRes, courseCapRes] = results;

courseDescInfoCache.current[courseCode] = {
course: unwrap(courseRes)?.data,
pathFrom: unwrap(pathFromRes)?.data.courses,
unlocked: unwrap(unlockedRes)?.data,
courseCap: getEnrolmentCapacity(unwrap(courseCapRes)?.data)
};
}

const {
course: courseData,
pathFrom,
unlocked,
courseCap
} = courseDescInfoCache.current[courseCode];

setCourse(courseData);
setCoursesPathFrom(pathFrom);
setCoursesUnlocked(unlocked);
setCourseCapacity(courseCap);
setIsLoading(false);
} catch (e) {
// eslint-disable-next-line no-console
Expand All @@ -87,7 +107,7 @@ const CourseDescriptionPanel = ({
// gets the associated info for a course
getCourseInfo();
}
}, [courseCode, degree, isLoading, planner]);
}, [courseCode, courseDescInfoCache, degree, isLoading, planner]);

if (isLoading || !course) {
// either still loading or the course wasn't fetchable (fatal)
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/pages/CourseSelector/CourseSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import axios from 'axios';
import { Structure } from 'types/api';
import { CourseDescInfoResCache } from 'types/courseDescription';
import { ProgramStructure } from 'types/structure';
import openNotification from 'utils/openNotification';
import infographic from 'assets/infographicFontIndependent.svg';
Expand All @@ -23,6 +24,7 @@ const CourseSelector = () => {

const dispatch = useDispatch();

const courseDescInfoCache = useRef({} as CourseDescInfoResCache);
const courseCode = tabs[active];

useEffect(() => {
Expand Down Expand Up @@ -65,6 +67,7 @@ const CourseSelector = () => {
<CourseDescriptionPanel
courseCode={courseCode}
onCourseClick={(code) => dispatch(addTab(code))}
courseDescInfoCache={courseDescInfoCache}
/>
</div>
) : (
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/pages/GraphicalSelector/GraphicalSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { Tabs } from 'antd';
import CourseSearchBar from 'components/CourseSearchBar';
import PageTemplate from 'components/PageTemplate';
import SidebarDrawer from 'components/SidebarDrawer';
import { CourseDescInfoResCache } from '../../types/courseDescription';
import { COURSE_INFO_TAB, HELP_TAB, PROGRAM_STRUCTURE_TAB } from './constants';
import CourseGraph from './CourseGraph';
import HowToUse from './HowToUse';
Expand All @@ -12,6 +13,7 @@ const GraphicalSelector = () => {
const [fullscreen, setFullscreen] = useState(false);
const [courseCode, setCourseCode] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState(HELP_TAB);
const courseDescInfoCache = useRef({} as CourseDescInfoResCache);

const items = [
{
Expand All @@ -22,6 +24,7 @@ const GraphicalSelector = () => {
courseCode={courseCode}
key={courseCode}
onCourseClick={setCourseCode}
courseDescInfoCache={courseDescInfoCache}
/>
) : (
'No course selected'
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/types/courseDescription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Course, CoursesUnlockedWhenTaken } from './api';
import { EnrolmentCapacityData } from './courseCapacity';
import { CourseList } from './courses';

type CourseInfo = {
course?: Course;
pathFrom?: CourseList;
unlocked?: CoursesUnlockedWhenTaken;
courseCap?: EnrolmentCapacityData;
};

export type CourseDescInfoResCache = Record<string, CourseInfo>;

0 comments on commit cf454b1

Please sign in to comment.