Skip to content

Commit

Permalink
Merge branch 'lint-and-format' into redesign
Browse files Browse the repository at this point in the history
  • Loading branch information
TyHil committed Jan 27, 2024
2 parents 97a6e6f + 4127c57 commit 559d150
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 57 deletions.
3 changes: 2 additions & 1 deletion src/data/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function buildProfessorProfiles(payload: ShowCourseTabPayload) {
);
const nebulaSections = await Promise.all(
nebulaProfessors.map((professor) => {
if (professor?._id === undefined) return null;
if (professor?._id === undefined || !nebulaCourse) return null;
return fetchNebulaSections({
courseReference: nebulaCourse._id,
professorReference: professor._id,
Expand All @@ -61,6 +61,7 @@ export async function buildProfessorProfiles(payload: ShowCourseTabPayload) {
for (let j = 0; j < nebulaSections[i]?.length; j++) {
const section = nebulaSections[i][j];
if (
section.grade_distribution &&
section.grade_distribution.length !== 0 &&
!compareArrays(section.grade_distribution, Array(14).fill(0))
)
Expand Down
1 change: 1 addition & 0 deletions src/data/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const NEBULA_FETCH_OPTIONS = {
};

export const SCHOOL_ID = '1273';
export const RMP_GRAPHQL_URL = 'https://www.ratemyprofessors.com/graphql';

function unRegister(key: string) {
let newVar = '';
Expand Down
181 changes: 125 additions & 56 deletions src/data/fetchFromRmp.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { HEADERS, PROFESSOR_QUERY } from '~data/config';
import { HEADERS, PROFESSOR_QUERY, RMP_GRAPHQL_URL } from '~data/config';

function reportError(context, err) {
console.error('Error in ' + context + ': ' + err);
}

function getProfessorUrl(professorName: string, schoolId: string): string {
return `https://www.ratemyprofessors.com/search/teachers?query=${encodeURIComponent(
return `https://www.ratemyprofessors.com/search/professors/${schoolId}?q=${encodeURIComponent(
professorName,
)}&sid=${btoa(`School-${schoolId}`)}`;
)}}`;
}
function getProfessorUrls(
professorNames: string[],
Expand All @@ -22,22 +26,37 @@ function getProfessorIds(texts: string[], professorNames: string[]): string[] {
name.toLowerCase(),
);
texts.forEach((text) => {
let matched = false;
const regex = /"legacyId":(\d+).*?"firstName":"(.*?)","lastName":"(.*?)"/g;
for (const match of text.matchAll(regex)) {
console.log(
match[2].split(' ')[0].toLowerCase() + ' ' + match[3].toLowerCase(),
);
if (
lowerCaseProfessorNames.includes(
match[2].split(' ')[0].toLowerCase() + ' ' + match[3].toLowerCase(),
)
) {
professorIds.push(match[1]);
matched = true;
let pendingMatch = null;
const regex =
/"legacyId":(\d+).*?"numRatings":(\d+).*?"firstName":"(.*?)","lastName":"(.*?)"/g;
const allMatches: string[] = text.match(regex);
const highestNumRatings = 0;

if (allMatches) {
for (const fullMatch of allMatches) {
for (const match of fullMatch.matchAll(regex)) {
console.log(
match[3].split(' ')[0].toLowerCase() +
' ' +
match[4].toLowerCase() +
' ',
);
const numRatings = parseInt(match[2]);
if (
lowerCaseProfessorNames.includes(
match[3].split(' ')[0].toLowerCase() +
' ' +
match[4].toLowerCase(),
) &&
numRatings >= highestNumRatings
) {
pendingMatch = match[1];
}
}
}
}
if (!matched) professorIds.push(null);

professorIds.push(pendingMatch);
});
return professorIds;
}
Expand All @@ -61,56 +80,106 @@ function getGraphQlUrlProps(professorIds: string[]) {
return graphQlUrlProps;
}

function fetchWithGraphQl(graphQlUrlProps, resolve) {
const graphqlUrl = 'https://www.ratemyprofessors.com/graphql';

Promise.all(graphQlUrlProps.map((u) => fetch(graphqlUrl, u)))
.then((responses) => Promise.all(responses.map((res) => res.json())))
.then((ratings) => {
for (let i = 0; i < ratings.length; i++) {
if (
ratings[i] != null &&
Object.hasOwn(ratings[i], 'data') &&
Object.hasOwn(ratings[i]['data'], 'node')
) {
ratings[i] = ratings[i]['data']['node'];
}
function wait(delay) {
return new Promise((resolve) => setTimeout(resolve, delay));
}

function fetchRetry(url: string, delay: number, tries: number, fetchOptions) {
function onError(err) {
const triesLeft: number = tries - 1;
if (!triesLeft) {
throw err;
}
return wait(delay).then(() =>
fetchRetry(url, delay, triesLeft, fetchOptions),
);
}
return fetch(url, fetchOptions).catch(onError);
}

// If using orderedFetchOpts, make sure that it is an array and that the index of the fetch options corresponds to the index of the response in the responses array.
async function validateResponses(responses, orderedFetchOpts) {
for (const [key, value] of Object.entries(responses)) {
const notOk = value?.status !== 200;
if (notOk && value && value.url) {
const details = {
status: value.status,
statusText: value.statusText,
redirected: value.redirected,
url: value.url,
};
reportError(
'validateResponses',
'Status not OK for fetch request. Details are: ' +
JSON.stringify(details),
);
const fetchOptions = orderedFetchOpts[key] || {}; // If we don't have fetch options, we just use an empty object.
responses[key] = await fetchRetry(value?.url, 200, 3, fetchOptions);
}
}
return responses;
}

export async function fetchWithGraphQl(graphQlUrlProps) {
try {
const responses = await validateResponses(
await Promise.all(graphQlUrlProps.map((u) => fetch(RMP_GRAPHQL_URL, u))),
graphQlUrlProps,
);
// We now have all the responses. So, we consider all the responses, and collect the ratings.
const ratings: RMPRatingInterface[] = await Promise.all(
responses.map((res) => res.json()),
);
for (let i = 0; i < ratings.length; i++) {
if (
ratings[i] != null &&
Object.prototype.hasOwnProperty.call(ratings[i], 'data') &&
Object.prototype.hasOwnProperty.call(ratings[i]['data'], 'node')
) {
ratings[i] = ratings[i]['data']['node'];
}
console.log(ratings);
resolve(ratings);
});
}
return ratings;
} catch (err) {
reportError('fetchWithGraphQl', err);
return [];
}
}

export interface RmpRequest {
professorNames: string[];
schoolId: string;
}
export function requestProfessorsFromRmp(

export async function requestProfessorsFromRmp(
request: RmpRequest,
): Promise<RMPInterface[]> {
return new Promise((resolve, reject) => {
// make a list of urls for promises
const professorUrls = getProfessorUrls(
request.professorNames,
request.schoolId,
// make a list of urls for promises
const professorUrls = getProfessorUrls(
request.professorNames,
request.schoolId,
);

// fetch professor ids from each url
try {
const responses = await validateResponses(
await Promise.all(professorUrls.map((u) => fetch(u))),
[],
);

// fetch professor ids from each url
Promise.all(professorUrls.map((u) => fetch(u)))
.then((responses) => Promise.all(responses.map((res) => res.text())))
.then((texts) => {
const professorIds = getProfessorIds(texts, request.professorNames);

// create fetch objects for each professor id
const graphQlUrlProps = getGraphQlUrlProps(professorIds);

// fetch professor info by id with graphQL
fetchWithGraphQl(graphQlUrlProps, resolve);
})
.catch((error) => {
reject(error);
});
});
const texts = await Promise.all(responses.map((res) => res.text()));
const professorIds = getProfessorIds(texts, request.professorNames);

// create fetch objects for each professor id
const graphQlUrlProps = getGraphQlUrlProps(professorIds);

// fetch professor info by id with graphQL
const professors = await fetchWithGraphQl(graphQlUrlProps);
return professors;
} catch (error) {
reportError('requestProfessorsFromRmp', error);
return [];
}
}

interface RMPInterface {
Expand Down
32 changes: 32 additions & 0 deletions src/data/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,35 @@ export interface SectionInterface {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
teaching_assistants: any[];
}

export interface CourseCodeInterface {
courseCount: number;
courseName: string;
}
export interface RatingsDistributionInterface {
r1: number;
r2: number;
r3: number;
r4: number;
r5: number;
total: number;
}
export interface TeacherRatingTag {
tagCount: number;
tagName: string;
}

export interface RMPRatingInterface {
avgDifficulty: number;
avgRating: number;
courseCodes: CourseCodeInterface[];
department: string;
firstName: string;
lastName: string;
legacyId: number;
numRatings: number;
ratingsDistribution: RatingsDistributionInterface;
school: { id: string };
teacherRatingTags: TeacherRatingTag[];
wouldTakeAgainPercent: number;
}

0 comments on commit 559d150

Please sign in to comment.