Compare commits

...
16 Commits
Author SHA1 Message Date
Roni Lindholm 6e4151bd14 Fix study programme graduation programmes 2022-07-04 14:13:07 +03:00
Roni Lindholm 939fd69e6d Update tests? 2022-07-04 13:45:25 +03:00
Roni Lindholm 2786fbfc0f Fix calculation of non degree and non major credits in study programme
Minor bug fixes.
2022-07-04 12:26:40 +03:00
SNurmivaara 8ec998044b Add barebones faculty page only visible to admins 2022-06-30 09:28:18 +03:00
Laura Sinikallio 29c914069e Merge branch 'master' of github.com:UniversityOfHelsinkiCS/oodikone 2022-06-29 15:18:14 +03:00
Laura Sinikallio 24033598f9 Edit load indicator [custom populations] 2022-06-29 15:17:57 +03:00
Roni Lindholm 94584e9faa Fix missing action in transfer filter 2022-06-29 14:14:59 +03:00
Roni Lindholm 953cf38ffd Display student count filter on study guidance courses of population
Fix #3441 as the filter was hidden but still filtered courses with low
enough count of students.
2022-06-29 13:54:20 +03:00
Laura Sinikallio 0b16c5eab2 Hide future semesters/years from course statistics 2022-06-29 13:22:27 +03:00
Roni Lindholm 58de142232 Use study right valid start date as fallback in study right element
Use old logic and additionally use fallback value when issue
described in #3387 occurs.
2022-06-29 12:54:03 +03:00
Roni Lindholm a88e0c0362 Happy birthday? :feels_birthday_man: 2022-06-29 12:10:01 +03:00
Roni Lindholm 7cb5f86937 Add study plan filter to study guidance groups
Add also possibility to set credit date filter to study start when
filtering credits by study plan.
2022-06-29 09:52:12 +03:00
Roni Lindholm e45e4ec239 Fix study right end date when graduated
Use graduation date as it is or study right end date - 1 day, as the
date range from Sisu is a semi-open interval.
2022-06-21 14:57:14 +03:00
Roni Lindholm 14308236c6 Add missing attainment date to population credits 2022-06-20 12:45:50 +03:00
Roni Lindholm d4254cc60f Fix wrong default start date for population credit graph 2022-06-20 12:19:21 +03:00
Roni Lindholm 4fcb3fcd07 Fix credit graph start on study guidance groups 2022-06-20 09:36:42 +03:00
24 changed files with 310 additions and 99 deletions
+3 -3
View File
@@ -153,7 +153,7 @@ describe('Population Statistics', () => {
it('Age filter works', () => {
runTestStepWithPreAndPostParts('Age', () => {
testRangeFilter('Age-filter-card', 20, 40, 49)
testRangeFilter('Age-filter-card', 20, 40, 47)
})
})
@@ -217,7 +217,7 @@ describe('Population Statistics', () => {
runTestStepWithPreAndPostParts('Age', () => {
const getCard = () => cy.cs('GraduatedFromProgramme-filter-card')
getCard().cs('option-graduated-true').click()
testRangeFilter('Age-filter-card', 20, 30, 11)
testRangeFilter('Age-filter-card', 20, 30, 1)
getCard().cs('option-all').click()
})
})
@@ -274,7 +274,7 @@ describe('Course Statistics', () => {
it('Age filter works', () => {
runTestStepWithPreAndPostParts('Age', () => {
testRangeFilter('Age-filter-card', 20, 40, 35)
testRangeFilter('Age-filter-card', 20, 40, 33)
})
})
@@ -61,10 +61,10 @@ describe('Studyprogramme overview', () => {
// [Year, Total, Major credits, Non-major credits, Non-degree credits, Transferred credits]
...years.map(year => [year, 0, 0, 0, 0, 0]),
[2021, 0, 0, 0, 0, 0],
[2020, 4928, 548, 4213, 0, 167],
[2019, 7737, 1644, 5744, 0, 349],
[2018, 6730, 1820, 4633, 5, 272],
[2017, 2900, 788, 1724, 0, 388],
[2020, 5860, 5591, 102, 0, 167],
[2019, 8696, 7737, 587, 23, 349],
[2018, 7818, 6584, 857, 105, 272],
[2017, 3085, 2492, 158, 47, 388],
]
cy.checkTableStats(tableContents, 'CreditsProducedByTheStudyprogramme')
@@ -150,9 +150,9 @@ describe('Studyprogramme overview', () => {
...years.map(year => [year, 0, 0, 0, 0]),
['2021 - 2022', 0, 0, 0, 0],
['2020 - 2021', 0, 0, 0, 0],
['2019 - 2020', 338.5, 0, 221, 110, 7.5],
['2018 - 2019', 173, 0, 74, 99, 0],
['2017 - 2018', 42, 0, 35, 7, 0],
['2019 - 2020', 338.5, 177, 154, 0, 7.5],
['2018 - 2019', 173, 0, 173, 0, 0],
['2017 - 2018', 42, 0, 42, 0, 0],
]
cy.checkTableStats(creditTableContents, 'CreditsProducedByTheStudyprogramme')
@@ -171,8 +171,8 @@ describe('Studyprogramme overview', () => {
.should('contain', 'Major students credits')
.should('contain', 'Non-major students credits')
.should('contain', 'Transferred credits')
.should('contain', 788)
.should('contain', 1820)
.should('contain', 857)
.should('contain', 587)
cy.get('[data-cy=Graph-GraduatedAndThesisWritersOfTheProgramme')
.should('contain', 'Graduated students')
+10 -1
View File
@@ -108,6 +108,11 @@ const creditsForCourses = async (codes, anonymizationSalt, unification) => {
{
model: Semester,
attributes: ['semestercode', 'name', 'yearcode', 'yearname'],
where: {
startdate: {
[Op.lte]: new Date(),
},
},
},
],
where: {
@@ -173,6 +178,11 @@ const enrollmentsForCourses = async (codes, anonymizationSalt, unification) => {
{
model: Semester,
attributes: ['semestercode', 'name', 'yearcode', 'yearname'],
where: {
startdate: {
[Op.lte]: new Date(),
},
},
},
],
where: {
@@ -187,7 +197,6 @@ const enrollmentsForCourses = async (codes, anonymizationSalt, unification) => {
[Op.or]: [{ is_open }, { is_open: null }],
},
})
return enrollments.map(enrollment => parseEnrollment(enrollment, anonymizationSalt))
}
@@ -929,6 +929,7 @@ const findCourses = async (studentnumbers, beforeDate, courses = [], studentCoun
ARRAY_AGG(JSON_BUILD_OBJECT(
'grade', grade,
'student_studentnumber', student_studentnumber,
'attainment_date', attainment_date,
'credittypecode', credittypecode,
'course_code', course_code
)) AS data
+54 -10
View File
@@ -257,7 +257,7 @@ const followingStudyrights = async (since, programmes, studentnumbers) =>
include: [
{
model: ElementDetail,
attributes: ['name'],
attributes: ['name', 'code', 'type'],
},
],
attributes: ['code'],
@@ -293,7 +293,7 @@ const previousStudyrights = async (programmes, studentnumbers) =>
include: [
{
model: ElementDetail,
attributes: ['name'],
attributes: ['name', 'code', 'type'],
},
],
attributes: ['code'],
@@ -369,7 +369,33 @@ const getProgrammesStudyrights = async studyprogramme =>
})
).map(formatStudyright)
const getCreditsForStudyProgramme = async (provider, since) => {
const getStudyRights = async students =>
(
await Studyright.findAll({
attributes: ['studyrightid', 'studystartdate', 'enddate', 'graduated', 'prioritycode', 'extentcode'],
where: {
studentStudentnumber: students,
},
include: [
{
model: StudyrightElement,
include: {
model: ElementDetail,
where: {
type: 20,
},
},
},
{
model: Student,
attributes: ['studentnumber'],
required: true,
},
],
})
).map(formatStudyright)
const getCreditsForStudyProgramme = async (codes, since) => {
const res = await Credit.findAll({
attributes: ['id', 'course_code', 'credits', 'attainment_date', 'student_studentnumber'],
include: {
@@ -378,13 +404,7 @@ const getCreditsForStudyProgramme = async (provider, since) => {
required: true,
where: {
is_study_module: false,
},
include: {
model: Organization,
required: true,
where: {
code: provider,
},
code: codes,
},
},
where: {
@@ -402,6 +422,28 @@ const getCreditsForStudyProgramme = async (provider, since) => {
return res
}
const getCourseCodesForStudyProgramme = async provider => {
const coursesByProvider = await Course.findAll({
attributes: ['code', 'substitutions'],
where: {
is_study_module: false,
},
include: {
model: Organization,
required: true,
where: {
code: provider,
},
},
})
const coursesWithOpenUniSubstitutions = coursesByProvider.map(({ code, substitutions }) => {
if (!substitutions || !substitutions.length) return [code]
const alternatives = [`AY-${code}`, `AY${code}`, `A-${code}`]
return [code].concat(substitutions.filter(sub => alternatives.includes(sub)))
})
return coursesWithOpenUniSubstitutions.flat()
}
const getAllProgrammeCourses = async providerCode => {
const res = await Course.findAll({
attributes: ['code', 'substitutions'],
@@ -676,4 +718,6 @@ module.exports = {
getStudentsWithoutStudyrightForProgrammeCourses,
getOtherStudentsForProgrammeCourses,
getAllProgrammeCourses,
getStudyRights,
getCourseCodesForStudyProgramme,
}
@@ -5,16 +5,18 @@ const {
getStatsBasis,
defineYear,
isMajorStudentCredit,
isNonMajorCredit,
getYearsArray,
getStartDate,
isSpecialGroupCredit,
tableTitles,
} = require('./studyprogrammeHelpers')
const {
getProgrammesStudyrights,
getStudyRights,
getCreditsForStudyProgramme,
getTransferredCredits,
allTransfers,
getCourseCodesForStudyProgramme,
} = require('./studyprogramme')
// Fetches all the credits for the studyprogramme and divides them into major-students and non-major students credits
@@ -22,36 +24,38 @@ const {
// If special groups are excluded, the transfer students are filtered away from the major-students as well
const getRegularCreditStats = async ({ studyprogramme, since, years, isAcademicYear, includeAllSpecials }) => {
const providercode = mapToProviders([studyprogramme])[0]
let studyrights = await getProgrammesStudyrights(studyprogramme, since)
const courses = await getCourseCodesForStudyProgramme(providercode)
const credits = await getCreditsForStudyProgramme(courses, since)
const students = [...new Set(credits.map(({ student_studentnumber }) => student_studentnumber))]
//let studyrights = await getProgrammesStudyrights(studyprogramme, since)
let studyrights = await getStudyRights(students, since)
const transfers = (await allTransfers(studyprogramme, since)).map(t => t.studyrightid)
if (!includeAllSpecials) {
studyrights.filter(s => !transfers.includes(s.studyrightid))
studyrights = studyrights.filter(s => !transfers.includes(s.studyrightid))
}
const credits = await getCreditsForStudyProgramme(providercode, since)
let majors = getStatsBasis(years)
let nonMajors = getStatsBasis(years)
let nonDegree = getStatsBasis(years)
credits.forEach(({ student_studentnumber, attainment_date, credits }) => {
const studyright = studyrights.find(studyright => studyright.studentnumber == student_studentnumber)
const studentStudyrights = studyrights.filter(studyright => studyright.studentnumber === student_studentnumber)
const attainmentYear = defineYear(attainment_date, isAcademicYear)
if (!includeAllSpecials && isSpecialGroupCredit(studyright, attainment_date, transfers)) {
if (!includeAllSpecials && isSpecialGroupCredit(studentStudyrights, attainment_date, transfers)) {
return
}
if (isMajorStudentCredit(studyright, attainment_date)) {
if (isMajorStudentCredit(studentStudyrights, attainment_date, studyprogramme)) {
majors.graphStats[indexOf(years, attainmentYear)] += credits || 0
majors.tableStats[attainmentYear] += credits || 0
} else if (!studyright) {
nonDegree.graphStats[indexOf(years, attainmentYear)] += credits || 0
nonDegree.tableStats[attainmentYear] += credits || 0
} else {
} else if (isNonMajorCredit(studentStudyrights, attainment_date, studyprogramme)) {
nonMajors.graphStats[indexOf(years, attainmentYear)] += credits || 0
nonMajors.tableStats[attainmentYear] += credits || 0
} else {
nonDegree.graphStats[indexOf(years, attainmentYear)] += credits || 0
nonDegree.tableStats[attainmentYear] += credits || 0
}
})
@@ -33,6 +33,15 @@ const defineStartDate = (studyright_elements, studystartdate) => {
return studystartdate
}
const resolveStudyRightCode = studyright_elements => {
if (!studyright_elements) return null
const studyRightElement = studyright_elements
.filter(sre => sre.element_detail.type === 20)
.sort((a, b) => new Date(b.startdate) - new Date(a.startdate))[0]
if (studyRightElement) return studyRightElement.code
return null
}
const formatStudyright = studyright => {
const {
studyrightid,
@@ -57,7 +66,7 @@ const formatStudyright = studyright => {
prioritycode,
extentcode,
studentnumber: student.studentnumber,
code: studyright_elements?.length ? studyright_elements[0].code : null,
code: resolveStudyRightCode(studyright_elements),
name:
studyright_elements?.length && studyright_elements[0].element_detail && studyright_elements[0].element_detail.name
? studyright_elements[0].element_detail.name
@@ -119,21 +128,36 @@ const getStatsBasis = years => {
}
}
const isMajorStudentCredit = (studyright, attainment_date) =>
studyright &&
(studyright.prioritycode === 1 || studyright.prioritycode === 30) && // Is studyright state = MAIN or state = GRADUATED
studyright.studystartdate && // The student has started studying in the programme
studyright.studystartdate <= attainment_date && // Has the credit been attained after studying in the programme started
studyright.enddate >= attainment_date // Has the credit been attained before the studyright ended
const isMajorStudentCredit = (studyrights, attainment_date, code) =>
studyrights.some(studyright => {
if (!studyright) return false
if (studyright.code !== code) return false
if (!studyright.graduated) return new Date(attainment_date) >= new Date(studyright.studystartdate)
return (
new Date(attainment_date) >= new Date(studyright.studystartdate) &&
new Date(attainment_date) <= new Date(studyright.enddate)
)
})
const isSpecialGroupCredit = (studyright, attainment_date, transfers) => {
if (!studyright) return true // If there is no studyright matching the credit, is not a major student credit
if (studyright.studystartdate > attainment_date) return true // Credits before the studyright started are not major student credits
if (studyright.enddate && attainment_date > studyright.enddate) return true // Credits after studyright are not major student credits
if ([7, 9, 16, 34, 33, 99, 14, 13].includes(studyright.extentcode)) return true // Excludes non-degree studyrights and exchange students
if (transfers.includes(studyright.studyrightid)) return true // Excludes both transfers in and out of the programme
return false
}
const isNonMajorCredit = (studyrights, attainment_date) =>
studyrights.some(studyright => {
if (!studyright) return false
if (!studyright.graduated) return new Date(attainment_date) >= new Date(studyright.studystartdate)
return (
new Date(attainment_date) >= new Date(studyright.studystartdate) &&
new Date(attainment_date) <= new Date(studyright.enddate)
)
})
const isSpecialGroupCredit = (studyrights, attainment_date, transfers) =>
studyrights.some(studyright => {
if (!studyright) return true // If there is no studyright matching the credit, is not a major student credit
if (studyright.studystartdate > attainment_date) return true // Credits before the studyright started are not major student credits
if (studyright.enddate && attainment_date > studyright.enddate) return true // Credits after studyright are not major student credits
if ([7, 9, 16, 34, 33, 99, 14, 13].includes(studyright.extentcode)) return true // Excludes non-degree studyrights and exchange students
if (transfers.includes(studyright.studyrightid)) return true // Excludes both transfers in and out of the programme
return false
})
const getMedian = values => {
if (values.length === 0) return 0
@@ -410,4 +434,5 @@ module.exports = {
getCreditThresholds,
tableTitles,
getCreditProgressTableTitles,
isNonMajorCredit,
}
@@ -9,7 +9,6 @@ import _ from 'lodash/fp'
import boost from 'highcharts/modules/boost'
import ReactHighstock from 'react-highcharts/ReactHighstock'
import './creditAccumulationGraphHC.css'
import { useSelector } from 'react-redux'
import CreditGraphTooltip from '../CreditGraphTooltip'
import { reformatDate, getTextIn, getStudyRightElementTargetDates } from '../../common'
import useLanguage from '../LanguagePicker/useLanguage'
@@ -132,14 +131,21 @@ const filterCoursesByStudyPlan = (plan, courses) =>
const filterCoursesByDate = (courses, date) => courses.filter(c => moment(c.date).isSameOrAfter(moment(date)))
const filterCourses = (student, singleStudent, byStudyPlanOfCode, cutStudyPlanCredits, startDate) => {
const filterCourses = (
student,
singleStudent,
byStudyPlanOfCode,
cutStudyPlanCredits,
startDate,
customStudyStartYear
) => {
if (byStudyPlanOfCode && cutStudyPlanCredits)
return filterCoursesByDate(
filterCoursesByStudyPlan(
student.studyplans.find(p => p.programme_code === byStudyPlanOfCode),
student.courses
),
student.studyrightStart
customStudyStartYear || student.studyrightStart
)
if (byStudyPlanOfCode)
return filterCoursesByStudyPlan(
@@ -273,7 +279,8 @@ const createStudentCreditLines = (
studyRightId,
studyPlanFilterIsActive,
cutStudyPlanCredits,
programmeCode
programmeCode,
customStudyStartYear
) =>
students.map(student => {
const { studyrightStart } = student
@@ -285,7 +292,15 @@ const createStudentCreditLines = (
const studyPlanProgrammeCode = singleStudent ? code : studyPlanFilterIsActive && programmeCode
const { points } = _.flow(
() => filterCourses(student, singleStudent, studyPlanProgrammeCode, cutStudyPlanCredits, startDate),
() =>
filterCourses(
student,
singleStudent,
studyPlanProgrammeCode,
cutStudyPlanCredits,
startDate,
customStudyStartYear
),
courses => [...courses].filter(({ date }) => new Date(date) <= new Date()),
sortCoursesByDate,
courses => courses.reduce(reduceCreditsToPoints, { credits: 0, points: [], singleStudent })
@@ -320,13 +335,13 @@ const CreditAccumulationGraphHighCharts = ({
studyRightId,
programmeCode,
customPopulation = false,
studyPlanFilterIsActive,
customStudyStartYear,
}) => {
const history = useHistory()
const chartRef = useRef()
const language = useLanguage()
const { active: studyPlanFilterIsActive } = useSelector(
state => state.filters?.views?.PopulationStatistics?.hops?.options ?? {}
)
const [graphHeight, setGraphHeight] = useState(700)
const [cutStudyPlanCredits, setCutStudyPlanCredits] = useState(false)
const selectedStudyRight =
@@ -343,9 +358,19 @@ const CreditAccumulationGraphHighCharts = ({
studyRightId,
studyPlanFilterIsActive,
cutStudyPlanCredits,
programmeCode
programmeCode,
customStudyStartYear
),
[students, singleStudent, startDate, studyRightId, studyPlanFilterIsActive, programmeCode, cutStudyPlanCredits]
[
students,
singleStudent,
startDate,
studyRightId,
studyPlanFilterIsActive,
programmeCode,
cutStudyPlanCredits,
customStudyStartYear,
]
)
if (singleStudent) {
@@ -372,6 +397,13 @@ const CreditAccumulationGraphHighCharts = ({
seriesData.push(createGoalSeries(starting, ending, filteredAbsences))
}
const getStudyRightStart = () => {
if (customStudyStartYear) return new Date(customStudyStartYear).getTime()
const studyRightStartFromStudent = new Date(students[0]?.studyrightStart ?? new Date(null))
if (studyRightStartFromStudent.getFullYear() < 2000)
return Math.min(..._.flatten(students.map(({ courses }) => courses.map(({ date }) => new Date(date).getTime()))))
return studyRightStartFromStudent.getTime()
}
const getGraphStart = () => {
if (startDate) return new Date(startDate).getTime()
@@ -383,23 +415,18 @@ const CreditAccumulationGraphHighCharts = ({
})
)
)
const studyRightStart = new Date(students[0]?.studyrightStart ?? new Date()).getTime()
const studyRightStart = getStudyRightStart()
if (studyPlanFilterIsActive && cutStudyPlanCredits) return studyRightStart
if (studyPlanFilterIsActive)
return Math.min(..._.flatten(students.map(({ courses }) => courses.map(({ date }) => new Date(date).getTime()))))
return Math.min(
studyRightStart,
..._.flatten(
students.map(({ courses }) => {
return courses.map(({ date }) => new Date(date).getTime())
})
)
)
return studyRightStart
}
const graduations = singleStudent ? filterGraduations(students[0], selectedStudyRight) : []
const studyRightStartLine =
!singleStudent && studyPlanFilterIsActive ? [new Date(students[0].studyrightStart).getTime()] : []
!singleStudent && studyPlanFilterIsActive
? [new Date(customStudyStartYear || students[0].studyrightStart).getTime()]
: []
const options = createGraphOptions({
singleStudent,
@@ -32,7 +32,7 @@ const CustomPopulationProgrammeDist = ({
studentToTargetCourseDateMap,
populationStatistics.elementdetails.data
)
if (programme && programme.code === '00000') {
if (programme && programme.code === '00000' && coursecode) {
const filteredEnrollments = (student.enrollments || [])
// eslint-disable-next-line camelcase
.filter(({ course_code }) => coursecode.includes(course_code))
@@ -102,7 +102,7 @@ const CustomPopulationContent = ({ students, custompop }) => {
const populations = useSelector(state => state.populations)
const { customPopulationFlag } = populations
const { progress } = useProgress(populations.loading)
const { progress } = useProgress(populations.pending)
const { data: courseStats } = useGetStudentListCourseStatisticsQuery({
studentNumbers: students.map(s => s.studentNumber),
@@ -0,0 +1,20 @@
import React from 'react'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { Segment, Header } from 'semantic-ui-react'
import { useTitle } from '../../common/hooks'
const FacultyStatistics = () => {
useTitle('Faculties')
return (
<div className="segmentContainer">
<Header className="segmentTitle" size="large">
Faculty statistics
</Header>
<Segment className="contentSegment">Tosi hienot faculty statsit tähän</Segment>
</div>
)
}
export default connect()(withRouter(FacultyStatistics))
@@ -30,7 +30,7 @@ const CreditDateFilterCard = ({ options, onOptionsChange }) => {
)
}
export default createFilter({
const filter = createFilter({
key: 'CreditDate',
title: 'Date of Course Credits',
@@ -62,5 +62,14 @@ export default createFilter({
return true
},
selectors: {
// eslint-disable-next-line no-unused-vars
selectedStartDate: ({ startDate }, _) => startDate,
},
component: CreditDateFilterCard,
})
export default filter
export const { selectedStartDate } = filter.selectors
@@ -1,17 +1,58 @@
import React from 'react'
import { Radio } from 'semantic-ui-react'
import { Radio, Button } from 'semantic-ui-react'
import useFilters from 'components/FilterView/useFilters'
import moment from 'moment'
import createFilter from './createFilter'
import creditDateFilter, { selectedStartDate } from './date'
const HopsFilterCard = ({ options, onOptionsChange }) => {
const { filterDispatch, useFilterSelector } = useFilters()
const selectedCreditStartDate = useFilterSelector(selectedStartDate(''))
return (
<div
style={{ display: 'flex', alignItems: 'center', gap: '1em', cursor: 'pointer' }}
onClick={() => {
onOptionsChange({ active: !options.active })
}}
>
<Radio style={{ width: '3.5rem', flexShrink: 0 }} toggle checked={options.active} />
<div>Show only credits included in study plan</div>
<div>
<div
style={{ display: 'flex', alignItems: 'center', gap: '1em', cursor: 'pointer' }}
onClick={() => {
if (
selectedCreditStartDate &&
options.studyStart &&
options.clearCreditDate &&
new Date(selectedCreditStartDate) > new Date(options.studyStart) &&
!options.active
)
filterDispatch(
creditDateFilter.actions.setOptions({
startDate: null,
endDate: null,
})
)
onOptionsChange({ ...options, active: !options.active })
}}
>
<Radio style={{ width: '3.5rem', flexShrink: 0 }} toggle checked={options.active} />
<div>Show only credits included in study plan</div>
</div>
{options.studyStart ? (
<Button
content="Cut credits to study start"
onClick={() =>
filterDispatch(
creditDateFilter.actions.setOptions({
startDate: moment(options.studyStart),
endDate: null,
})
)
}
disabled={!options.active}
className="credit-date-filter-input"
size="mini"
style={{
margin: '0.5rem',
whiteSpace: 'nowrap',
}}
/>
) : null}
</div>
)
}
@@ -19,7 +60,7 @@ const HopsFilterCard = ({ options, onOptionsChange }) => {
export default createFilter({
key: 'hops',
title: 'HOPS',
title: 'Personal Study Plan',
priority: -200,
@@ -58,5 +58,11 @@ export default createFilter({
filter: (student, { transferred }) => student.transferredStudyright === transferred,
actions: {
set: (options, value) => {
options.transferred = value
},
},
component: TransferredToProgrammeFilterCard,
})
@@ -9,6 +9,7 @@ import LanguagePicker from '../LanguagePicker'
import { isDev, adminerUrls } from '../../conf'
const allNavigationItems = {
faculty: { path: '/faculties', key: 'faculties', label: 'Faculties', reqRights: ['admin'] },
populations: {
key: 'studyProgramme',
label: 'Study programme',
@@ -1,6 +1,8 @@
import React, { useRef } from 'react'
import { connect } from 'react-redux'
import { Accordion } from 'semantic-ui-react'
import useFilters from 'components/FilterView/useFilters'
import studyPlanFilter from 'components/FilterView/filters/hops'
import { useLocalStorage } from '../../common/hooks'
import CreditAccumulationGraphHighCharts from '../CreditAccumulationGraphHighCharts'
@@ -53,8 +55,10 @@ const PopulationDetails = ({
*/
}
const renderCreditGainGraphs = () => {
const RenderCreditGainGraphs = () => {
const { CreditAccumulation } = infotooltips.PopulationStatistics
const { useFilterSelector } = useFilters()
const studyPlanFilterIsActive = useFilterSelector(studyPlanFilter.selectors.isActive)
const graphs = (
<CreditAccumulationGraphHighCharts
@@ -63,6 +67,7 @@ const PopulationDetails = ({
trayOpen={() => {}}
language={language}
programmeCode={query?.studyRights?.programme}
studyPlanFilterIsActive={studyPlanFilterIsActive}
/>
)
return (
@@ -93,7 +98,7 @@ const PopulationDetails = ({
},
onTitleClick: () => handleClick(0),
content: {
content: <div ref={creditGraphRef}>{renderCreditGainGraphs()}</div>,
content: <div ref={creditGraphRef}>{RenderCreditGainGraphs()}</div>,
},
},
{
@@ -9,7 +9,7 @@ const FilterActiveNote = () => {
const handleClick = () => {
setClicked(true)
filterDispatch(transferredToProgrammeFilter.actions.clear())
filterDispatch(transferredToProgrammeFilter.actions.set(null))
}
if (clicked) return true
@@ -103,6 +103,9 @@ const PopulationStatistics = () => {
[transferredToProgrammeFilter.key]: {
transferred: false,
},
[hopsFilter.key]: {
studyStart: (students[0] || {}).studyrightStart,
},
}}
>
{filteredStudents => (
@@ -39,6 +39,7 @@ const CustomPopulation = React.lazy(() => retry(() => import('../CustomPopulatio
const Updater = React.lazy(() => retry(() => import('../Updater')))
const Trends = React.lazy(() => retry(() => import('../Trends')))
const StudyGuidanceGroups = React.lazy(() => retry(() => import('../StudyGuidanceGroups')))
const FacultyStatistics = React.lazy(() => retry(() => import('../FacultyStatistics')))
const routes = {
students: '/students/:studentNumber?',
@@ -51,6 +52,7 @@ const routes = {
updater: '/updater',
trends: '/trends',
studyGuidanceGroups: '/studyguidancegroups/:groupid?',
faculties: '/faculties/:facultyCode?',
}
const Routes = () => (
@@ -60,6 +62,7 @@ const Routes = () => (
<Route exact path={routes.feedback} component={Feedback} />
<Route path={routes.trends} component={Trends} />
<ProtectedRoute requireUserHasRights exact path="/populations" component={Populations} />
<ProtectedRoute requiredRoles={['admin']} exact path="/faculties/:facultyCode?" component={FacultyStatistics} />
<ProtectedRoute
requireUserHasRights
exact
@@ -87,7 +87,11 @@ const StudyrightsTable = ({
new Date(c.startdate).getTime() > new Date(c.studystartdate).getTime() ? c.startdate : c.studystartdate
// End dates of study rights are semi-open intervals, subtract 1 day to get acual end date
const getAcualEndDate = endDate => (endDate ? new Date(endDate).setDate(new Date(endDate).getDate() - 1) : null)
const getAcualEndDate = (endDate, graduated) => {
if (!endDate) return null
if (graduated) return new Date(endDate)
return new Date(endDate).setDate(new Date(endDate).getDate() - 1)
}
const renderProgrammes = c =>
sortBy(c.elements.programmes.filter(filterDuplicates), 'startdate')
@@ -95,7 +99,7 @@ const StudyrightsTable = ({
.map(programme => (
<p key={`${programme.name}-${programme.startdate}`}>
{`${programme.name} (${reformatDate(getActualStartDate(programme), 'DD.MM.YYYY')} - ${reformatDate(
getAcualEndDate(programme.enddate),
getAcualEndDate(programme.enddate, c.graduated),
'DD.MM.YYYY'
)})`}
{programmeCodes.includes(programme.code) && (
@@ -113,7 +117,7 @@ const StudyrightsTable = ({
.map(studytrack => (
<p key={studytrack.name}>
{`${studytrack.name} (${reformatDate(studytrack.startdate, 'DD.MM.YYYY')} - ${reformatDate(
getAcualEndDate(studytrack.enddate),
getAcualEndDate(studytrack.enddate, c.graduated),
'DD.MM.YYYY'
)})`}
<br />{' '}
@@ -10,6 +10,7 @@ import scrollToComponent from 'react-scroll-to-component'
import SegmentDimmer from 'components/SegmentDimmer'
import useFilters from 'components/FilterView/useFilters'
import creditDateFilter from 'components/FilterView/filters/date'
import studyPlanFilter from 'components/FilterView/filters/hops'
import { useFilteredAndFormattedElementDetails } from 'redux/elementdetails'
import {
useGetStudyGuidanceGroupPopulationQuery,
@@ -59,6 +60,7 @@ const SingleStudyGroupContent = ({ filteredStudents, population, group, language
)
const creditDateFilterActive = useFilterSelector(creditDateFilter.selectors.isActive)
const studyPlanFilterIsActive = useFilterSelector(studyPlanFilter.selectors.isActive)
const [coursesStructuredByProgramme, toggleCoursesStructuredByProgramme] = useToggleAndSetNewestIndex({
defaultValue: !!group.tags?.studyProgramme,
@@ -99,6 +101,8 @@ const SingleStudyGroupContent = ({ filteredStudents, population, group, language
togglePanel(0)
}, [population])
const customStudyStartYear = group?.tags?.year ? new Date(`${group.tags.year}-07-31`) : null
const createPanels = students => [
{
key: 0,
@@ -118,7 +122,12 @@ const SingleStudyGroupContent = ({ filteredStudents, population, group, language
{creditDateFilterActive ? 'Show all credits' : 'Show starting from associated year'}
</Button>
)}
<CreditAccumulationGraphHighCharts students={students} />
<CreditAccumulationGraphHighCharts
students={students}
studyPlanFilterIsActive={studyPlanFilterIsActive}
programmeCode={group?.tags?.studyProgramme}
customStudyStartYear={customStudyStartYear}
/>
</div>
),
},
@@ -201,6 +210,7 @@ const SingleStudyGroupFilterView = props => {
language: props.language,
}),
filters.ageFilter,
filters.hopsFilter({ programmeCode: props.group?.tags?.studyProgramme }),
filters.genderFilter,
filters.startYearAtUniFilter,
filters.tagsFilter,
@@ -252,6 +262,10 @@ const SingleStudyGroupFilterView = props => {
initialOptions[filters.creditDateFilter.key] = {
startDate: moment(createAcademicYearStartDate(props.group.tags?.year)),
}
initialOptions[filters.hopsFilter.key] = {
studyStart: props.group?.tags?.year ? `${props.group.tags.year}-07-31` : null,
clearCreditDate: true,
}
}
return (
@@ -38,12 +38,7 @@ const StudyGuidanceGroupPopulationCourses = ({
{showStructured ? (
<PopulationCourseStats courses={courses} pending={false} filteredStudents={filteredStudents} />
) : (
<PopulationCourseStatsFlat
courses={courses}
pending={false}
filteredStudents={filteredStudents}
showFilter={false}
/>
<PopulationCourseStatsFlat courses={courses} pending={false} filteredStudents={filteredStudents} />
)}
{pending && <SegmentDimmer isLoading={pending} />}
</Segment>
@@ -140,7 +140,7 @@ const getColumns = (language, showStudents) => {
title: 'Major credits',
cellStyle: { textAlign: 'right' },
filterType: 'range',
getRowVal: course => course.totalProgrammecredits,
getRowVal: course => course.totalProgrammeCredits,
getRowContent: course => course.totalProgrammeCredits,
},
{
@@ -272,7 +272,7 @@ const updateStudyRightElements = async (
const studentnumber = personIdToStudentNumber[mainStudyRight.person_id]
// according to Eija Airio this is the right way to get the date... at least when studyright has changed
let startDate = snapshot.first_snapshot_date_time
let startDate = snapshot.first_snapshot_date_time || snapshot.valid.startDate
// fix for varhaiskasvatus, see https://github.com/UniversityOfHelsinkiCS/oodikone/issues/2741
if (