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

Monitor crowd-sourced data pod #734

Draft
wants to merge 76 commits into
base: gql
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
8f16396
start rating module
PineND Oct 28, 2024
1604307
initial session
PineND Oct 28, 2024
360b54e
added ratings
nnicolee Oct 30, 2024
f738445
made progress on adding ratings/data
nnicolee Oct 30, 2024
7cc3134
Merge branch 'crowd-sourced-data' of https://github.com/asuc-octo/ber…
nnicolee Oct 30, 2024
84255a8
new typedef & controller
PineND Oct 30, 2024
9aa0933
Merge branch 'crowd-sourced-data' of https://github.com/asuc-octo/ber…
PineND Oct 30, 2024
9888104
Merge branch 'gql' into crowd-sourced-data
mathhulk Oct 30, 2024
35a49ee
minor type casting fixes
PineND Oct 30, 2024
8938ca8
Merge remote-tracking branch 'refs/remotes/origin/crowd-sourced-data'…
mathhulk Oct 30, 2024
344993b
user rating adding/removing functionality
PineND Oct 30, 2024
31835b6
Merge branch 'crowd-sourced-data' of https://github.com/asuc-octo/ber…
PineND Oct 30, 2024
a551439
added resolver
PineND Oct 30, 2024
e145e12
added input security
PineND Oct 31, 2024
950ee57
improve aggregation performance
PineND Oct 31, 2024
62a4a10
add mongo aggergation for user ratings
PineND Oct 31, 2024
977aa89
resolver fix
PineND Nov 1, 2024
6137ea4
update
PineND Nov 1, 2024
3a64620
formatter fix
PineND Nov 1, 2024
f2af68a
added number to the dropdown option for the ratings and added pine ba…
nnicolee Nov 1, 2024
63cf853
unbrick the backend
PineND Nov 1, 2024
a741787
Merge branch 'crowd-sourced-data' of https://github.com/asuc-octo/ber…
PineND Nov 1, 2024
ce31be4
add email, metric weightedAverage
PineND Nov 1, 2024
eb09978
update
PineND Nov 1, 2024
091659a
remove placeholder function
PineND Nov 1, 2024
65b5999
Merge branch 'gql' into crowd-sourced-data
mathhulk Nov 1, 2024
4ae7e45
Merge branch 'crowd-sourced-data' of https://github.com/asuc-octo/ber…
PineND Nov 1, 2024
adc89b1
removed email
PineND Nov 1, 2024
d9a29d3
add semesters with available ratings
PineND Nov 1, 2024
e2db725
better handling of empty fetches
PineND Nov 1, 2024
fe20093
overall improvements, verified all resolver functions working
PineND Nov 2, 2024
73fd0b9
added ratings to class module
PineND Nov 2, 2024
89fd7b3
formatter
PineND Nov 2, 2024
cfc2096
chore: Clean up
mathhulk Nov 2, 2024
fd2fcbd
Connected frontend to backend
nnicolee Nov 4, 2024
e72380e
Added aggregatedMetricsModel to "cache" running counts of ratings
PineND Nov 4, 2024
d19527c
small bug fixes and improvements
PineND Nov 5, 2024
03da5d0
small name change for clarity
PineND Nov 6, 2024
7f0561a
abstract out aggregation to seperate file
PineND Nov 7, 2024
27febc0
speedups to frontend query for aggregated ratings
Nov 9, 2024
c5a1cab
formatter
Nov 9, 2024
c6dfd67
formatting
PineND Nov 9, 2024
b51b0ca
added ratings count to user schema
PineND Nov 13, 2024
f00bcb2
Merge branch 'gql' of https://github.com/asuc-octo/berkeleytime into …
Nov 13, 2024
102be00
added reccomended stats
PineND Nov 13, 2024
cdedda9
allow for unselection
PineND Nov 13, 2024
9bbfef8
remove bcourse specific note
PineND Nov 13, 2024
4935347
removed discussion, improve drop down styling
PineND Nov 13, 2024
c6fdec4
factor out RatingsContainer into seperate files
PineND Nov 13, 2024
2846c0f
extract placeholder data
PineND Nov 13, 2024
29e0218
improve ratingForm
PineND Nov 13, 2024
997afb0
fix styling redundancies
PineND Nov 13, 2024
550869d
Revert "fix styling redundancies"
PineND Nov 13, 2024
ff49eeb
improve clarity
PineND Nov 13, 2024
ed79d75
modify add / remove rating mutations to be mongoDB transactions to en…
PineND Nov 14, 2024
3e9ab7c
parallelize mongo requests, improve handling
PineND Nov 14, 2024
74173a3
minor typecasting fix
PineND Nov 14, 2024
225ce94
formatting
PineND Nov 14, 2024
091849c
add getting semesters by instructor
PineND Nov 15, 2024
e811a36
update
PineND Nov 15, 2024
f7370e6
update
PineND Nov 15, 2024
1944a03
update
PineND Nov 15, 2024
05dd847
update
PineND Nov 15, 2024
f6a437d
update
PineND Nov 15, 2024
d8f8c1b
update
PineND Nov 15, 2024
270c7ac
Merge branch 'crowd-sourced-data-frontend-and-backend' into crowd-sou…
Nov 15, 2024
931bb79
linter
Nov 15, 2024
064e155
minor changes
nnicolee Nov 15, 2024
58b8ca3
Merge branch 'crowd-sourced-data-frontend-and-backend' into crowd-sou…
Nov 15, 2024
aae3d49
merge frontend errors fixed
Nov 15, 2024
6c66a8e
linter
Nov 15, 2024
6f354f2
add mongo replica set for transactions
PineND Nov 15, 2024
3a26aa3
Merge branch 'crowd-sourced-data' of https://github.com/asuc-octo/ber…
PineND Nov 15, 2024
9a80ec9
connect
PineND Nov 15, 2024
ad70465
fixed merge header
Nov 15, 2024
a665864
removed isAllTime bug
Nov 15, 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
2 changes: 2 additions & 0 deletions apps/backend/src/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Common from "./common";
import Course from "./course";
import Enrollment from "./enrollment";
import GradeDistribution from "./grade-distribution";
import Rating from "./rating";
import Schedule from "./schedule";
import Term from "./term";
import User from "./user";
Expand All @@ -20,6 +21,7 @@ const modules = [
Course,
Class,
Enrollment,
Rating,
];

