Skip to content

Commit 25a796e

Browse files
authored
Fix average rating route (#506)
* fix avg rating * fix featured review route for undefined
1 parent deb8b14 commit 25a796e

File tree

5 files changed

+32
-39
lines changed

5 files changed

+32
-39
lines changed

api/src/controllers/reviews.ts

+11-14
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import { TRPCError } from '@trpc/server';
1717
import { db } from '../db';
1818
import { review, user, vote } from '../db/schema';
19-
import { and, count, desc, eq, sql } from 'drizzle-orm';
19+
import { and, avg, count, desc, eq, sql } from 'drizzle-orm';
2020
import { datesToStrings } from '../helpers/date';
2121

2222
async function userWroteReview(userId: number | undefined, reviewId: number) {
@@ -208,33 +208,30 @@ const reviewsRouter = router({
208208
const featuredReviewCriteria = [desc(review.content), desc(voteSubQuery.score), desc(review.verified)];
209209

210210
const field = input.type === 'course' ? review.courseId : review?.professorId;
211-
const featuredReview = (
212-
await db
213-
.select()
214-
.from(review)
215-
.where(eq(field, input.id))
216-
.leftJoin(voteSubQuery, eq(voteSubQuery.reviewId, review.id))
217-
.orderBy(...featuredReviewCriteria)
218-
.limit(1)
219-
)[0];
211+
const featuredReview = await db
212+
.select()
213+
.from(review)
214+
.where(eq(field, input.id))
215+
.leftJoin(voteSubQuery, eq(voteSubQuery.reviewId, review.id))
216+
.orderBy(...featuredReviewCriteria)
217+
.limit(1);
220218

221-
return datesToStrings(featuredReview.review) as FeaturedReviewData;
219+
return featuredReview.length > 0 ? (datesToStrings(featuredReview[0].review) as FeaturedReviewData) : undefined;
222220
}),
223221

224222
/**
225223
* Get avg ratings for a course's professors or a professor's courses
226224
*/
227-
scores: publicProcedure
225+
avgRating: publicProcedure
228226
.input(z.object({ type: z.enum(['course', 'professor']), id: z.string() }))
229227
.query(async ({ input }) => {
230228
const field = input.type === 'course' ? review.courseId : review.professorId;
231229
const otherField = input.type === 'course' ? review.professorId : review.courseId;
232230

233231
const results = await db
234-
.select({ name: otherField, score: sql`COALESCE(SUM(${vote.vote}), 0)`.mapWith(Number) })
232+
.select({ name: otherField, avgRating: avg(review.rating).mapWith(Number) })
235233
.from(review)
236234
.where(eq(field, input.id))
237-
.leftJoin(vote, eq(vote.reviewId, review.id))
238235
.groupBy(otherField);
239236

240237
return results;

site/src/component/SearchPopup/SearchPopup.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,15 @@ const SearchPopupContent: FC<SearchPopupProps> = (props) => {
109109
<div key={`search-popup-carousel-${i}`} className="search-popup-carousel search-popup-block">
110110
<div>
111111
<span className="search-popup-carousel-score">
112-
{score.score == -1 ? '?' : Number.isInteger(score.score) ? score.score : score.score.toFixed(2)}
112+
{score.avgRating == -1
113+
? '?'
114+
: Number.isInteger(score.avgRating)
115+
? score.avgRating
116+
: score.avgRating.toFixed(2)}
113117
</span>
114118
<span className="search-popup-carousel-max-score">/ 5.0</span>
115119
</div>
116-
<Link to={`/${props.searchType == 'course' ? 'professor' : 'course'}/${score.key}`}>
120+
<Link to={`/${props.searchType == 'course' ? 'professor' : 'course'}/${score.id}`}>
117121
{score.name}
118122
</Link>
119123
</div>

site/src/pages/SearchPage/CoursePopup.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const CoursePopup: FC = () => {
1212

1313
useEffect(() => {
1414
if (course) {
15-
trpc.reviews.scores.query({ type: 'course', id: course.id }).then((res) => {
15+
trpc.reviews.avgRating.query({ type: 'course', id: course.id }).then((res) => {
1616
const scores: ScoreData[] = [];
1717
// set of ucinetid professors with scores
1818
const scoredProfessors = new Set(res.map((v) => v.name));
@@ -21,19 +21,19 @@ const CoursePopup: FC = () => {
2121
if (course.instructors[entry.name]) {
2222
scores.push({
2323
name: course.instructors[entry.name].name,
24-
score: entry.score,
25-
key: entry.name,
24+
avgRating: entry.avgRating,
25+
id: entry.name,
2626
});
2727
}
2828
});
2929
// add unknown score
3030
Object.keys(course.instructors).forEach((ucinetid) => {
3131
if (!scoredProfessors.has(ucinetid)) {
32-
scores.push({ name: course.instructors[ucinetid].name, score: -1, key: ucinetid });
32+
scores.push({ name: course.instructors[ucinetid].name, avgRating: -1, id: ucinetid });
3333
}
3434
});
3535
// sort by highest score
36-
scores.sort((a, b) => b.score - a.score);
36+
scores.sort((a, b) => b.avgRating - a.avgRating);
3737
setScores(scores);
3838
});
3939
}

site/src/pages/SearchPage/ProfessorPopup.tsx

+7-16
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { FeaturedReviewData } from '@peterportal/types';
99

1010
const ProfessorPopup: FC = () => {
1111
const professor = useAppSelector(selectProfessor);
12-
const [featured, setFeatured] = useState<FeaturedReviewData>(null!);
12+
const [featured, setFeatured] = useState<FeaturedReviewData | undefined>();
1313
const [scores, setScores] = useState<ScoreData[]>([]);
1414

1515
useEffect(() => {
@@ -18,31 +18,22 @@ const ProfessorPopup: FC = () => {
1818
type: 'professor',
1919
id: professor.ucinetid,
2020
} as const;
21-
trpc.reviews.scores.query(reviewParams).then((res: ScoreData[]) => {
21+
trpc.reviews.avgRating.query(reviewParams).then((res) => {
22+
const scores = res.map((course) => ({ name: course.name, avgRating: course.avgRating, id: course.name }));
2223
const scoredCourses = new Set(res.map((v) => v.name));
23-
res.forEach((v) => (v.key = v.name));
2424
Object.keys(professor.courses).forEach((course) => {
2525
// remove spaces
2626
course = course.replace(/\s+/g, '');
2727
// add unknown score
2828
if (!scoredCourses.has(course)) {
29-
res.push({ name: course, score: -1, key: course });
29+
scores.push({ name: course, avgRating: -1, id: course });
3030
}
3131
});
3232
// sort by highest score
33-
res.sort((a, b) => b.score - a.score);
34-
setScores(res);
35-
});
36-
trpc.reviews.featured.query(reviewParams).then((res) => {
37-
// if has a featured review
38-
if (res) {
39-
setFeatured(res);
40-
}
41-
// no reviews for this professor
42-
else {
43-
setFeatured(null!);
44-
}
33+
res.sort((a, b) => b.avgRating - a.avgRating);
34+
setScores(scores);
4535
});
36+
trpc.reviews.featured.query(reviewParams).then(setFeatured);
4637
}
4738
}, [professor]);
4839

site/src/types/types.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import {
99

1010
export interface ScoreData {
1111
name: string;
12-
score: number;
13-
key?: string;
12+
avgRating: number;
13+
/** course id or ucinetid */
14+
id: string;
1415
}
1516

1617
export type SearchIndex = 'courses' | 'professors';

0 commit comments

Comments
 (0)