mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-13 03:13:40 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cb8601cc9 | ||
|
|
74b16f7e50 | ||
|
|
ec2b1fe6a8 | ||
|
|
a8ec3c2257 | ||
|
|
d9b4aa58f1 | ||
|
|
f29c33cd60 | ||
|
|
fccd0d7769 | ||
|
|
5e59956485 | ||
|
|
5c3fe43473 | ||
|
|
3fbcbbf0d2 | ||
|
|
b040bac675 | ||
|
|
c1689253ea |
+46
-159
@@ -7,68 +7,35 @@ const {
|
||||
refreshUber,
|
||||
refreshProtoCProgramme,
|
||||
getStartYears,
|
||||
} = require('./services/coolDataScience')
|
||||
|
||||
const {
|
||||
refreshProtoC: refreshProtoCV2,
|
||||
refreshStatus: refreshStatusV2,
|
||||
refreshStatusGraduated: refreshStatusGraduatedV2,
|
||||
refreshUber: refreshUberV2,
|
||||
refreshProtoCProgramme: refreshProtoCProgrammeV2,
|
||||
getStartYears: getStartYearsV2,
|
||||
} = require('./servicesV2/trends')
|
||||
|
||||
const { refreshAssociationsInRedis: refreshAssociationsInRedisV2 } = require('./servicesV2/studyrights')
|
||||
const { refreshAssociationsInRedis } = require('./servicesV2/studyrights')
|
||||
const { getAllProgrammes, nonGraduatedStudentsOfElementDetail } = require('./servicesV2/studyrights')
|
||||
const { productivityStatsForStudytrack, throughputStatsForStudytrack } = require('./servicesV2/studyprogramme')
|
||||
const { findAndSaveTeachers } = require('./servicesV2/topteachers')
|
||||
const {
|
||||
getAllProgrammes: getAllProgrammesV2,
|
||||
nonGraduatedStudentsOfElementDetail: nonGraduatedStudentsOfElementDetailV2,
|
||||
} = require('./servicesV2/studyrights')
|
||||
|
||||
const {
|
||||
productivityStatsForStudytrack: productivityStatsForStudytrackV2,
|
||||
throughputStatsForStudytrack: throughputStatsForStudytrackV2,
|
||||
} = require('./servicesV2/studyprogramme')
|
||||
|
||||
const topteachersV2 = require('./servicesV2/topteachers')
|
||||
|
||||
const { isNewHYStudyProgramme } = require('./util')
|
||||
const { calculateFacultyYearlyStats } = require('./services/faculties')
|
||||
const { patchFacultyYearlyStats } = require('./servicesV2/analyticsService')
|
||||
|
||||
const {
|
||||
setProductivity: setProductivityV2,
|
||||
setThroughput: setThroughputV2,
|
||||
patchProductivity: patchProductivityV2,
|
||||
patchThroughput: patchThroughputV2,
|
||||
patchNonGraduatedStudents: patchNonGraduatedStudentsV2,
|
||||
setProductivity,
|
||||
setThroughput,
|
||||
patchProductivity,
|
||||
patchThroughput,
|
||||
patchNonGraduatedStudents,
|
||||
} = require('./servicesV2/analyticsService')
|
||||
const { isNewHYStudyProgramme } = require('./util')
|
||||
|
||||
const schedule = (cronTime, func) => new CronJob({ cronTime, onTick: func, start: true, timeZone: 'Europe/Helsinki' })
|
||||
|
||||
// This is currently done with old data, update to use sis db
|
||||
const refreshFacultyYearlyStats = async () => {
|
||||
try {
|
||||
console.log('Refreshing faculty yearly stats...')
|
||||
const data = await calculateFacultyYearlyStats()
|
||||
await patchFacultyYearlyStats(data)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const refreshStudyrightAssociationsV2 = async () => {
|
||||
const refreshStudyrightAssociations = async () => {
|
||||
try {
|
||||
console.log('Refreshing studyright associations...')
|
||||
await refreshAssociationsInRedisV2()
|
||||
await refreshAssociationsInRedis()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const refreshOverviewV2 = async () => {
|
||||
const refreshOverview = async () => {
|
||||
try {
|
||||
console.log('Refreshing throughput and productivity for programmes...')
|
||||
const codes = (await getAllProgrammesV2()).map(p => p.code)
|
||||
const codes = (await getAllProgrammes()).map(p => p.code)
|
||||
let ready = 0
|
||||
for (const code of codes) {
|
||||
let programmeStatsSince = new Date('2017-07-31')
|
||||
@@ -78,12 +45,12 @@ const refreshOverviewV2 = async () => {
|
||||
programmeStatsSince = new Date('2000-07-31')
|
||||
}
|
||||
try {
|
||||
await patchThroughputV2({ [code]: { status: 'RECALCULATING' } })
|
||||
const data = await throughputStatsForStudytrackV2(code, programmeStatsSince.getFullYear())
|
||||
await setThroughputV2(data)
|
||||
await patchThroughput({ [code]: { status: 'RECALCULATING' } })
|
||||
const data = await throughputStatsForStudytrack(code, programmeStatsSince.getFullYear())
|
||||
await setThroughput(data)
|
||||
} catch (e) {
|
||||
try {
|
||||
await patchThroughputV2({ [code]: { status: 'RECALCULATION ERRORED' } })
|
||||
await patchThroughput({ [code]: { status: 'RECALCULATION ERRORED' } })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@@ -91,12 +58,12 @@ const refreshOverviewV2 = async () => {
|
||||
console.log(`Failed to update throughput stats for code: ${code}, reason: ${e.message}`)
|
||||
}
|
||||
try {
|
||||
await patchProductivityV2({ [code]: { status: 'RECALCULATING' } })
|
||||
const data = await productivityStatsForStudytrackV2(code, programmeStatsSince)
|
||||
await setProductivityV2(data)
|
||||
await patchProductivity({ [code]: { status: 'RECALCULATING' } })
|
||||
const data = await productivityStatsForStudytrack(code, programmeStatsSince)
|
||||
await setProductivity(data)
|
||||
} catch (e) {
|
||||
try {
|
||||
await patchProductivityV2({
|
||||
await patchProductivity({
|
||||
[code]: { status: 'RECALCULATION ERRORED' },
|
||||
})
|
||||
} catch (e) {
|
||||
@@ -113,20 +80,20 @@ const refreshOverviewV2 = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const refreshTeacherLeaderboardV2 = async () => {
|
||||
const refreshTeacherLeaderboard = async () => {
|
||||
try {
|
||||
const startyearcode = new Date().getFullYear() - 1950
|
||||
const endyearcode = startyearcode + 1
|
||||
console.log('Refreshing teacher leaderboard...')
|
||||
await topteachersV2.findAndSaveTeachers(startyearcode, endyearcode)
|
||||
await findAndSaveTeachers(startyearcode, endyearcode)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
const refreshNonGraduatedStudentsOfOldProgrammesV2 = async () => {
|
||||
const refreshNonGraduatedStudentsOfOldProgrammes = async () => {
|
||||
try {
|
||||
const oldProgrammeCodes = (await getAllProgrammesV2()).map(p => p.code).filter(c => !isNewHYStudyProgramme(c))
|
||||
const oldProgrammeCodes = (await getAllProgrammes()).map(p => p.code).filter(c => !isNewHYStudyProgramme(c))
|
||||
let i = 0
|
||||
console.log('Refreshing non-graduated students of old programmes...')
|
||||
await Promise.all(
|
||||
@@ -134,8 +101,8 @@ const refreshNonGraduatedStudentsOfOldProgrammesV2 = async () => {
|
||||
c =>
|
||||
new Promise(async res => {
|
||||
try {
|
||||
const [nonGraduatedStudents, studentnumbers] = await nonGraduatedStudentsOfElementDetailV2(c)
|
||||
await patchNonGraduatedStudentsV2({ [c]: { formattedData: nonGraduatedStudents, studentnumbers } })
|
||||
const [nonGraduatedStudents, studentnumbers] = await nonGraduatedStudentsOfElementDetail(c)
|
||||
await patchNonGraduatedStudents({ [c]: { formattedData: nonGraduatedStudents, studentnumbers } })
|
||||
console.log(`${++i}/${oldProgrammeCodes.length}`)
|
||||
} catch (e) {
|
||||
console.log(`Failed refreshing non-graduated students of programme ${c}!`)
|
||||
@@ -203,8 +170,9 @@ const refreshUberToRedis = async () => {
|
||||
|
||||
const refreshProtoCProgrammeToRedis = async () => {
|
||||
try {
|
||||
const codes = (await getAllProgrammesV2()).map(p => p.code).filter(code => code.includes('KH'))
|
||||
const codes = (await getAllProgrammes()).map(p => p.code).filter(code => code.includes('KH'))
|
||||
codes.forEach(async code => {
|
||||
console.log('refreshing code', code)
|
||||
const defaultQuery = { include_old_attainments: 'false', exclude_non_enrolled: 'false', code }
|
||||
const onlyOld = { include_old_attainments: 'true', exclude_non_enrolled: 'false', code }
|
||||
const onlyEnr = { include_old_attainments: 'false', exclude_non_enrolled: 'true', code }
|
||||
@@ -220,111 +188,30 @@ const refreshProtoCProgrammeToRedis = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const refreshProtoCtoRedisV2 = async () => {
|
||||
try {
|
||||
const defaultQuery = { include_old_attainments: 'false', exclude_non_enrolled: 'false' }
|
||||
const onlyOld = { include_old_attainments: 'true', exclude_non_enrolled: 'false' }
|
||||
const onlyEnr = { include_old_attainments: 'false', exclude_non_enrolled: 'true' }
|
||||
const bothToggles = { include_old_attainments: 'true', exclude_non_enrolled: 'true' }
|
||||
console.log('Refreshing CDS ProtoC')
|
||||
await refreshProtoCV2(defaultQuery)
|
||||
await refreshProtoCV2(onlyOld)
|
||||
await refreshProtoCV2(onlyEnr)
|
||||
await refreshProtoCV2(bothToggles)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
const refreshStatistics = async () => {
|
||||
await refreshStudyrightAssociations()
|
||||
await refreshOverview()
|
||||
await refreshNonGraduatedStudentsOfOldProgrammes()
|
||||
await refreshTeacherLeaderboard()
|
||||
}
|
||||
|
||||
const refreshStatusToRedisV2 = async () => {
|
||||
try {
|
||||
const unixMillis = moment().valueOf()
|
||||
const date = new Date(Number(unixMillis))
|
||||
|
||||
date.setHours(23, 59, 59, 999)
|
||||
const showByYearOff = 'false'
|
||||
const showByYear = 'true'
|
||||
console.log('Refreshing CDS Status')
|
||||
await refreshStatusV2(date.getTime(), showByYearOff)
|
||||
await refreshStatusV2(date.getTime(), showByYear)
|
||||
|
||||
console.log('Refreshing CDS Graduated')
|
||||
await refreshStatusGraduatedV2(date.getTime(), showByYearOff)
|
||||
await refreshStatusGraduatedV2(date.getTime(), showByYear)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
const refreshUberToRedisV2 = async () => {
|
||||
try {
|
||||
const years = await getStartYearsV2()
|
||||
const mappedYears = years.map(({ studystartdate }) => studystartdate)
|
||||
mappedYears.forEach(async year => {
|
||||
console.log('Refreshing CDS Uber data for date', year)
|
||||
const defaultQuery = { include_old_attainments: 'false', start_date: year }
|
||||
const oldAttainmentsQuery = { include_old_attainments: 'true', start_date: year }
|
||||
await refreshUberV2(defaultQuery)
|
||||
await refreshUberV2(oldAttainmentsQuery)
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
const refreshProtoCProgrammeToRedisV2 = async () => {
|
||||
try {
|
||||
const codes = (await getAllProgrammesV2()).map(p => p.code).filter(code => code.includes('KH'))
|
||||
codes.forEach(async code => {
|
||||
console.log('refreshing code', code)
|
||||
const defaultQuery = { include_old_attainments: 'false', exclude_non_enrolled: 'false', code }
|
||||
const onlyOld = { include_old_attainments: 'true', exclude_non_enrolled: 'false', code }
|
||||
const onlyEnr = { include_old_attainments: 'false', exclude_non_enrolled: 'true', code }
|
||||
const bothToggles = { include_old_attainments: 'true', exclude_non_enrolled: 'true', code }
|
||||
console.log('Refreshing CDS ProtoCProgramme for code ', code)
|
||||
await refreshProtoCProgrammeV2(defaultQuery)
|
||||
await refreshProtoCProgrammeV2(onlyOld)
|
||||
await refreshProtoCProgrammeV2(onlyEnr)
|
||||
await refreshProtoCProgrammeV2(bothToggles)
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
const refreshStatisticsV2 = async () => {
|
||||
await refreshStudyrightAssociationsV2()
|
||||
await refreshOverviewV2()
|
||||
await refreshNonGraduatedStudentsOfOldProgrammesV2()
|
||||
await refreshTeacherLeaderboardV2()
|
||||
await refreshFacultyYearlyStats() // using old data
|
||||
}
|
||||
|
||||
const refreshTrendsV2 = async () => {
|
||||
await refreshProtoCtoRedisV2()
|
||||
await refreshStatusToRedisV2()
|
||||
await refreshUberToRedisV2()
|
||||
await refreshProtoCProgrammeToRedisV2()
|
||||
}
|
||||
|
||||
const startCron = () => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
schedule('0 6 * * *', async () => {
|
||||
await refreshStatisticsV2()
|
||||
await refreshTrendsV2()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const refreshCDS = async () => {
|
||||
const refreshTrends = async () => {
|
||||
await refreshProtoCtoRedis()
|
||||
await refreshStatusToRedis()
|
||||
await refreshUberToRedis()
|
||||
await refreshProtoCProgrammeToRedis()
|
||||
}
|
||||
|
||||
const startCron = () => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
schedule('0 6 * * *', async () => {
|
||||
await refreshStatistics()
|
||||
await refreshTrends()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
startCron,
|
||||
refreshStatisticsV2,
|
||||
refreshCDS,
|
||||
refreshStatistics,
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ const auth = require('./middleware/auth')
|
||||
const teachers = require('./routes/teachers')
|
||||
const providers = require('./routes/providers')
|
||||
const faculties = require('./routes/faculties')
|
||||
const facultyStats = require('./routes/facultyStats')
|
||||
const semesters = require('./routes/semesters')
|
||||
const courseGroups = require('./routes/courseGroups')
|
||||
const mandatoryCourses = require('./routes/mandatorycourses')
|
||||
@@ -56,7 +55,6 @@ module.exports = (app, url) => {
|
||||
app.use(url, programmeModules)
|
||||
app.use(url, diff)
|
||||
app.use(`${url}/faculties`, faculties)
|
||||
app.use(`${url}/facultystats`, auth.roles(['faculties']), facultyStats)
|
||||
app.use(`${url}/updater`, auth.roles(['admin']), updater)
|
||||
app.use(`${url}/teachers`, auth.roles(['teachers']), teachers)
|
||||
app.use(`${url}/users`, auth.roles(['users']), users)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const router = require('express').Router()
|
||||
const { all } = require('../services/organisations')
|
||||
const { faculties } = require('../servicesV2/organisations')
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
const faculties = await all()
|
||||
res.json(faculties)
|
||||
const facultyList = await faculties()
|
||||
console.log('faculties')
|
||||
res.json(facultyList)
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
const router = require('express').Router()
|
||||
const { all } = require('../services/organisations')
|
||||
const { byType: getElementDetailsByType } = require('../services/elementdetails')
|
||||
const { getFacultyYearlyStats } = require('../servicesV2/analyticsService')
|
||||
|
||||
router.get('/faculties', async (req, res) => {
|
||||
const { roles, faculties } = req
|
||||
const allFaculties = await all()
|
||||
res.json(roles.includes('admin') ? allFaculties : allFaculties.filter(({ code }) => faculties.has(code)))
|
||||
})
|
||||
|
||||
router.get('/yearlystats', async (req, res) => {
|
||||
try {
|
||||
const { roles, faculties } = req
|
||||
const facultyYearlyStats = await getFacultyYearlyStats()
|
||||
res.json(roles.includes('admin') ? facultyYearlyStats : facultyYearlyStats.filter(({ id }) => faculties.has(id)))
|
||||
} catch (e) {
|
||||
res.status(400).json({ error: e })
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/programmes', async (req, res) => {
|
||||
try {
|
||||
const { roles } = req
|
||||
const isAdmin = roles.includes('admin')
|
||||
const facultyProgrammes = await getElementDetailsByType(20)
|
||||
const userRightsSet = new Set(req.rights)
|
||||
res.json(facultyProgrammes.filter(({ code }) => userRightsSet.has(code) || isAdmin))
|
||||
} catch (e) {
|
||||
res.status(400).json({ error: e })
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
@@ -9,7 +9,7 @@ const {
|
||||
abort,
|
||||
updateCoursesByCourseCode,
|
||||
} = require('../services/sisUpdaterService')
|
||||
const { refreshStatisticsV2 } = require('../events')
|
||||
const { refreshStatistics } = require('../events')
|
||||
|
||||
router.get('/update/v2/meta', async (req, res) => {
|
||||
const { roles } = req
|
||||
@@ -117,7 +117,7 @@ router.get('/refresh_redis_cache', async (req, res) => {
|
||||
|
||||
router.post('/refresh_statistic_v2', async (req, res) => {
|
||||
try {
|
||||
refreshStatisticsV2()
|
||||
refreshStatistics()
|
||||
res.status(200).json('Refreshing sis statistics')
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
const axios = require('axios')
|
||||
const moment = require('moment')
|
||||
const eachLimit = require('async/eachLimit')
|
||||
const { USERSERVICE_URL, DB_MAX_CRON_CONNECTIONS } = require('../conf-backend')
|
||||
const { Credit, StudyrightElement } = require('../models')
|
||||
const { lruMemoize } = require('../util')
|
||||
|
||||
const client = axios.create({ baseURL: USERSERVICE_URL, headers: { secret: process.env.USERSERVICE_SECRET } })
|
||||
|
||||
const calculateFacultyYearlyStats = async () => {
|
||||
const { data: facultyProgrammes } = await client.get('/faculty_programmes')
|
||||
|
||||
const getFilteredStudentCredits = lruMemoize(
|
||||
studentNumber =>
|
||||
Credit.findAll({
|
||||
where: {
|
||||
student_studentnumber: studentNumber,
|
||||
credittypecode: [4, 10],
|
||||
isStudyModule: false,
|
||||
},
|
||||
}),
|
||||
args => args[0],
|
||||
{
|
||||
max: 10000,
|
||||
}
|
||||
)
|
||||
|
||||
const res = {}
|
||||
const studentSets = {}
|
||||
|
||||
let amountDone = 0
|
||||
// Don't try to make this fast with Promise.all without taking into consideration that
|
||||
// we only have a limited amount of database connections
|
||||
for (const { faculty_code, programme_code } of facultyProgrammes) {
|
||||
if (!res[faculty_code]) {
|
||||
res[faculty_code] = {}
|
||||
studentSets[faculty_code] = {}
|
||||
}
|
||||
if (!res[faculty_code][programme_code]) {
|
||||
res[faculty_code][programme_code] = {}
|
||||
studentSets[faculty_code][programme_code] = {}
|
||||
}
|
||||
|
||||
const facultyStudents = await StudyrightElement.findAll({
|
||||
where: {
|
||||
code: programme_code,
|
||||
},
|
||||
attributes: ['studentnumber'],
|
||||
})
|
||||
|
||||
await eachLimit(facultyStudents, DB_MAX_CRON_CONNECTIONS, async student => {
|
||||
const filteredStudentCredits = await getFilteredStudentCredits(student.studentnumber)
|
||||
|
||||
filteredStudentCredits.forEach(c => {
|
||||
const attainmentYear = moment(c.attainment_date).year()
|
||||
if (!res[faculty_code][attainmentYear] && !res[faculty_code][programme_code][attainmentYear]) {
|
||||
const facultyYearStats = {}
|
||||
facultyYearStats.studentCredits = 0
|
||||
facultyYearStats.coursesPassed = 0
|
||||
facultyYearStats.coursesFailed = 0
|
||||
facultyYearStats.students = 0
|
||||
facultyYearStats.studentArray = []
|
||||
res[faculty_code][programme_code][attainmentYear] = facultyYearStats
|
||||
studentSets[faculty_code][programme_code][attainmentYear] = new Set()
|
||||
}
|
||||
const facultyYearStats = res[faculty_code][programme_code][attainmentYear]
|
||||
const studentSet = studentSets[faculty_code][programme_code][attainmentYear]
|
||||
if (!studentSet.has(c.student_studentnumber)) {
|
||||
facultyYearStats.students++
|
||||
facultyYearStats.studentArray.push(c.student_studentnumber)
|
||||
studentSet.add(c.student_studentnumber)
|
||||
}
|
||||
if (c.credittypecode === 4) {
|
||||
facultyYearStats.studentCredits += c.credits
|
||||
facultyYearStats.coursesPassed += 1
|
||||
} else {
|
||||
facultyYearStats.coursesFailed += 1
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
amountDone += 1
|
||||
console.log(`Faculty programmes done ${amountDone}/${facultyProgrammes.length}`)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
calculateFacultyYearlyStats,
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
const { Provider } = require('../models/index')
|
||||
|
||||
const getAllProviders = async () =>
|
||||
Provider.findAll({
|
||||
attributes: ['providercode', 'name'],
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
getAllProviders,
|
||||
}
|
||||
@@ -88,21 +88,6 @@ const patchThroughput = async data => {
|
||||
}
|
||||
}
|
||||
|
||||
const patchFacultyYearlyStats = async data => {
|
||||
const redisKey = 'FACULTY_YEARLY_STATS'
|
||||
const dataToPatch = Object.entries(data).reduce((acc, [id, data]) => [...acc, { id, data }], [])
|
||||
const setOperationStatus = await redisClient.setAsync(redisKey, JSON.stringify(dataToPatch))
|
||||
if (setOperationStatus !== 'OK') return null
|
||||
return dataToPatch
|
||||
}
|
||||
|
||||
const getFacultyYearlyStats = async () => {
|
||||
const redisKey = 'FACULTY_YEARLY_STATS'
|
||||
const dataFromRedis = await redisClient.getAsync(redisKey)
|
||||
if (!dataFromRedis) return null
|
||||
return JSON.parse(dataFromRedis)
|
||||
}
|
||||
|
||||
const patchNonGraduatedStudents = async data => {
|
||||
const [id, dataToPatch] = Object.entries(data)[0]
|
||||
const redisKey = createRedisKeyForNonGraduatedStudents(id)
|
||||
@@ -135,8 +120,6 @@ module.exports = {
|
||||
getThroughput,
|
||||
setThroughput,
|
||||
patchThroughput,
|
||||
patchFacultyYearlyStats,
|
||||
getFacultyYearlyStats,
|
||||
patchNonGraduatedStudents,
|
||||
getNonGraduatedStudents,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const { Organization } = require('../modelsV2')
|
||||
const Op = Sequelize.Op
|
||||
|
||||
// Have facultyfetching to work like it worked during oodi-db time
|
||||
const facultiesInOodi = [
|
||||
'H10',
|
||||
'H20',
|
||||
'H30',
|
||||
'H40',
|
||||
'H50',
|
||||
'H55',
|
||||
'H57',
|
||||
'H60',
|
||||
'H70',
|
||||
'H74',
|
||||
'H80',
|
||||
'H90',
|
||||
'H92',
|
||||
'H930',
|
||||
'H99',
|
||||
'Y',
|
||||
'Y01',
|
||||
]
|
||||
|
||||
const faculties = () => {
|
||||
return Organization.findAll({
|
||||
where: {
|
||||
code: {
|
||||
[Op.in]: facultiesInOodi,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
faculties,
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { arrayOf, shape, number, func } from 'prop-types'
|
||||
import { calculateTotalPassedCourses, calculateTotalFailedCourses } from '../facultyUtils'
|
||||
import { getTextIn } from '../../../common'
|
||||
import SortableTable from '../../SortableTable'
|
||||
import FacultyStatsGraph from '../FacultyStatsGraph'
|
||||
import useLanguage from '../../LanguagePicker/useLanguage'
|
||||
|
||||
const FacultySelector = ({ faculties, facultyYearlyStats, fromYear, toYear, handleSelect }) => {
|
||||
const { language } = useLanguage()
|
||||
const calculateYearlyStatsForFaculty = facultyCode => {
|
||||
const res = {}
|
||||
const yearData = {
|
||||
studentCredits: 0,
|
||||
coursesPassed: 0,
|
||||
coursesFailed: 0,
|
||||
students: [],
|
||||
}
|
||||
|
||||
const faculty = facultyYearlyStats.find(f => f.id === facultyCode)
|
||||
if (!faculty) return res
|
||||
Object.values(faculty.data).forEach(programmeYearlyStats => {
|
||||
Object.entries(programmeYearlyStats).forEach(([year, stat]) => {
|
||||
if (!res[year]) res[year] = { ...yearData }
|
||||
res[year].studentCredits += stat.studentCredits
|
||||
res[year].coursesPassed += stat.coursesPassed
|
||||
res[year].coursesFailed += stat.coursesFailed
|
||||
res[year].students = [...new Set(res[year].students.concat(stat.studentArray))]
|
||||
})
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
const calculateTotalStatsForFaculty = facultyCode => {
|
||||
const initial = {
|
||||
totalStudentCredits: 0,
|
||||
totalCoursesPassed: 0,
|
||||
totalCoursesFailed: 0,
|
||||
totalStudents: [],
|
||||
}
|
||||
return Object.entries(calculateYearlyStatsForFaculty(facultyCode))
|
||||
.filter(([year]) => year >= fromYear && year <= toYear)
|
||||
.reduce(
|
||||
(res, [, curr]) => ({
|
||||
totalStudentCredits: res.totalStudentCredits + curr.studentCredits,
|
||||
totalCoursesPassed: res.totalCoursesPassed + curr.coursesPassed,
|
||||
totalCoursesFailed: res.totalCoursesFailed + curr.coursesFailed,
|
||||
totalStudents: [...new Set(res.totalStudents.concat(curr.students))],
|
||||
}),
|
||||
{ ...initial }
|
||||
)
|
||||
}
|
||||
|
||||
const totalStats = useMemo(
|
||||
() =>
|
||||
faculties.reduce((res, { code }) => {
|
||||
res[code] = calculateTotalStatsForFaculty(code)
|
||||
return { ...res }
|
||||
}, {}),
|
||||
[fromYear, toYear]
|
||||
)
|
||||
|
||||
const graphData = useMemo(
|
||||
() =>
|
||||
faculties.map(({ code, name }) => ({
|
||||
name: getTextIn(name, language),
|
||||
data: calculateYearlyStatsForFaculty(code),
|
||||
})),
|
||||
[]
|
||||
)
|
||||
|
||||
const headers = [
|
||||
{
|
||||
key: 'name',
|
||||
title: 'name',
|
||||
getRowVal: faculty => getTextIn(faculty.name, language),
|
||||
},
|
||||
{
|
||||
key: 'code',
|
||||
title: 'code',
|
||||
getRowVal: faculty => faculty.code,
|
||||
},
|
||||
{
|
||||
key: 'studentCredits',
|
||||
title: 'Student credits',
|
||||
getRowVal: ({ code }) => Math.round(totalStats[code].totalStudentCredits),
|
||||
},
|
||||
{
|
||||
key: 'coursesPassed',
|
||||
title: 'Courses passed',
|
||||
getRowVal: ({ code }) => calculateTotalPassedCourses(totalStats[code]),
|
||||
getRowContent: ({ code }) => `${calculateTotalPassedCourses(totalStats[code]).toFixed(2)}%`,
|
||||
},
|
||||
{
|
||||
key: 'coursesFailed',
|
||||
title: 'Courses failed',
|
||||
getRowVal: ({ code }) => calculateTotalFailedCourses(totalStats[code]),
|
||||
getRowContent: ({ code }) => `${calculateTotalFailedCourses(totalStats[code]).toFixed(2)}%`,
|
||||
},
|
||||
{
|
||||
key: 'students',
|
||||
title: 'Students',
|
||||
getRowVal: ({ code }) => totalStats[code].totalStudents.length,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<SortableTable
|
||||
columns={headers}
|
||||
getRowKey={faculty => faculty.code}
|
||||
getRowProps={faculty => ({ onClick: () => handleSelect(faculty.code), style: { cursor: 'pointer' } })}
|
||||
data={faculties}
|
||||
/>
|
||||
<FacultyStatsGraph data={graphData} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
FacultySelector.propTypes = {
|
||||
faculties: arrayOf(shape({})).isRequired,
|
||||
facultyYearlyStats: arrayOf(shape({})).isRequired,
|
||||
fromYear: number.isRequired,
|
||||
toYear: number.isRequired,
|
||||
handleSelect: func.isRequired,
|
||||
}
|
||||
|
||||
export default FacultySelector
|
||||
@@ -1,119 +0,0 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { string, arrayOf, shape, number } from 'prop-types'
|
||||
import { Segment, Icon, Header, Item } from 'semantic-ui-react'
|
||||
import { getTextIn } from '../../../common'
|
||||
import { calculateStatsForProgramme, calculateTotalPassedCourses, calculateTotalFailedCourses } from '../facultyUtils'
|
||||
import SortableTable from '../../SortableTable'
|
||||
import FacultyStatsGraph from '../FacultyStatsGraph'
|
||||
import useLanguage from '../../LanguagePicker/useLanguage'
|
||||
|
||||
const ShowProgrammeOverView = ({ code }) => {
|
||||
return (
|
||||
<Item as={Link} title={`Overview of study programme ${code}`} to={`/study-programme/${code}`}>
|
||||
<Icon name="level up alternate" />
|
||||
</Item>
|
||||
)
|
||||
}
|
||||
|
||||
ShowProgrammeOverView.propTypes = {
|
||||
code: string.isRequired,
|
||||
}
|
||||
|
||||
const FacultyStats = ({ facultyProgrammes, selectedFacultyProgrammesStats, fromYear, toYear }) => {
|
||||
const { language } = useLanguage()
|
||||
const totalStats = useMemo(
|
||||
() =>
|
||||
Object.entries(selectedFacultyProgrammesStats).reduce((res, [code, stats]) => {
|
||||
res[code] = calculateStatsForProgramme(stats, fromYear, toYear)
|
||||
return { ...res }
|
||||
}, {}),
|
||||
[fromYear, toYear]
|
||||
)
|
||||
|
||||
const getNameOfProgramme = code => {
|
||||
const foundProgramme = facultyProgrammes.find(p => p.code === code)
|
||||
return foundProgramme ? getTextIn(foundProgramme.name, language) : code
|
||||
}
|
||||
|
||||
const graphData = useMemo(
|
||||
() =>
|
||||
Object.entries(selectedFacultyProgrammesStats).map(([code, data]) => ({ name: getNameOfProgramme(code), data })),
|
||||
[]
|
||||
)
|
||||
|
||||
if (!Object.keys(selectedFacultyProgrammesStats).length) {
|
||||
return <Segment textAlign="center">No data</Segment>
|
||||
}
|
||||
|
||||
/* eslint-disable react/prop-types */
|
||||
const headers = [
|
||||
{
|
||||
key: 'name',
|
||||
title: 'name',
|
||||
getRowVal: ({ code }) => code,
|
||||
getRowContent: ({ code }) => (
|
||||
<div>
|
||||
{' '}
|
||||
{getNameOfProgramme(code)} <ShowProgrammeOverView code={code} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'code',
|
||||
title: 'code',
|
||||
getRowVal: ({ code }) => code,
|
||||
},
|
||||
{
|
||||
key: 'studentCredits',
|
||||
title: 'Student credits',
|
||||
getRowVal: ({ code }) => Math.round(totalStats[code].totalStudentCredits),
|
||||
},
|
||||
{
|
||||
key: 'coursesPassed',
|
||||
title: 'Courses passed',
|
||||
getRowVal: ({ code }) => calculateTotalPassedCourses(totalStats[code]),
|
||||
getRowContent: ({ code }) => `${calculateTotalPassedCourses(totalStats[code]).toFixed(2)}%`,
|
||||
},
|
||||
{
|
||||
key: 'coursesFailed',
|
||||
title: 'Courses failed',
|
||||
getRowVal: ({ code }) => calculateTotalFailedCourses(totalStats[code]),
|
||||
getRowContent: ({ code }) => `${calculateTotalFailedCourses(totalStats[code]).toFixed(2)}%`,
|
||||
},
|
||||
{
|
||||
key: 'students',
|
||||
title: 'Students',
|
||||
getRowVal: ({ code }) => totalStats[code].totalStudents.length,
|
||||
},
|
||||
]
|
||||
/* eslint-enable react/prop-types */
|
||||
|
||||
const data = Object.entries(selectedFacultyProgrammesStats).map(([code, stats]) => ({ code, stats }))
|
||||
const bachelors = data.filter(programme => programme.code.includes('KH'))
|
||||
const masters = data.filter(programme => programme.code.includes('MH'))
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>Bachelor degrees</Header>
|
||||
<SortableTable columns={headers} getRowKey={({ code }) => code} data={bachelors} />
|
||||
<Header>Masters degrees</Header>
|
||||
<SortableTable columns={headers} getRowKey={({ code }) => code} data={masters} />
|
||||
<FacultyStatsGraph data={graphData} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
FacultyStats.propTypes = {
|
||||
facultyProgrammes: arrayOf(shape({})).isRequired,
|
||||
selectedFacultyProgrammesStats: shape({}).isRequired,
|
||||
fromYear: number.isRequired,
|
||||
toYear: number.isRequired,
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ faculties }) => ({
|
||||
facultyProgrammes: faculties.facultyProgrammes,
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps)(FacultyStats)
|
||||
@@ -1,88 +0,0 @@
|
||||
import React, { useState, useMemo } from 'react'
|
||||
import { arrayOf, string, number, shape } from 'prop-types'
|
||||
import { Segment, Button } from 'semantic-ui-react'
|
||||
import Highcharts from 'highcharts/highstock'
|
||||
import ReactHighstock from 'react-highcharts/ReactHighstock'
|
||||
|
||||
const FacultyStatsGraph = ({ data }) => {
|
||||
const [mode, setMode] = useState('studentCredits')
|
||||
const defaultStartTimestamp = new Date(2000, 0, 1).getTime()
|
||||
|
||||
const series = useMemo(
|
||||
() =>
|
||||
data.map(({ name, data: entryData }) => ({
|
||||
name,
|
||||
data: Object.entries(entryData).map(([year, stats]) => ({
|
||||
x: new Date(parseInt(year, 10), 0, 1, 0, 0, 0, 0).getTime(),
|
||||
y: Math.round(
|
||||
['coursesPassed', 'coursesFailed'].includes(mode)
|
||||
? (stats[mode] / (stats.coursesPassed + stats.coursesFailed)) * 100
|
||||
: stats[mode].length || stats[mode]
|
||||
),
|
||||
})),
|
||||
})),
|
||||
[data, mode]
|
||||
)
|
||||
|
||||
const { min, max } = useMemo(
|
||||
() =>
|
||||
series.reduce(
|
||||
(res, { data }) => {
|
||||
data.forEach(({ x }) => {
|
||||
res.min = Math.min(res.min, x)
|
||||
res.max = Math.max(res.max, x)
|
||||
})
|
||||
return res
|
||||
},
|
||||
{ min: Infinity, max: -Infinity }
|
||||
),
|
||||
[series]
|
||||
)
|
||||
|
||||
const options = {
|
||||
findNearestPointBy: 'xy',
|
||||
series,
|
||||
xAxis: {
|
||||
min: Math.max(min, defaultStartTimestamp),
|
||||
max,
|
||||
ordinal: false,
|
||||
},
|
||||
}
|
||||
|
||||
const handleClick = (_, { name }) => setMode(name)
|
||||
|
||||
return (
|
||||
<Segment>
|
||||
<ReactHighstock highcharts={Highcharts} constructorType="stockChart" config={options} />
|
||||
<Button active={mode === 'studentCredits'} onClick={handleClick} name="studentCredits">
|
||||
Total credits
|
||||
</Button>
|
||||
<Button active={mode === 'coursesPassed'} onClick={handleClick} name="coursesPassed">
|
||||
% of courses passed
|
||||
</Button>
|
||||
<Button active={mode === 'coursesFailed'} onClick={handleClick} name="coursesFailed">
|
||||
% of courses failed
|
||||
</Button>
|
||||
<Button active={mode === 'students'} onClick={handleClick} name="students">
|
||||
Amount of students
|
||||
</Button>
|
||||
</Segment>
|
||||
)
|
||||
}
|
||||
|
||||
FacultyStatsGraph.propTypes = {
|
||||
data: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
data: shape({
|
||||
year: shape({
|
||||
studentCredits: number,
|
||||
coursesPassed: number,
|
||||
coursesFailed: number,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
).isRequired,
|
||||
}
|
||||
|
||||
export default FacultyStatsGraph
|
||||
@@ -1,30 +0,0 @@
|
||||
export const calculateStatsForProgramme = (stats, fromYear, toYear) => {
|
||||
const res = {
|
||||
totalStudentCredits: 0,
|
||||
totalCoursesPassed: 0,
|
||||
totalCoursesFailed: 0,
|
||||
totalStudents: [],
|
||||
}
|
||||
|
||||
Object.entries(stats)
|
||||
.filter(([year]) => year >= fromYear && year <= toYear)
|
||||
.forEach(([, stat]) => {
|
||||
const { studentCredits, coursesPassed, coursesFailed, studentArray } = stat
|
||||
res.totalStudentCredits += studentCredits
|
||||
res.totalCoursesPassed += coursesPassed
|
||||
res.totalCoursesFailed += coursesFailed
|
||||
res.totalStudents = [...new Set(res.totalStudents.concat(studentArray))]
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
export const calculateTotalPassedCourses = ({ totalCoursesPassed, totalCoursesFailed }) => {
|
||||
const ratio = (totalCoursesPassed / (totalCoursesPassed + totalCoursesFailed)) * 100
|
||||
return Number.isNaN(ratio) ? 0 : ratio
|
||||
}
|
||||
|
||||
export const calculateTotalFailedCourses = ({ totalCoursesPassed, totalCoursesFailed }) => {
|
||||
const totalPassedRatio = calculateTotalPassedCourses({ totalCoursesPassed, totalCoursesFailed })
|
||||
if (totalPassedRatio === 0) return 0
|
||||
return 100 - totalPassedRatio
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { Header, Segment, Form } from 'semantic-ui-react'
|
||||
import { uniq } from 'lodash'
|
||||
import { arrayOf, shape, func, bool } from 'prop-types'
|
||||
import { getTextIn } from '../../common'
|
||||
import { useTitle } from '../../common/hooks'
|
||||
import { getUserFaculties, getFacultiesYearlyStats, getFacultyProgrammes } from '../../redux/faculties'
|
||||
import YearFilter from '../CourseStatistics/SearchForm/YearFilter'
|
||||
import FacultySelector from './FacultySelector'
|
||||
import FacultyStats from './FacultyStats'
|
||||
import useLanguage from '../LanguagePicker/useLanguage'
|
||||
|
||||
const Faculty = ({
|
||||
getUserFaculties,
|
||||
getFacultiesYearlyStats,
|
||||
getFacultyProgrammes,
|
||||
faculties,
|
||||
pending,
|
||||
error,
|
||||
facultyYearlyStats,
|
||||
history,
|
||||
match,
|
||||
}) => {
|
||||
const { language } = useLanguage()
|
||||
const [selectedFaculty, setSelectedFaculty] = useState(null)
|
||||
const [fromYear, setFromYear] = useState(-1)
|
||||
const [toYear, setToYear] = useState(-1)
|
||||
const [years, setYears] = useState([])
|
||||
const [initialized, setInitialized] = useState(false)
|
||||
useTitle('Faculties')
|
||||
|
||||
const facultyCodes = faculties.map(({ code }) => code)
|
||||
const selectedFacultyProgrammesStats = facultyYearlyStats.find(({ id }) => id === selectedFaculty)
|
||||
const hasLoaded = faculties.length > 0 && facultyYearlyStats.length > 0
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedFaculty) {
|
||||
history.push(`/faculties/${selectedFaculty}`)
|
||||
}
|
||||
}, [selectedFaculty])
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
params: { facultyid },
|
||||
} = match
|
||||
if (facultyid) setSelectedFaculty(facultyid)
|
||||
else setSelectedFaculty(null)
|
||||
}, [history.location.pathname])
|
||||
|
||||
useEffect(() => {
|
||||
getUserFaculties()
|
||||
getFacultiesYearlyStats()
|
||||
getFacultyProgrammes()
|
||||
setInitialized(true)
|
||||
}, [])
|
||||
|
||||
const getYearFilterData = () => {
|
||||
const filterYears = uniq(
|
||||
facultyYearlyStats
|
||||
.filter(f => facultyCodes.includes(f.id))
|
||||
.map(({ data }) => Object.values(data).reduce((res, curr) => [...res, ...Object.keys(curr)], []))
|
||||
.reduce((acc, curr) => [...acc, ...curr], [])
|
||||
)
|
||||
.sort((a, b) => b - a)
|
||||
.map(year => parseInt(year, 10))
|
||||
|
||||
return {
|
||||
fromYear: filterYears[filterYears.length - 1],
|
||||
toYear: filterYears[0],
|
||||
years: filterYears.map(y => ({ key: y, text: y, value: y })),
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (faculties.length && facultyYearlyStats.length) {
|
||||
const { fromYear: newFromYear, toYear: newToYear, years: newYears } = getYearFilterData()
|
||||
if (fromYear < newFromYear || fromYear > newToYear) setFromYear(Math.max(newFromYear, 2000))
|
||||
if (toYear < newFromYear || toYear > newToYear) setToYear(newToYear)
|
||||
setYears(newYears)
|
||||
}
|
||||
}, [faculties, facultyYearlyStats, selectedFaculty])
|
||||
|
||||
const handleYearChange = (e, target) => {
|
||||
const { name, value } = target
|
||||
if (name === 'fromYear') setFromYear(value)
|
||||
else setToYear(value)
|
||||
}
|
||||
|
||||
const getTitle = () =>
|
||||
selectedFaculty && hasLoaded
|
||||
? getTextIn(faculties.find(({ code }) => code === selectedFaculty).name, language)
|
||||
: 'Faculties'
|
||||
|
||||
if (!(faculties.length || pending) && initialized) {
|
||||
return !error ? (
|
||||
<Header textAlign="center" content="No access to any faculties!" as="h1" />
|
||||
) : (
|
||||
<Header textAlign="center" content="Not authorized for faculties!" as="h1" />
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="segmentContainer">
|
||||
<Header className="segmentTitle" size="large">
|
||||
{hasLoaded && getTitle()}
|
||||
</Header>
|
||||
<Segment className="contentSegment" loading={!hasLoaded}>
|
||||
{!hasLoaded && null}
|
||||
{hasLoaded && (
|
||||
<>
|
||||
<Segment>
|
||||
<Form>
|
||||
<Header content="Filter by time range" as="h4" />
|
||||
<YearFilter fromYear={fromYear} toYear={toYear} years={years} handleChange={handleYearChange} />
|
||||
</Form>
|
||||
</Segment>
|
||||
{!selectedFaculty ? (
|
||||
<FacultySelector
|
||||
faculties={faculties}
|
||||
facultyYearlyStats={facultyYearlyStats}
|
||||
fromYear={fromYear}
|
||||
toYear={toYear}
|
||||
handleSelect={setSelectedFaculty}
|
||||
/>
|
||||
) : (
|
||||
<FacultyStats
|
||||
selectedFacultyProgrammesStats={
|
||||
selectedFacultyProgrammesStats ? selectedFacultyProgrammesStats.data : {}
|
||||
}
|
||||
history={history}
|
||||
fromYear={fromYear}
|
||||
toYear={toYear}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Segment>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Faculty.propTypes = {
|
||||
getUserFaculties: func.isRequired,
|
||||
getFacultiesYearlyStats: func.isRequired,
|
||||
getFacultyProgrammes: func.isRequired,
|
||||
faculties: arrayOf(shape({})).isRequired,
|
||||
facultyYearlyStats: arrayOf(shape({})).isRequired,
|
||||
history: shape({}).isRequired,
|
||||
match: shape({}).isRequired,
|
||||
pending: bool.isRequired,
|
||||
error: bool.isRequired,
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ faculties }) => ({
|
||||
faculties: faculties.data,
|
||||
facultyYearlyStats: faculties.yearlyStats,
|
||||
pending: faculties.userFacultiesPending,
|
||||
error: faculties.userFacultiesError,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = {
|
||||
getUserFaculties,
|
||||
getFacultiesYearlyStats,
|
||||
getFacultyProgrammes,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Faculty))
|
||||
@@ -25,7 +25,6 @@ const allNavigationItems = {
|
||||
teachers: { path: '/teachers', key: 'teachers', label: 'Teachers', reqRights: ['teachers'] },
|
||||
users: { path: '/users', key: 'users', label: 'Users', reqRights: ['users'] },
|
||||
trends: { path: '/trends', key: 'trends', label: 'Trends' },
|
||||
faculty: { path: '/faculties', key: 'faculty', label: 'Faculty', reqRights: ['faculties'] },
|
||||
updater: { path: '/updater', key: 'updater', label: 'Updater', reqRights: ['dev', 'admin'] },
|
||||
sandbox: { path: '/sandbox', key: 'sandbox', label: 'Sandbox', reqRights: ['dev'] },
|
||||
feedback: { path: '/feedback', key: 'feedback', label: 'Give feedback' },
|
||||
|
||||
@@ -34,7 +34,6 @@ const StudyProgramme = React.lazy(() => retry(() => import('../StudyProgramme'))
|
||||
const Teachers = React.lazy(() => retry(() => import('../Teachers')))
|
||||
const Sandbox = React.lazy(() => retry(() => import('../Sandbox')))
|
||||
const Feedback = React.lazy(() => retry(() => import('../Feedback')))
|
||||
const Faculty = React.lazy(() => retry(() => import('../Faculty')))
|
||||
const CoursePopulation = React.lazy(() => retry(() => import('../CoursePopulation')))
|
||||
const CustomPopulation = React.lazy(() => retry(() => import('../CustomPopulation')))
|
||||
const Updater = React.lazy(() => retry(() => import('../Updater')))
|
||||
@@ -45,7 +44,6 @@ const routes = {
|
||||
courseStatistics: '/coursestatistics',
|
||||
teachers: '/teachers/:teacherid?',
|
||||
users: '/users/:userid?',
|
||||
faculty: '/faculties/:facultyid?',
|
||||
sandbox: '/sandbox',
|
||||
feedback: '/feedback',
|
||||
coursepopulation: '/coursepopulation',
|
||||
@@ -63,7 +61,6 @@ const Routes = () => (
|
||||
<Route exact path="/study-programme/:studyProgrammeId/course-group/:courseGroupId" component={StudyProgramme} />
|
||||
<Route exact path={routes.students} component={StudentStatistics} />
|
||||
<Route exact path={routes.courseStatistics} component={CourseStatistics} />
|
||||
<Route exact path={routes.faculty} component={Faculty} />
|
||||
<Route exact path={routes.users} component={EnableUsers} />
|
||||
<Route exact path={routes.teachers} component={Teachers} />
|
||||
<Route exact path={routes.sandbox} component={Sandbox} />
|
||||
|
||||
@@ -6,24 +6,6 @@ export const getFaculties = () => {
|
||||
return callController(route, prefix)
|
||||
}
|
||||
|
||||
export const getUserFaculties = () => {
|
||||
const route = '/facultystats/faculties'
|
||||
const prefix = 'GET_USER_FACULTIES_'
|
||||
return callController(route, prefix)
|
||||
}
|
||||
|
||||
export const getFacultiesYearlyStats = () => {
|
||||
const route = '/facultystats/yearlystats'
|
||||
const prefix = 'GET_FACULTIES_YEARLY_STATS_'
|
||||
return callController(route, prefix)
|
||||
}
|
||||
|
||||
export const getFacultyProgrammes = () => {
|
||||
const route = '/facultystats/programmes'
|
||||
const prefix = 'GET_FACULTY_PROGRAMMES_'
|
||||
return callController(route, prefix)
|
||||
}
|
||||
|
||||
const reducer = (
|
||||
state = {
|
||||
data: [],
|
||||
@@ -56,57 +38,6 @@ const reducer = (
|
||||
pending: false,
|
||||
error: true,
|
||||
}
|
||||
case 'GET_USER_FACULTIES_ATTEMPT':
|
||||
return {
|
||||
...state,
|
||||
userFacultiesPending: true,
|
||||
userFacultiesError: false,
|
||||
}
|
||||
case 'GET_USER_FACULTIES_FAILURE':
|
||||
return {
|
||||
...state,
|
||||
userFacultiesPending: false,
|
||||
userFacultiesError: true,
|
||||
}
|
||||
case 'GET_USER_FACULTIES_SUCCESS':
|
||||
return {
|
||||
...state,
|
||||
data: action.response,
|
||||
userFacultiesPending: false,
|
||||
userFacultiesError: false,
|
||||
}
|
||||
case 'GET_FACULTIES_YEARLY_STATS_FAILURE':
|
||||
return {
|
||||
...state,
|
||||
pending: false,
|
||||
error: true,
|
||||
}
|
||||
case 'GET_FACULTIES_YEARLY_STATS_SUCCESS':
|
||||
return {
|
||||
...state,
|
||||
yearlyStats: action.response,
|
||||
pending: false,
|
||||
error: true,
|
||||
}
|
||||
case 'GET_FACULTY_PROGRAMMES_ATTEMPT':
|
||||
return {
|
||||
...state,
|
||||
pending: true,
|
||||
error: false,
|
||||
}
|
||||
case 'GET_FACULTY_PROGRAMMES_FAILURE':
|
||||
return {
|
||||
...state,
|
||||
pending: false,
|
||||
error: true,
|
||||
}
|
||||
case 'GET_FACULTY_PROGRAMMES_SUCCESS':
|
||||
return {
|
||||
...state,
|
||||
facultyProgrammes: action.response,
|
||||
pending: false,
|
||||
error: false,
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user