export const resolvers = merge(modules.map((module) => module.resolver));
Expand Down
334 changes: 334 additions & 0 deletions apps/backend/src/modules/rating/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
import { RatingModel } from '@repo/common';
import {
ClassIdentifier,
MetricName,
RatingIdentifier
} from "../../generated-types/graphql";
import {
formatUserRatings,
formatAggregatedRatings
} from "./formatter";

// TODO: get list of all avaiable semesters with ratings (check for class offered?)
// TODO: get list of all available semesters class offered in
// TODO: get user ratings for given class

export const getClassRatingsForUser = async (
context: any,
classIdentifier: ClassIdentifier
) => {
if (!context.user._id) throw new Error("Unauthorized");

const aggregated = await ratingAggregator(classIdentifier);
if (!aggregated.length) return null;

return formatAggregatedRatings(aggregated[0]);
};

export const createRating = async (
context: any,
ratingIdentifier: RatingIdentifier,
email: string,
PineND marked this conversation as resolved.
Show resolved Hide resolved
value: number
) => {
if (!context.user._id) throw new Error("Unauthorized");
checkValueConstraint(ratingIdentifier.metricName, value);

const existingRating = await checkRatingExists(context, ratingIdentifier);
if (existingRating) {
existingRating.value = value;
await existingRating.save();
}

// TODO: add ratechecking for user (get timestamps of most recent ratings)

else {
await RatingModel.create({
createdBy: context.user._id,
email: email,
...ratingIdentifier,
value: value
});
}
const aggregated = await ratingAggregator(ratingIdentifier);
if (!aggregated.length) return null;

return formatAggregatedRatings(aggregated[0]);
};

export const deleteRating = async (
context: any,
ratingIdentifier: RatingIdentifier
) => {
if (!context.user._id) throw new Error("Unauthorized");

await RatingModel.findOneAndDelete({
createdBy: context.user._id,
...ratingIdentifier,
});

return true;
};

export const getUserRatings = async (context: any) => {
if (!context.user._id) throw new Error("Unauthorized");

const userRatings = await userRatingAggregator(context);
if (!userRatings.length) return null;

return formatUserRatings(userRatings[0]);
};

export const getAggregatedRatings = async (
classIdentifier: ClassIdentifier,
isAllTime: boolean
) => {
let filter;
if (isAllTime) {
filter = classIdentifier;
} else {
filter = {
subject: classIdentifier.subject,
courseNumber: classIdentifier.courseNumber,
class: classIdentifier.class
};
}
const aggregated = await ratingAggregator(filter);
if (!aggregated.length) return null;

return formatAggregatedRatings(aggregated[0]);
};

// Helper functions

const checkRatingExists = async (context: any, ratingIdentifier: RatingIdentifier) => {
return await RatingModel.findOne({
...ratingIdentifier,
createdBy: context.user._id
});
};

const checkValueConstraint = (metricName: MetricName, value: number) => {
const numberScaleMetrics = ['Usefulness', 'Difficulty', 'Workload'] as const;
const booleanScaleMetrics = ['Attendance', 'Recording'] as const;

if (numberScaleMetrics.includes(metricName as typeof numberScaleMetrics[number])) {
if (value < 1 || value > 5 || !Number.isInteger(value)) {
throw new Error(`${metricName} rating must be an integer between 1 and 5`);
}
} else if (booleanScaleMetrics.includes(metricName as typeof booleanScaleMetrics[number])) {
if (value !== 0 && value !== 1) {
throw new Error(`${metricName} rating must be either 0 or 1`);
}
}
}

