Skip to content

Commit

Permalink
[Teachers] Calculate leaderboard data if it's not present
Browse files Browse the repository at this point in the history
  • Loading branch information
valtterikantanen committed Oct 9, 2024
1 parent b0590af commit f6d3728
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 83 deletions.
7 changes: 6 additions & 1 deletion services/backend/src/routes/teachers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ router.get('/top', async (req: GetTopTeachersRequest, res: Response) => {
}

const result = await getTeacherStats(category, Number(yearcode))
res.json(result)
if (result) {
return res.json(result)
}
await findAndSaveTeachers(Number(yearcode), Number(yearcode))
const updatedStats = await getTeacherStats(category, Number(yearcode))
res.json(updatedStats)
})

interface PostTopTeachersRequest extends Request {
Expand Down
128 changes: 65 additions & 63 deletions services/backend/src/services/teachers/top.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Op } from 'sequelize'
import { col, Op } from 'sequelize'

import { Course, Credit, Semester, Teacher } from '../../models'
import { Credit, Semester, Teacher } from '../../models'
import { CreditTypeCode } from '../../types'
import logger from '../../util/logger'
import { redisClient } from '../redis'
import { getCurrentSemester, getSemestersAndYears } from '../semesters'
import { isRegularCourse, TeacherStats } from './helpers'
import { TeacherStats } from './helpers'

