mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-12 19:03:39 +03:00
[Course statistics] Move minimum and maximum attainment date calculation to the backend
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import crypto from 'crypto'
|
||||
import { Request, Response, Router } from 'express'
|
||||
import moment from 'moment'
|
||||
|
||||
import { serviceProvider } from '../config'
|
||||
import { getCourseYearlyStats } from '../services/courses'
|
||||
import { getCoursesByNameAndOrCode, getCoursesByCodes } from '../services/courses/courseFinders'
|
||||
import { CourseWithSubsId } from '../types'
|
||||
import { getFullStudyProgrammeRights, hasFullAccessToStudentData, validateParamLength } from '../util'
|
||||
import logger from '../util/logger'
|
||||
|
||||
@@ -24,24 +24,45 @@ router.get('/v2/coursesmulti', async (req: GetCoursesRequest, res: Response) =>
|
||||
return res.status(400).json({ error: 'Query parameter name or code is invalid' })
|
||||
}
|
||||
|
||||
let results: { courses: CourseWithSubsId[] } = { courses: [] }
|
||||
results = await getCoursesByNameAndOrCode(name, code)
|
||||
const courses = await getCoursesByNameAndOrCode(name, code)
|
||||
|
||||
if (combineSubstitutions === 'false') {
|
||||
const courseCodes = results.courses.map(course => course.code)
|
||||
const courseCodes = courses.map(course => course.code)
|
||||
const substitutions = [
|
||||
...new Set(
|
||||
results.courses
|
||||
.flatMap(course => course.substitutions)
|
||||
.filter(code => code !== null && !courseCodes.includes(code))
|
||||
courses.flatMap(course => course.substitutions).filter(code => code !== null && !courseCodes.includes(code))
|
||||
),
|
||||
]
|
||||
const substitutionDetails = await getCoursesByCodes(substitutions)
|
||||
for (const substitution of substitutionDetails) {
|
||||
results.courses.push(substitution.dataValues)
|
||||
courses.push(substitution.toJSON())
|
||||
}
|
||||
}
|
||||
res.json(results)
|
||||
|
||||
const mergedCourses = {}
|
||||
|
||||
for (const course of courses) {
|
||||
const groupId = combineSubstitutions === 'true' ? course.subsId! : course.code
|
||||
if (!(course.max_attainment_date && course.min_attainment_date)) {
|
||||
continue
|
||||
}
|
||||
if (!mergedCourses[groupId]) {
|
||||
mergedCourses[groupId] = {
|
||||
...course,
|
||||
substitutions: combineSubstitutions === 'true' ? course.substitutions : [],
|
||||
}
|
||||
} else {
|
||||
const mergedCourse = mergedCourses[groupId]
|
||||
if (moment(mergedCourse.max_attainment_date).isBefore(course.max_attainment_date)) {
|
||||
mergedCourse.max_attainment_date = course.max_attainment_date
|
||||
}
|
||||
if (moment(mergedCourse.min_attainment_date).isAfter(course.min_attainment_date)) {
|
||||
mergedCourse.min_attainment_date = course.min_attainment_date
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ courses: Object.values(mergedCourses) })
|
||||
})
|
||||
|
||||
interface GetCourseYearlyStatsRequest extends Request {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { orderBy } from 'lodash'
|
||||
import { Op } from 'sequelize'
|
||||
|
||||
import { Course } from '../../models'
|
||||
@@ -52,7 +53,17 @@ const getRawCourses = async (name: string, code: string) => {
|
||||
const getCourses = async (name: string, code: string) => {
|
||||
const rawCourses = await getRawCourses(name, code)
|
||||
const courses: CourseWithSubsId[] = rawCourses
|
||||
.map(course => ({ ...course.dataValues }))
|
||||
.map(course => ({
|
||||
...course.toJSON(),
|
||||
substitutions: orderBy(course.substitutions, [
|
||||
substitution => {
|
||||
if (/^A/.exec(substitution)) return 4 // open university codes come last
|
||||
if (/^\d/.exec(substitution)) return 2 // old numeric codes come second
|
||||
if (/^[A-Za-z]/.exec(substitution)) return 1 // new letter based codes come first
|
||||
return 3 // unknown, comes before open uni?
|
||||
},
|
||||
]),
|
||||
}))
|
||||
.sort((a, b) => getSortRank(b.code) - getSortRank(a.code))
|
||||
return courses
|
||||
}
|
||||
@@ -87,7 +98,7 @@ export const getCoursesByNameAndOrCode = async (name: string, code: string) => {
|
||||
}
|
||||
})
|
||||
|
||||
return { courses }
|
||||
return courses
|
||||
}
|
||||
|
||||
export const getCoursesByCodes = (codes: string[]) => {
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { createSelector } from '@reduxjs/toolkit'
|
||||
import { orderBy } from 'lodash'
|
||||
|
||||
const sortSubstitutions = course => {
|
||||
const result = orderBy(
|
||||
course.substitutions,
|
||||
[
|
||||
a => {
|
||||
if (a.match(/^A/)) return 4 // open university codes come last
|
||||
if (a.match(/^\d/)) return 2 // old numeric codes come second
|
||||
if (a.match(/^[A-Za-z]/)) return 1 // new letter based codes come first
|
||||
return 3 // unknown, comes before open uni?
|
||||
},
|
||||
],
|
||||
['asc']
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
const filterCourseSearchResults = (courses, combineSubstitutions) => {
|
||||
const mergedCourses = {}
|
||||
courses.forEach(course => {
|
||||
// Filter out courses made out of custom attainments (#4624)
|
||||
if (/-\d{9}/.test(course.code)) {
|
||||
return
|
||||
}
|
||||
const groupId = combineSubstitutions ? course.subsId : course.code
|
||||
|
||||
if (!(course.max_attainment_date && course.min_attainment_date)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!mergedCourses[groupId]) {
|
||||
mergedCourses[groupId] = {
|
||||
...course,
|
||||
substitutions: combineSubstitutions ? sortSubstitutions(course) : [],
|
||||
min_attainment_date: new Date(course.min_attainment_date),
|
||||
max_attainment_date: new Date(course.max_attainment_date),
|
||||
}
|
||||
} else {
|
||||
const mergedCourse = mergedCourses[groupId]
|
||||
mergedCourse.min_attainment_date = new Date(
|
||||
Math.min(mergedCourse.min_attainment_date, new Date(course.min_attainment_date))
|
||||
)
|
||||
mergedCourse.max_attainment_date = new Date(
|
||||
Math.max(mergedCourse.max_attainment_date, new Date(course.max_attainment_date))
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const result = Object.values(mergedCourses)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const getCourseSearchSelector = state => state.courseSearch.data.courses
|
||||
|
||||
export const getCourseSearchResults = createSelector(
|
||||
[getCourseSearchSelector, (state, combineSubstitutions) => combineSubstitutions],
|
||||
(courses, combineSubstitutions) => {
|
||||
if (!courses) {
|
||||
return []
|
||||
}
|
||||
return filterCourseSearchResults(courses, combineSubstitutions)
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user