const userRatingAggregator = async (context: any) => {
return await RatingModel.aggregate([
{ $match: { createdBy: context.user._id } },
{
$group: {
_id: {
createdBy: "$createdBy",
email: "$email",
subject: "$subject",
courseNumber: "$courseNumber",
semester: "$semester",
year: "$year",
class: "$class",
},
metrics: {
$push: {
metricName: "$metricName",
value: "$value",
// updatedAt: "$updatedAt" - not sure how to do the typedef
}
}
}
},
{
$group: {
_id: {
createdBy: "$_id.createdBy",
email: "$_id.email"
},
classes: {
$push: {
subject: "$_id.subject",
courseNumber: "$_id.courseNumber",
semester: "$_id.semester",
year: "$_id.year",
class: "$_id.class",
metrics: "$metrics"
}
},
totalCount: { $sum: 1 }
}
},
{
$project: {
_id: 0,
email: "$_id.email",
createdBy: "$_id.createdBy",
count: "$totalCount",
classes: 1
}
}
]);
};

const ratingAggregator = async (filter: any) => {
return await RatingModel.aggregate([
{ $match: filter },
{
$group: {
_id: {
subject: "$subject",
courseNumber: "$courseNumber",
semester: "$semester",
year: "$year",
class: "$class",
metricName: "$metricName",
category: "$category"
},
categoryCount: { $sum: 1 },
values: { $push: "$value" }
}
},
{
$group: {
_id: {
subject: "$_id.subject",
courseNumber: "$_id.courseNumber",
semester: "$_id.semester",
year: "$_id.year",
class: "$_id.class",
metricName: "$_id.metricName"
},
totalCount: { $sum: "$categoryCount" },
sumValues: { $sum: { $sum: "$values" } },
categories: {
$push: {
value: "$_id.category",
count: "$categoryCount"
}
}
}
},
{
$addFields: {
weightedAverage: { $divide: ["$sumValues", "$totalCount"] }
}
},
{
$group: {
_id: {
subject: "$_id.subject",
courseNumber: "$_id.courseNumber",
semester: "$_id.semester",
year: "$_id.year",
class: "$_id.class"
},
metrics: {
$push: {
metricName: "$_id.metricName",
count: "$totalCount",
weightedAverage: "$weightedAverage",
categories: "$categories"
}
}
}
},
{
$project: {
_id: 0,
subject: "$_id.subject",
courseNumber: "$_id.courseNumber",
semester: "$_id.semester",
year: "$_id.year",
class: "$_id.class",
metrics: {
$map: {
input: "$metrics",
as: "metric",
in: {
metricName: "$$metric.metricName",
count: "$$metric.count",
weightedAverage: "$$metric.weightedAverage",
categories: "$$metric.categories"
}
}
}
}
}
]);
};

// example userRatingAggregator return value:
// {
// createdBy: "Pine",
// email: "[email protected]",
// count: 2,
// classes: [
// {
// subject: "COMPSCI",
// courseNumber: "1001",
// semester: "FALL",
// year: 2023,
// class: "61B",
// metrics: [
// { metricName: "Difficulty", value: 4, updatedAt: "2024-03-20T15:30:45.123Z" },
// { metricName: "Workload", value: 3, updatedAt: "2024-03-20T15:30:45.123Z" },
// { metricName: "Usefulness", value: 5, updatedAt: "2024-03-20T15:30:45.123Z" },
// { metricName: "Attendance", value: 0, updatedAt: "2024-03-20T15:30:45.123Z" },
// { metricName: "Recording", value: 1, updatedAt: "2024-03-20T15:30:45.123Z" }
// ]
// },
// {
// subject: "DATA",
// courseNumber: "1002",
// semester: "SPRING",
// year: 2024,
// class: "140",
// metrics: [
// { metricName: "Difficulty", value: 5, updatedAt: "2024-03-20T15:30:45.123Z" },
// { metricName: "Recording", value: 0, updatedAt: "2024-03-20T15:30:45.123Z" }
// ]
// }
// ],
// }

// example ratingAggregator return value:
// {
// subject: "COMPSCI",
// courseNumber: "1003",
// semester: "FALL",
// year: 2023,
// class: "70",
// metrics: [
// {
// metricName: "Difficulty",
// count: 45,
// weightedAverage: 3.44,
// categories: [
// { value: 5, count: 10 },
// { value: 4, count: 15 },
// { value: 3, count: 10 },
// { value: 2, count: 5 },
// { value: 1, count: 5 }
// ]
// },
// {
// metricName: "Workload",
// count: 92,
// weightedAverage: 2.8,
// categories: [
// { value: 5, count: 15 },
// { value: 4, count: 12 },
// { value: 3, count: 10 },
// { value: 2, count: 50 },
// { value: 1, count: 5 }
// ]
// },
// ]
// }
Loading