export enum CategoryID {
ALL = 'all',
Expand All @@ -21,7 +20,7 @@ const categories = {
export const getTeacherStats = async (categoryId: string, yearCode: number) => {
const { redisKey } = categories[categoryId]
const category = await redisClient.hGet(redisKey, `${yearCode}`)
return category ? JSON.parse(category) : []
return category ? JSON.parse(category) : null
}

const setTeacherStats = async (categoryId: string, yearCode: number, stats: TeacherStats[]) => {
Expand All @@ -38,33 +37,41 @@ export const getCategoriesAndYears = async () => {
}
}

const getCreditsWithTeachersForYear = async (yearCode: number) => {
const getCreditsWithTeachersForYear = async (semesters: string[]) => {
const credits = await Credit.findAll({
attributes: ['id', 'credits', 'credittypecode', 'isStudyModule', 'is_open'],
include: [
{
model: Semester,
required: true,
attributes: [
'credits',
'credittypecode',
'isStudyModule',
'is_open',
[col('teachers.id'), 'teacherId'],
[col('teachers.name'), 'teacherName'],
],
include: {
model: Teacher,
through: {
attributes: [],
where: {
yearcode: {
[Op.eq]: yearCode,
},
},
},
{
model: Teacher,
attributes: ['id', 'name'],
required: true,
attributes: [],
where: {
id: {
[Op.notLike]: 'hy-hlo-org%',
},
},
{
model: Course,
attributes: ['code', 'name', 'coursetypecode'],
required: true,
},
where: {
semester_composite: {
[Op.in]: semesters,
},
],
},
raw: true,
})
return credits
return credits as unknown as Array<
Pick<Credit, 'credits' | 'credittypecode' | 'isStudyModule' | 'is_open'> & {
teacherId: string
teacherName: string
}
>
}

const updatedStats = (
Expand All @@ -76,22 +83,17 @@ const updatedStats = (
transferred: boolean
) => {
const { id, name } = teacher
const stats = teacherStats[id] || { id, name, passed: 0, failed: 0, credits: 0, transferred: 0 }
if (passed) {
return {
...stats,
passed: stats.passed + 1,
credits: transferred ? stats.credits : stats.credits + credits,
transferred: transferred ? stats.transferred + credits : stats.transferred,
}
if (!teacherStats[id]) {
teacherStats[id] = { id, name, passed: 0, failed: 0, credits: 0, transferred: 0 }
}
if (failed) {
return {
...stats,
failed: stats.failed + 1,
}
const stats = teacherStats[id]
if (passed) {
stats.passed += 1
stats.credits = transferred ? stats.credits : stats.credits + credits
stats.transferred = transferred ? stats.transferred + credits : stats.transferred
} else if (failed) {
stats.failed += 1
}
return stats
}

const filterTopTeachers = (stats: Record<string, TeacherStats>, limit: number = 50) => {
Expand All @@ -105,30 +107,30 @@ const filterTopTeachers = (stats: Record<string, TeacherStats>, limit: number =
}

const findTopTeachers = async (yearCode: number) => {
const credits = await getCreditsWithTeachersForYear(yearCode)
const all = {} as Record<string, TeacherStats>
const openuni = {} as Record<string, TeacherStats>
credits
.filter(isRegularCourse)
.map(credit => {
const { credits, credittypecode, is_open } = credit
const teachers = credit.teachers
.filter(({ id }) => !id.includes('hy-hlo-org')) // Remove faculties from the leaderboards
.map(({ id, name }) => ({ id, name }))
const passed = Credit.passed(credit) || Credit.improved(credit)
const failed = Credit.failed(credit)
const transferred = credittypecode === CreditTypeCode.APPROVED
return { passed, failed, credits, teachers, is_open, transferred }
})
.forEach(credit => {
const { passed, failed, credits, teachers, is_open, transferred } = credit
teachers.forEach(teacher => {
all[teacher.id] = updatedStats(all, teacher, credits, passed, failed, transferred)
if (is_open) {
openuni[teacher.id] = updatedStats(openuni, teacher, credits, passed, failed, transferred)
}
})
const semesters = (
await Semester.findAll({
where: {
yearcode: yearCode,
},
attributes: ['composite'],
})
).map(({ composite }) => composite)
const credits = await getCreditsWithTeachersForYear(semesters)
const all: Record<string, TeacherStats> = {}
const openuni: Record<string, TeacherStats> = {}
for (const credit of credits) {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { credits, credittypecode, isStudyModule, is_open, teacherId, teacherName } = credit
if (isStudyModule) continue
const passed = Credit.passed(credit) || Credit.improved(credit)
const failed = Credit.failed(credit)
const transferred = credittypecode === CreditTypeCode.APPROVED
const teacher = { id: teacherId, name: teacherName }
updatedStats(all, teacher, credits, passed, failed, transferred)
if (is_open) {
updatedStats(openuni, teacher, credits, passed, failed, transferred)
}
}
return {
all: filterTopTeachers(all),
openuni: filterTopTeachers(openuni),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,34 @@ import moment from 'moment'
import { useState } from 'react'
import { Message, Segment } from 'semantic-ui-react'

import { useGetTopTeachersCategoriesQuery, useLazyGetTopTeachersQuery } from '@/redux/teachers'
import { useGetTopTeachersCategoriesQuery, useGetTopTeachersQuery } from '@/redux/teachers'
import { TeacherStatisticsTable } from '../TeacherStatisticsTable'
import { LeaderForm } from './LeaderForm'

export const TeacherLeaderBoard = () => {
const [selectedYear, setSelectedYear] = useState(null)
const [selectedCategory, setSelectedCategory] = useState(null)
const [getTopTeachers, { data: topTeachers = [] }] = useLazyGetTopTeachersQuery()
const { data: yearsAndCategories, isLoading, isFetching } = useGetTopTeachersCategoriesQuery()
const { data: yearsAndCategories, isFetching: categoriesAreLoading } = useGetTopTeachersCategoriesQuery()
const { data: topTeachers = {}, isFetching: statsAreLoading } = useGetTopTeachersQuery(
{ yearcode: selectedYear, category: selectedCategory },
{ skip: !selectedYear || !selectedCategory }
)

const updateAndSubmitForm = args => {
const year = args.selectedYear || selectedYear
const category = args.selectedCategory || selectedCategory
getTopTeachers({ yearcode: year, category })
}
if (categoriesAreLoading) return <Segment basic loading />

const initLeaderboard = (year, category) => {
setSelectedYear(year)
setSelectedCategory(category)
updateAndSubmitForm({ selectedYear: year, selectedCategory: category })
}

const handleYearChange = (_event, { value }) => {
setSelectedYear(value)
updateAndSubmitForm({ selectedYear: value })
}

const handleCategoryChange = (_event, { value }) => {
setSelectedCategory(value)
updateAndSubmitForm({ selectedCategory: value })
}

if (isLoading || isFetching) return <Segment basic loading />

const yearOptions = Object.values(yearsAndCategories.years)
.map(({ yearcode, yearname }) => ({ key: yearcode, value: yearcode, text: yearname }))
.sort((y1, y2) => y2.value - y1.value)
Expand All @@ -53,8 +47,13 @@ export const TeacherLeaderBoard = () => {
text: name,
}))

const lastUpdated = new Date(topTeachers?.updated).toLocaleDateString(undefined, {
dateStyle: 'long',
const lastUpdated = new Date(topTeachers?.updated).toLocaleString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
minute: 'numeric',
hour: 'numeric',
second: 'numeric',
})

return (
Expand All @@ -72,9 +71,9 @@ export const TeacherLeaderBoard = () => {
selectedyear={selectedYear}
yearoptions={yearOptions}
/>
<Segment>
{topTeachers.length > 0 && <Message>{`Last updated: ${lastUpdated}`}</Message>}
<TeacherStatisticsTable statistics={topTeachers?.stats || []} variant="leaderboard" />
<Segment loading={statsAreLoading}>
{topTeachers.stats?.length > 0 && <Message>{`Last updated: ${lastUpdated}`}</Message>}
<TeacherStatisticsTable statistics={topTeachers.stats ?? []} variant="leaderboard" />
</Segment>
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion services/frontend/src/redux/teachers.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ export const {
useGetTeacherQuery,
useFindTeachersQuery,
useLazyGetTeacherStatisticsQuery,
useLazyGetTopTeachersQuery,
useGetTopTeachersQuery,
useGetTopTeachersCategoriesQuery,
} = teachersApi

0 comments on commit f6d3728

Please sign in to comment.