Skip to content

Commit

Permalink
feat: migrate to Anteater API (#1031)
Browse files Browse the repository at this point in the history
Co-authored-by: MinhxNguyen7 <[email protected]>
  • Loading branch information
ecxyzzy and MinhxNguyen7 authored Nov 18, 2024
1 parent c2f7d35 commit df581af
Show file tree
Hide file tree
Showing 72 changed files with 1,220 additions and 990 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/deploy_production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ env:
VITE_TILES_ENDPOINT: ${{ secrets.VITE_TILES_ENDPOINT}}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
ANTEATER_API_KEY: ${{ secrets.ANTEATER_API_KEY }}

# Turborepo credentials.
TURBO_API: ${{ vars.TURBO_API }}
Expand Down Expand Up @@ -112,6 +113,8 @@ jobs:

- name: Build backend
run: pnpm --filter "antalmanac-backend" build
env:
ANTEATER_API_KEY: ${{ secrets.ANTEATER_API_KEY }}

- name: Deploy backend production CloudFormation stack
run: pnpm --filter "antalmanac-cdk" backend-production deploy
Expand Down Expand Up @@ -140,6 +143,7 @@ jobs:
run: pnpm --filter "antalmanac-backend" build
env:
NODE_ENV: development
ANTEATER_API_KEY: ${{ secrets.ANTEATER_API_KEY }}

- name: Deploy backend development CloudFormation stack
run: pnpm --filter "antalmanac-cdk" backend-development deploy
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/deploy_staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ env:
VITE_TILES_ENDPOINT: ${{ secrets.VITE_TILES_ENDPOINT}}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
ANTEATER_API_KEY: ${{ secrets.ANTEATER_API_KEY }}

# Turborepo credentials.
TURBO_API: ${{ vars.TURBO_API }}
Expand Down Expand Up @@ -168,6 +169,8 @@ jobs:

- name: Build backend
run: pnpm --filter "antalmanac-backend" build
env:
ANTEATER_API_KEY: ${{ secrets.ANTEATER_API_KEY }}

- name: Deploy backend staging CloudFormation stack
run: pnpm --filter "antalmanac-cdk" backend-staging deploy
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
VITE_TILES_ENDPOINT: ${{ secrets.VITE_TILES_ENDPOINT}}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
ANTEATER_API_KEY: ${{ secrets.ANTEATER_API_KEY }}

# Turborepo credentials.
TURBO_API: ${{ vars.TURBO_API }}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ A summary of the libraries we use are listed below.

### Backend
- [tRPC](https://trpc.io) - type-safe API access layer for the AntAlmanac API.
- [PeterPortal API](https://api.peterportal.org) - API maintained by ICSSC for retrieving UCI data.
- [Anteater API](https://docs.icssc.club/developer/anteaterapi) - API maintained by ICSSC for retrieving UCI data.

### Tooling
- [Vite](https://vitejs.dev) - Blazingly fast, modern bundler.
Expand Down
4 changes: 1 addition & 3 deletions apps/antalmanac/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@
"recharts": "^2.4.2",
"superjson": "^1.12.3",
"ua-parser-js": "^1.0.37",
"websoc-fuzzy-search": "^1.0.1",
"zustand": "^4.3.2"
},
"devDependencies": {
Expand Down Expand Up @@ -96,9 +95,8 @@
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"peterportal-api-next-types": "1.0.0-rc.2.68.0",
"prettier": "^2.8.4",
"typescript": "^4.9.5",
"typescript": "5.6.3",
"vite": "^4.4.9",
"vite-plugin-svgr": "^2.4.0"
}
Expand Down
5 changes: 2 additions & 3 deletions apps/antalmanac/src/actions/AppStoreActions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { RepeatingCustomEvent, ScheduleCourse, ShortCourseSchedule } from '@packages/antalmanac-types';
import { RepeatingCustomEvent, ScheduleCourse, ShortCourseSchedule, WebsocSection } from '@packages/antalmanac-types';
import { CourseDetails } from '@packages/antalmanac-types';
import { TRPCError } from '@trpc/server';
import { VariantType } from 'notistack';
import { WebsocSection } from 'peterportal-api-next-types';

import { SnackbarPosition } from '$components/NotificationSnackbar';
import analyticsEnum, { logAnalytics, courseNumAsDecimal } from '$lib/analytics';
import trpc from '$lib/api/trpc';
import { CourseDetails } from '$lib/course_data.types';
import { warnMultipleTerms } from '$lib/helpers';
import { removeLocalStorageUserId, setLocalStorageUserId } from '$lib/localStorage';
import AppStore from '$stores/AppStore';
Expand Down
21 changes: 5 additions & 16 deletions apps/antalmanac/src/components/Calendar/CourseCalendarEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Chip, IconButton, Paper, Tooltip } from '@material-ui/core';
import { Theme, withStyles } from '@material-ui/core/styles';
import { ClassNameMap, Styles } from '@material-ui/core/styles/withStyles';
import { Delete } from '@material-ui/icons';
import { WebsocSectionFinalExam } from '@packages/antalmanac-types';
import { useEffect, useRef, useCallback } from 'react';
import { Event } from 'react-big-calendar';
import { Link } from 'react-router-dom';
Expand Down Expand Up @@ -107,21 +108,9 @@ export interface Location {
days?: string;
}

export interface FinalExam {
examStatus: 'NO_FINAL' | 'TBA_FINAL' | 'SCHEDULED_FINAL';
dayOfWeek: 'Sun' | 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri' | 'Sat' | null;
month: number | null;
day: number | null;
startTime: {
hour: number;
minute: number;
} | null;
endTime: {
hour: number;
minute: number;
} | null;
locations: Location[] | null;
}
export type FinalExam =
| (Omit<Extract<WebsocSectionFinalExam, { examStatus: 'SCHEDULED_FINAL' }>, 'bldg'> & { locations: Location[] })
| Extract<WebsocSectionFinalExam, { examStatus: 'NO_FINAL' | 'TBA_FINAL' }>;

export interface CourseEvent extends CommonCalendarEvent {
locations: Location[];
Expand Down Expand Up @@ -195,7 +184,7 @@ const CourseCalendarEvent = (props: CourseCalendarEventProps) => {
} else if (finalExam.examStatus == 'TBA_FINAL') {
finalExamString = 'Final TBA';
} else {
if (finalExam.startTime && finalExam.endTime && finalExam.month && finalExam.locations) {
if (finalExam.examStatus === 'SCHEDULED_FINAL') {
const timeString = formatTimes(finalExam.startTime, finalExam.endTime, isMilitaryTime);
const locationString = `at ${finalExam.locations
.map((location) => `${location.building} ${location.room}`)
Expand Down
2 changes: 1 addition & 1 deletion apps/antalmanac/src/components/Header/Import.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import {
} from '@material-ui/core';
import InputLabel from '@material-ui/core/InputLabel';
import { PostAdd } from '@material-ui/icons';
import { CourseInfo } from '@packages/antalmanac-types';
import { ChangeEvent, useCallback, useEffect, useState } from 'react';

import TermSelector from '../RightPane/CoursePane/SearchForm/TermSelector';
import RightPaneStore from '../RightPane/RightPaneStore';

import { addCustomEvent, openSnackbar, addCourse } from '$actions/AppStoreActions';
import analyticsEnum, { logAnalytics } from '$lib/analytics';
import { CourseInfo } from '$lib/course_data.types';
import { QueryZotcourseError } from '$lib/customErrors';
import { warnMultipleTerms } from '$lib/helpers';
import { WebSOC } from '$lib/websoc';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ function getCourses() {
...course.section,
},
],
updatedAt: null,
};
formattedCourses.push(formattedCourse);
}
Expand Down Expand Up @@ -266,7 +267,7 @@ function SkeletonSchedule() {
<ScheduleNoteBox />

<Typography variant="body1">
PeterPortal or WebSoc is currently unreachable. This is the information that we can currently retrieve.
Anteater API is currently unreachable. This is the information that we can currently retrieve.
</Typography>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Close } from '@mui/icons-material';
import { Alert, Box, IconButton, useMediaQuery } from '@mui/material';
import { AACourse, AASection } from '@packages/antalmanac-types';
import { WebsocDepartment, WebsocSchool, WebsocAPIResponse, GE } from 'peterportal-api-next-types';
import { AACourse, AASection, WebsocDepartment, WebsocSchool, WebsocAPIResponse, GE } from '@packages/antalmanac-types';
import { useCallback, useEffect, useState } from 'react';
import LazyLoad from 'react-lazyload';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import TextField from '@material-ui/core/TextField';
import Autocomplete, { AutocompleteInputChangeReason } from '@material-ui/lab/Autocomplete';
import type { SearchResult } from '@packages/antalmanac-types';
import { PureComponent } from 'react';
import UAParser from 'ua-parser-js';
import search from 'websoc-fuzzy-search';

type SearchResult = ReturnType<typeof search>;

import RightPaneStore from '../../RightPaneStore';

import analyticsEnum, { logAnalytics } from '$lib/analytics';
import trpc from '$lib/api/trpc';

const SEARCH_TIMEOUT_MS = 150;

const emojiMap: Record<string, string> = {
GE_CATEGORY: '🏫', // U+1F3EB :school:
DEPARTMENT: '🏢', // U+1F3E2 :office:
COURSE: '📚', // U+1F4DA :books:
INSTRUCTOR: '🍎', // U+1F34E :apple:
};

const romanArr = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII'];
Expand All @@ -34,10 +34,13 @@ interface FuzzySearchProps {
}

interface FuzzySearchState {
cache: Record<string, SearchResult>;
cache: Record<string, Record<string, SearchResult> | undefined>;
open: boolean;
results: SearchResult;
results: Record<string, SearchResult> | undefined;
value: string;
loading: boolean;
requestTimestamp?: number;
pendingRequest?: number;
}

class FuzzySearch extends PureComponent<FuzzySearchProps, FuzzySearchState> {
Expand All @@ -46,12 +49,15 @@ class FuzzySearch extends PureComponent<FuzzySearchProps, FuzzySearchState> {
open: false,
results: {},
value: '',
loading: false,
requestTimestamp: undefined,
pendingRequest: undefined,
};

doSearch = (value: string) => {
if (!value) return;
const emoji = value.slice(0, 2);
const ident: string[] = emoji === emojiMap.INSTRUCTOR ? [value.slice(3)] : value.slice(3).split(':');
const ident = value.slice(3).split(':');
const term = RightPaneStore.getFormData().term;
RightPaneStore.resetFormValues();
RightPaneStore.updateFormValue('term', term);
Expand All @@ -68,36 +74,11 @@ class FuzzySearch extends PureComponent<FuzzySearchProps, FuzzySearchState> {
break;
case emojiMap.COURSE: {
const deptValue = ident[0].split(' ').slice(0, -1).join(' ');
let deptLabel;
for (const [key, value] of Object.entries(this.state.cache)) {
if (Object.keys(value ?? {}).includes(deptValue)) {
deptLabel = this.state.cache[key]?.[deptValue].name;
break;
}
}
if (!deptLabel) {
const deptSearch = search({ query: deptValue.toLowerCase(), numResults: 1 });
if (deptSearch?.[deptValue]) {
deptLabel = deptSearch[deptValue].name;
this.setState({
cache: {
...this.state.cache,
[deptValue.toLowerCase()]: deptSearch,
},
});
}
}
RightPaneStore.updateFormValue('deptValue', deptValue);
RightPaneStore.updateFormValue('deptLabel', `${deptValue}: ${deptLabel}`);
RightPaneStore.updateFormValue('deptLabel', deptValue);
RightPaneStore.updateFormValue('courseNumber', ident[0].split(' ').slice(-1)[0]);
break;
}
case emojiMap.INSTRUCTOR:
RightPaneStore.updateFormValue(
'instructor',
Object.keys(this.state.results ?? {}).filter((x) => this.state.results?.[x].name === ident[0])[0]
);
break;
default:
break;
}
Expand All @@ -124,18 +105,43 @@ class FuzzySearch extends PureComponent<FuzzySearchProps, FuzzySearchState> {
case 'DEPARTMENT':
return `${emojiMap.DEPARTMENT} ${option}: ${object.name}`;
case 'COURSE':
// @ts-expect-error type SearchResult.metadata can only be of type CourseMetaData in this case, but the type is not exposed so we can't cast directly
return `${emojiMap.COURSE} ${object.metadata.department} ${object.metadata.number}: ${object.name}`;
case 'INSTRUCTOR':
return `${emojiMap.INSTRUCTOR} ${object.name}`;
default:
break;
return '';
}
return '';
};

getOptionSelected = () => true;

requestIsCurrent = (requestTimestamp: number) => this.state.requestTimestamp === requestTimestamp;

// Returns a function for use with setTimeout that exhibits the following behavior:
// If the request is current, make the request. Then, if it is still current, update the component's
// state to reflect the results of the query.
maybeDoSearchFactory = (requestTimestamp: number) => () => {
if (!this.requestIsCurrent(requestTimestamp)) return;
trpc.search.doSearch
.query({ query: this.state.value })
.then((result) => {
if (!this.requestIsCurrent(requestTimestamp)) return;
this.setState({
cache: {
...this.state.cache,
[this.state.value]: result,
},
results: result,
loading: false,
pendingRequest: undefined,
requestTimestamp: undefined,
});
})
.catch((e) => {
if (!this.requestIsCurrent(requestTimestamp)) return;
this.setState({ results: {}, loading: false });
console.error(e);
});
};

onInputChange = (_event: unknown, value: string, reason: AutocompleteInputChangeReason) => {
const lowerCaseValue = value.toLowerCase();
if (reason === 'input') {
Expand All @@ -149,16 +155,15 @@ class FuzzySearch extends PureComponent<FuzzySearchProps, FuzzySearchState> {
if (this.state.cache[this.state.value]) {
this.setState({ results: this.state.cache[this.state.value] });
} else {
try {
const result = search({ query: this.state.value, numResults: 10 });
this.setState({
cache: { ...this.state.cache, [this.state.value]: result },
results: result,
});
} catch (e) {
this.setState({ results: {} });
console.error(e);
}
const requestTimestamp = Date.now();
this.setState({ results: {}, loading: true, requestTimestamp }, () => {
window.clearTimeout(this.state.pendingRequest);
const pendingRequest = window.setTimeout(
this.maybeDoSearchFactory(requestTimestamp),
SEARCH_TIMEOUT_MS
);
this.setState({ pendingRequest });
});
}
}
);
Expand All @@ -176,6 +181,7 @@ class FuzzySearch extends PureComponent<FuzzySearchProps, FuzzySearchState> {
render() {
return (
<Autocomplete
loading={this.state.loading}
style={{ width: '100%' }}
options={Object.keys(this.state.results ?? {})}
renderInput={(params) => (
Expand Down
Loading

0 comments on commit df581af

Please sign in to comment.