Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrate to Anteater API #1031

Merged
merged 34 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0bdcd2c
feat: initial
ecxyzzy Nov 11, 2024
744a530
courses display (i think)
ecxyzzy Nov 11, 2024
fe7c479
feat: move websoc logic to backend
ecxyzzy Nov 12, 2024
f0a0ccc
feat: reimplement 'clearCache()'
ecxyzzy Nov 12, 2024
80d8c32
feat: something
ecxyzzy Nov 12, 2024
8372860
feat: grades trpc
ecxyzzy Nov 12, 2024
5ea8ba6
feat: enrollhist trpc
ecxyzzy Nov 12, 2024
5e68bc2
feat: nuke ppapi-next-types
ecxyzzy Nov 12, 2024
58482b1
feat: misc random stuff
ecxyzzy Nov 12, 2024
a94d08b
fix: misc
ecxyzzy Nov 12, 2024
b1436e1
99 typescript errors in the code, 99 ts errors
ecxyzzy Nov 12, 2024
a9d20de
random fixes
ecxyzzy Nov 12, 2024
be259e6
docs
ecxyzzy Nov 12, 2024
0d3660a
anteater api key for ci
ecxyzzy Nov 12, 2024
13ff3d5
chore: undo unnecessary changes to backend
ecxyzzy Nov 12, 2024
58a9603
fix: tests
ecxyzzy Nov 12, 2024
9b4f174
feat: remove hardcoded prereq tree types
ecxyzzy Nov 13, 2024
a3a9a8a
feat: hybrid fuzzy search
ecxyzzy Nov 17, 2024
3c25782
fix(backend): remove constraint on process.env.STAGE
ecxyzzy Nov 17, 2024
40770e8
feat: env shim but string, sort websoc resp
ecxyzzy Nov 17, 2024
f7795a0
feat: codegen'd local fuzzy search
ecxyzzy Nov 18, 2024
51e51b5
fix(backend): websoc tweaks
ecxyzzy Nov 18, 2024
7fb801b
docs: PeterPortal => Anteater API
ecxyzzy Nov 18, 2024
c1c4529
fix(search): avoid race condition
ecxyzzy Nov 18, 2024
c50d531
ci: add api key to backend build steps
ecxyzzy Nov 18, 2024
bce3484
ci: I'm stupid
ecxyzzy Nov 18, 2024
d9d3f4a
fix(backend): i might actually be dumb bruh
ecxyzzy Nov 18, 2024
9fd4cd4
fix(tour): finals
ecxyzzy Nov 18, 2024
d4418f1
chore: sort order
MinhxNguyen7 Nov 18, 2024
a76fd9a
docs: update dotenv and readme
MinhxNguyen7 Nov 18, 2024
646ec85
chore(deps): remove websoc-fuzzy-search
MinhxNguyen7 Nov 18, 2024
e07eddb
chore: add extra console.log to get-search-data
MinhxNguyen7 Nov 18, 2024
7214433
feat(fuzzy): decrease delay
MinhxNguyen7 Nov 18, 2024
7052a3e
refactor: make fuzzy more readable
MinhxNguyen7 Nov 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading