Compare commits

...
922 Commits
Author SHA1 Message Date
joonas-a a6d69cbff5 [Backend][Updater] Add way to flush redis completely in prod
Vastuuvapauslauseke: käyttö omalla vastuulla
2026-06-08 16:10:51 +03:00
Aaro Koivukangas c0b3decaf7 [CompletedCoursesSearch] Refactor substitutions => substitution_groups 2026-06-06 13:41:42 +03:00
joonas-a 1258c0c55c [Backend][Study track stats] discard study rights that do not belong in any category
If viewing bs+ms study right, bs study rights granted before 2017 do not
belong to any category -> do not show them
2026-06-05 12:08:05 +03:00
Aaro Koivukangas 3a0cb6f0ef [PreCommit] Restore previous behaviour for husky pre-commit hook 2026-06-04 14:57:39 +03:00
joonas-a 33e8fb7a39 [Docker] Enable nodemon for dev-updater + fix error 2026-06-04 14:50:48 +03:00
joonas-a 39c78791c2 [Updater panel] layout tweak and fix eslint breaking functionality 2026-06-03 19:13:51 +03:00
joonas-a 54e0469a02 [Updater panel] Improvements
- Convert to TS
- Show status of ongoing and queued jobs
2026-06-03 19:08:39 +03:00
Aaro Koivukangas 9c9e120d8b [Meta] Remove one more instance of substitutions 2026-06-03 17:38:00 +03:00
Aaro Koivukangas 2466aab3a4 [StudyProgrammes] Replace one instance of substitutions with substitution_groups 2026-06-03 16:51:54 +03:00
Santeri Rajamäki 0ac0d8befa [Frontend] Use full width with ErrorBackground as it doesn't have a parent to fill 2026-06-03 13:24:46 +03:00
Santeri Rajamäki e8b46a8b56 [Frontend] FilterView improvements 2026-06-03 13:12:37 +03:00
Santeri Rajamäki 73ee57b6bc [Frontend] Filter rework 2026-06-03 13:11:14 +03:00
joonas-a 4b60f985ac [Curriculum picker] add translation to infobox 2026-06-03 12:33:39 +03:00
Aaro Koivukangas b975714c97 [Infoboxes] Refactor population infobox text object 2026-06-03 12:30:39 +03:00
joonas-a f7c22f4d6e [Filters] Clarify date of course part. infobox text 2026-06-03 12:19:45 +03:00
joonas-a f8cfe9c23a [Percentiles] add english infobox translation 2026-06-03 11:50:54 +03:00
Aaro Koivukangas e818ffa03a [Infoboxes] Refactor faculty infobox text object 2026-06-03 10:39:13 +03:00
Aaro Koivukangas 571a51f09f [Infoboxes] Refactor studyprogramme infobox text object 2026-06-03 10:14:05 +03:00
joonas-a 64deef0c27 [Programme overview][Percentiles] Use proper class sizes
... instead of the amount of months in the range oops.
2026-06-02 18:47:49 +03:00
joonas-a 0e7746184c [Percentiles chart] Fix HTML tag use 2026-06-02 18:39:20 +03:00
joonas-a 628a93e6df [Backend] add proper class sizes to studytrackstats response 2026-06-02 18:37:59 +03:00
joonas-a e4091d6b45 [Percentiles] Add data view feature to chart 2026-06-02 13:45:43 +03:00
joonas-a f57dce2259 [Percentiles][prog overview] add infobox
finnish only :d
2026-06-02 13:45:43 +03:00
Aaro Koivukangas 853912f25b [InfoBoxes] Fix remaining broken info-boxes 2026-06-02 12:37:22 +03:00
Aaro Koivukangas a55198464c [Meta] Remove unused 'substitutions' from types 2026-06-01 15:13:13 +03:00
Aaro Koivukangas b8e39aa29a [Faculty] Change Faculty-view, Update credit data -function to use substitution_groups 2026-06-01 15:09:02 +03:00
Aaro Koivukangas 94d0e54e8b [E2E] Course filter e2e fix 2026-06-01 13:22:02 +03:00
joonas-a ed90d13846 [META] remove safechain in favor of npmrc in docker images 2026-06-01 13:13:39 +03:00
joonas-a 7731a4c699 [META] add npmrcs 2026-06-01 12:52:51 +03:00
Aaro Koivukangas 486613febd [Filters][Courses] Course filter calculates enrolled, no grade correctly 2026-06-01 12:48:27 +03:00
Aaro Koivukangas d7e26d22d0 [Meta] Remove unused unifyOpenUniCourses query param from links 2026-05-29 15:25:54 +03:00
Santeri Rajamäki e069c315a5 [META] Remove open uni search 2026-05-29 11:54:02 +03:00
Aaro Koivukangas f4ac2b3806 [Class statistics][Filters] Course filter respects substitution_groups 2026-05-28 16:08:18 +03:00
Santeri Rajamäki 03756dfce0 [Frontend] Introduce expanding infoboxes 2026-05-28 13:00:36 +03:00
Santeri Rajamäki 1c26912ac7 [Frontend] Add HTML support to react markdown using "an official plugin" 2026-05-28 12:59:36 +03:00
Aaro Koivukangas 8a7b9b4894 [Navbar] Make dev version easily distinguishable from production 2026-05-28 12:40:22 +03:00
Aaro Koivukangas b4103ca779 [CourseSearch] Implement hover for Course search substitution groups 2026-05-27 15:55:15 +03:00
Aaro Koivukangas 4a68fe19e6 [CI] Disable using prebuilt frontend version 2026-05-27 13:52:15 +03:00
Santeri Rajamäki 69365473a4 [META] Use node user in local dev environments to avoid root devour 2026-05-27 13:37:18 +03:00
Aaro Koivukangas 2cfe0d1f6c [CI] Fix correct image name for frontend 2026-05-27 13:36:42 +03:00
Aaro Koivukangas 26d33c1201 [CI] Add missing uses from gh action 2026-05-27 13:20:10 +03:00
Aaro Koivukangas b1c8396098 [CI] Use same config for frontend as backend 2026-05-27 13:18:10 +03:00
Santeri Rajamäki 9d608dbbcc [META] Update docker dev builds 2026-05-27 13:09:02 +03:00
Aaro Koivukangas ae52189689 [CI] Utilize prebuild frontend image 2026-05-26 13:59:20 +03:00
Aaro Koivukangas ecfc8053a0 [CI] Use prebuilt backend image during e2e 2026-05-26 13:31:08 +03:00
Aaro Koivukangas 71bff0214e [CI] Don't pull buildable images eg. backend 2026-05-26 13:19:35 +03:00
Aaro Koivukangas 58bbe00171 [CI] Try acually utilizing the built backend image 2026-05-26 13:13:52 +03:00
Aaro Koivukangas 75302bdc14 [CI] Use more docker-image caching during testing 2026-05-26 12:54:52 +03:00
Aaro Koivukangas 55135030e0 [CI] Fix docker-image missing 2026-05-26 12:42:46 +03:00
Santeri Rajamäki a8b87eed3c [SGG] Remove extra Paper wrapper from Courses of population 2026-05-26 12:34:56 +03:00
Santeri Rajamäki 95f36ef7e8 [PopulationStatistics] Move curriculum selection to the top-level details panel 2026-05-26 12:34:56 +03:00
Aaro Koivukangas f8107bc475 [CI] Increase test parallelization 2026-05-26 12:14:07 +03:00
Aaro Koivukangas 391ea1cbaf [E2E] More fixes 2026-05-26 11:54:31 +03:00
Aaro Koivukangas 5f1d3a12b7 [E2E] e2e test typo fixes 2026-05-26 11:39:26 +03:00
Santeri Rajamäki c533c25bfd [Backend] Remove redundant FD user code 2026-05-26 11:11:31 +03:00
Santeri Rajamäki d4e2d856e8 [Frontend] Use better font 2026-05-26 11:11:31 +03:00
Aaro Koivukangas c5b3147f04 [CourseStats] Update tests to match page title changes 2026-05-26 11:09:04 +03:00
Aaro Koivukangas 8977c52225 [CoursePopulation] Add includeSubstitutions flag to links to Course populations instead of massive coursecode arrays 2026-05-25 22:45:58 +03:00
Aaro Koivukangas a8f48f24d1 [Meta] Add curl to alpine images 2026-05-24 22:31:34 +03:00
Aaro Koivukangas 714682c6c5 [CI] Properly skip e2e if api fails 2026-05-24 21:54:34 +03:00
Aaro Koivukangas 0659d162b5 [Meta] Change alpine images to use ash instead of bash 2026-05-24 21:54:34 +03:00
Aaro KoivukangasandAaro Koivukangas 7057e00ee2 [E2E] Disable the broken test entirely 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas adf64a3d1d [E2E] Disable incorrect check in test 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 36421cf5e7 [E2E] Now acually scroll the bar into view 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas a81b569ab9 [E2E] Scroll bar chart to view in e2e test 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 7641ca3ba1 [E2E] Fix remaining broken tests with new data 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 9515adb1be [ApiTests] Update data used in api tests 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 55668cc313 [E2E][CourseStats] Fix incorrect e2e test values 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 28b7cdfe6b [CI] Increase sleep time on failure logs 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 4b6bba653a [CourseStats] Safeguard all length checks 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas eea167df9b [CourseStats] Handle courses with incorrect substitution_groups better 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas ce946bcb0d [CI] Pull api-test docker images quietly, less logs 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 6580f42eab [CI] Wait for docker to output all error logs to stdout 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 043b0d2cbd [CourseStats] Add better handling for invalid values 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 25fb4bad84 [CourseStats][E2E] Fix broken e2e tests 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 53f3955427 [CourseStats][E2E] Fix e2e tests 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 49390cf92f [CourseStats] Lint fixes 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 128ee465ff [CourseStats] Fix provider organization selector 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 6ed84bee52 [CourseStats] Fix population link course lists 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas fe665cc8ad [CourseStats] Fix enrollments 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 413f2b4fda [CourseStats] Fix substitution group chips on combineSubstitutions=true 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas 8959204a37 [CourseStats] Fix attempts calculations 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas f069d705cb [CourseStats] Fix student statistics 2026-05-24 21:53:26 +03:00
Aaro KoivukangasandAaro Koivukangas adb8c9322b [CourseStats] Fix substitution calculations WIP 2026-05-24 21:53:26 +03:00
Aaro Koivukangas 3e83b63324 [Meta] Add safe-chain to all actions and Dockerfiles 2026-05-24 21:34:23 +03:00
joonas-a 4dcb6b4e37 [Percentiles] Add class size indicator to chart 2026-05-20 16:31:20 +03:00
joonas-a 7830a8abc3 [Backend][Math] fix interpolation logic 2026-05-20 16:23:40 +03:00
joonas-a 34e006e12a [Backend][Percentiles] guarantee obj keys in order
failsafe
2026-05-20 16:14:31 +03:00
joonas-a a33fe22bac [Percentiles] fix credit calculation for bsc+msc combos 2026-05-20 15:46:19 +03:00
joonas-a 4ac467fa7a [lint] moar fix lint error 2026-05-20 14:45:02 +03:00
joonas-a 6aad36d06a [lint] run eslint --fix 2026-05-20 14:32:35 +03:00
Santeri Rajamäki 8413369b89 [CoursePopulation] Fetch course details instead of relying on the fetched population 2026-05-20 13:51:45 +03:00
Santeri Rajamäki 4f2780bf00 [CoursePopulation] Clean up table utils 2026-05-20 13:51:45 +03:00
joonas-a 59120fa69c [Study tracks and class stats] Implement credit acc percentile view 2026-05-20 13:50:54 +03:00
joonas-a 5e502fa23a [Year selector] add slim option 2026-05-19 17:00:35 +03:00
joonas-a 28eec2db0f [Styled message] enhance 2026-05-19 17:00:09 +03:00
joonas-a 9df131b326 [Backend][Study tracks and class stats] include student credit gain percentiles in response 2026-05-19 15:58:57 +03:00
Santeri Rajamäki b9bd916b91 [META] Less lodash 2026-05-19 13:50:02 +03:00
Santeri Rajamäki c99034575f [CoursesOfClass] orderBy in the OT instance instead of setModules hook 2026-05-19 12:26:40 +03:00
Santeri Rajamäki dc12b0fd04 [CoursesOfClass] Add warning message when there are no courses/modules to show with the current student amount limit 2026-05-19 11:51:57 +03:00
Santeri Rajamäki 3ec2b5e5d1 [E2E] Use hover 2026-05-18 15:56:03 +03:00
Santeri Rajamäki 26461f5277 [E2E] Add hover command 2026-05-18 15:40:50 +03:00
Santeri Rajamäki 17b83a8574 [E2E][Students] Remove highcharts specific code 2026-05-18 14:06:40 +03:00
Santeri Rajamäki ec2e203e6b [E2E][DegreeProgramme] Fix mouseover for the rest of the failing tests 2026-05-18 14:00:33 +03:00
Santeri Rajamäki 6f6a732e7f [E2E][DegreeProgramme] Fix mouseover for split view 2026-05-18 13:37:51 +03:00
Santeri Rajamäki fbbf3830b3 [E2E][DegreeProgramme] Fix mouseover 2026-05-18 13:12:14 +03:00
Santeri Rajamäki 4cbccd29ba Merge remote-tracking branch 'origin/chart-refresh' 2026-05-16 01:30:37 +03:00
joonas-a 3d0fd3563f Delete highcharts 2026-05-15 17:49:11 +03:00
joonas-a a929613e83 [Course stats] enable data view on chart + cleanup 2026-05-15 17:48:36 +03:00
joonas-a d64aca5285 [Course stats] convert grade dist to echarts 2026-05-15 17:46:38 +03:00
joonas-a 9e1307595c [Course stats] convert pass rate chart to echarts 2026-05-15 17:37:15 +03:00
joonas-a 0e9ba8490d Delete old files 2026-05-15 17:17:47 +03:00
joonas-a 50b4166a7f [Charts] cleanup 2026-05-15 17:01:03 +03:00
joonas-a 3843937429 [Graduation times chart] more refactor 2026-05-15 15:55:42 +03:00
joonas-a b60b1dd88b [Graduation times chart] refactor 2026-05-15 14:56:49 +03:00
Santeri Rajamäki 4f4868a32b [E2E] Skip now implemented feature test 2026-05-15 13:04:17 +03:00
Santeri Rajamäki d45bddbeab [DegreeProgramme][StudyTracks] Add ability to view progress by study track 2026-05-15 12:35:29 +03:00
Santeri Rajamäki 2abda1de50 [Frontend] StudyTrack is study track 2026-05-13 14:47:48 +03:00
Santeri Rajamäki 333944d377 [Programme][StudyTracks] Add export option to tables, mirroring faculties view 2026-05-13 13:55:00 +03:00
Santeri Rajamäki 50c301fe82 [Degree programme] Replace study programme with degree programme 2026-05-13 11:34:11 +03:00
joonas-a ed513c14bf [Error background] put veterans to rest 2026-05-12 16:23:38 +03:00
joonas-a 8e2b8e5443 [University][Backend] fix graduation targets
from months to semesters like frontend expects
2026-05-12 15:39:58 +03:00
joonas-a bb215bb785 [Charts] bring back missing legends oops 2026-05-12 15:38:27 +03:00
Santeri Rajamäki 35ea22e125 [Frontend] Convert ColorizedCoursesTable to typescript 2026-05-12 15:13:38 +03:00
Santeri Rajamäki c3c7b5b479 [Frontend] Rework useSemesters 2026-05-12 14:59:50 +03:00
joonas-a 635fb97b25 [Charts] final tweaks 2026-05-12 14:54:07 +03:00
joonas-a a5334c8e2f [Progres bar chart] Change emphasis strategy
fixes incorrect highlighting bug when clicking categories from the
legend
2026-05-12 14:25:18 +03:00
joonas-a 18b8daabff [Charts] convert horizontal stacked bar chart 2026-05-12 14:24:26 +03:00
Santeri Rajamäki b6494f88d0 [Frontend] Cleanup ColorizedCourseTable CSS rules 2026-05-12 14:14:09 +03:00
Santeri Rajamäki 87bc8b24a9 [Frontend] Rework StudentsByStartingYear StudentDataTable and exportStudentTable 2026-05-12 14:14:09 +03:00
joonas-a b181fda7fc [Charts] Fix styling issues 2026-05-12 13:27:23 +03:00
joonas-a 747c37697c [Median graduations] add guard for undefined data and fix style 2026-05-12 12:05:12 +03:00
joonas-a 76d8fef4eb [Dist devlpmnt] Update infobox 2026-05-12 11:51:06 +03:00
Aaro Koivukangas e08d99c801 [Semesters] Remove type assertion and use reduce<type> 2026-05-12 11:47:11 +03:00
Aaro Koivukangas b5f7a8bd86 [ApiTests] Add initial test and rename incorrect test name 2026-05-12 11:45:32 +03:00
Aaro Koivukangas 7d7d71b6be [Admin][Updater] Re-enable all buttons in staging 2026-05-12 11:36:46 +03:00
Aaro Koivukangas c1bacd11cc [Updater] Add hack fix to mainCourseCode being array 2026-05-12 11:36:46 +03:00
joonas-a 743ccb6797 [Credit accumulation] infobox update 2026-05-11 18:00:13 +03:00
joonas-a dab81f994d [Charts][Crdt accmltn] bring back click to open individual student 2026-05-11 16:51:42 +03:00
joonas-a b17fa2a11d [Charts][Graduation times] Convert breakdown chart 2026-05-11 15:25:09 +03:00
joonas-a ea8a84420f [Charts][Graduation times] Invert order to match previous behaviour 2026-05-11 15:24:43 +03:00
joonas-a 56d98e16cd [Charts][Graduation times] convert median chart 2026-05-11 13:56:52 +03:00
Santeri Rajamäki 110994ea46 [Filters] Remove view restriction from actions 2026-05-06 12:27:01 +03:00
Santeri Rajamäki 11bde27811 [Filters] Apply students length filter to Credit Statistics filter buttons 2026-05-06 12:24:51 +03:00
Santeri Rajamäki 0f4ef4a3cd [ClassStatistics][Students] Add emails to courses table 2026-05-06 12:02:47 +03:00
Santeri Rajamäki a3bb84e169 [Frontend] Demangle FilterView 2026-05-06 11:50:20 +03:00
joonas-a b9b90876f1 [Charts][Year expansion chart] first attempt at echarts conversion 2026-04-29 16:08:35 +03:00
joonas-a 777a19b291 [Charts][LineGraph] Add emphasis strategy
Will make it so when hovering only one item blurs all the others
2026-04-29 11:13:15 +03:00
joonas-a 362930c481 [Student tab] change credit acc graph dot icon 2026-04-28 11:42:52 +03:00
joonas-a 8ca1636517 [Charts][Progress of students] convert to echarts 2026-04-27 17:22:29 +03:00
joonas-a d6beafa13a [Charts][Graduated/thesis writers] convert graph to echart 2026-04-27 14:03:49 +03:00
joonas-a f92d72b77a [Charts][StackedBarChart] echart conversion 2026-04-27 13:42:46 +03:00
joonas-a dca144d58b [Charts] Replace students of x with echarts 2026-04-27 13:08:21 +03:00
Santeri Rajamäki 915d5c097c [Backend] Remove unused eslint disable 2026-04-24 09:58:35 +03:00
joonas-a 9a9f723b46 [Charts][Distribution development] Migrate to echarts 2026-04-21 12:41:39 +03:00
Santeri Rajamäki 523f6aee3e [Backend] Add rudimentary types to studyProgramme and programmeModules routes 2026-04-21 12:28:52 +03:00
Santeri Rajamäki e874454e40 [Backend] Remove reflect-metadata from haunting us 2026-04-21 12:04:24 +03:00
Santeri Rajamäki 45289702d5 [Backend] Update express types 2026-04-21 11:23:17 +03:00
Aaro Koivukangas 1ab6366fd1 [CI] Can't docker:down without .env, reverting adding docker:down 2026-04-21 10:16:52 +03:00
Aaro Koivukangas a01b06b8bc [CI] Remove updater from api-test compose, missing .env 2026-04-21 10:00:27 +03:00
Aaro Koivukangas 11dfa4c6f1 [Updater] Fix logger typo 2026-04-20 15:43:49 +03:00
Aaro Koivukangas b94e4fc25d [Updater] Remove cron jobs in staging environment 2026-04-20 15:37:46 +03:00
Aaro Koivukangas c1bb44bd55 [CI] Restart docker when starting api-ci environment 2026-04-20 15:37:19 +03:00
Aaro Koivukangas 1d8f92c663 [ApiTests] Add api tests for /populationstatisticsbycourse 2026-04-20 15:34:04 +03:00
joonas-a e6686c024a [Charts][Student details] final touches 2026-04-20 14:12:21 +03:00
joonas-a 2aa6e9c549 [Student grade graph] Move legend to right for consistency 2026-04-20 12:39:44 +03:00
joonas-a 898a2b1f1b [Charts][Student details] Renew grade graphs 2026-04-20 12:25:39 +03:00
joonas-a 6a9a49952b [Frontend] Further enhance StyledMessage 2026-04-20 12:02:49 +03:00
joonas-a 0f76e50e4b [Frontend] Enhance StyledMessage 2026-04-20 11:59:16 +03:00
Aaro Koivukangas b5ee1f2190 [Eslint] Correct ignore rules for backend/tests 2026-04-17 13:28:37 +03:00
Aaro Koivukangas c5649e9d39 [ApiTests] Restructure tests 2026-04-17 13:25:14 +03:00
Aaro Koivukangas 2a979af974 [Lint] Lint fixes 2026-04-17 12:40:12 +03:00
Aaro Koivukangas 06964526dc [Husky] Enable front+back linting again :D 2026-04-17 12:37:12 +03:00
Aaro Koivukangas b2b471dab5 [ApiTests] Add api tests for /populationstatistics/studyprogrammes 2026-04-17 12:32:52 +03:00
joonas-a d9695901b2 [Charts][Student details] remake credit graph 2026-04-16 16:08:15 +03:00
joonas-a b85edaf6f0 [Charts][Custom pop] Use new credit acc
Also add graduations if programme set.
2026-04-16 14:30:53 +03:00
joonas-a b165ece770 [Lint] fix 2026-04-16 14:19:51 +03:00
joonas-a a90e82a31e [Charts][SGG] use new credit acc 2026-04-16 14:18:58 +03:00
joonas-a 651e37051d [Charts][Credit acc] fix combined programme graduation markers 2026-04-16 13:55:21 +03:00
joonas-a 77b6d1cb16 [Charts] cleanup and edge case handling 2026-04-16 12:48:30 +03:00
Aaro Koivukangas 15b9551f7a [Husky] Lint all files in pre-commit hook and don't lint them twice 2026-04-15 17:17:13 +03:00
Aaro Koivukangas c6f4e6066b [Meta] Remove rapodiff + unused npm commands 2026-04-15 17:10:33 +03:00
Aaro Koivukangas 7ebb60e564 [Staging] Disable updater update buttons in staging, breaks things 2026-04-15 16:32:48 +03:00
Aaro Koivukangas b641cf9ca6 [Scripts] Add script to update staging databases with test data 2026-04-15 16:11:51 +03:00
Santeri Rajamäki 686697f202 [Frontend] Convert LanguageCenterView to tsx 2026-04-14 16:32:00 +03:00
Santeri Rajamäki 296299e635 [Frontend] Simplify ColorizedCoursesTable top-level usage 2026-04-14 16:30:24 +03:00
joonas-a afa6804d09 [Charts] Credit acc done for non bscmsc classes 2026-04-14 16:19:46 +03:00
Santeri Rajamäki c8b8b033ed [Frontend] Drop ProgressBar support 2026-04-14 15:43:45 +03:00
Santeri Rajamäki f64f34b971 [Frontend] Convert DateSelector and DateRangeSelector to tsx 2026-04-14 15:31:22 +03:00
Santeri Rajamäki 9b4f689dd3 [Frontend] Convert LogOutButton to tsx 2026-04-14 15:14:48 +03:00
Santeri Rajamäki cd32860300 [Frontend] Convert FilterDegreeCoursesModal to tsx 2026-04-14 15:10:28 +03:00
Santeri Rajamäki 7ed58eb38a [Frontend] Default CreditCriteriaSection to previous value instead of showing "help text" 2026-04-14 15:06:04 +03:00
Santeri Rajamäki 291082e6c3 [Frontend] Convert ExternalGradeFilterToggle to tsx 2026-04-14 14:44:31 +03:00
Santeri Rajamäki 54f363be6e [META] Drop version numbers from api routes 2026-04-14 14:41:52 +03:00
Santeri Rajamäki 539ed1e3b8 [Backend] Actually return after sending response for close to graduation 2026-04-14 13:24:48 +03:00
Santeri Rajamäki 9d61b50db5 [Frontend] Better type integration for redux users path 2026-04-14 13:22:52 +03:00
Santeri Rajamäki 8ede9de315 [Shared] Move user typings and route types to shared from backend 2026-04-14 13:07:11 +03:00
Santeri Rajamäki 1cb43cee6b [Teachers] Add more handling 2026-04-14 12:38:06 +03:00
Santeri Rajamäki b2617c8933 [Backend] Add handleQueryArrays to other backend routes that crash without it 2026-04-14 12:19:03 +03:00
Santeri Rajamäki beb1130050 [Backend] Add handleQueryArrays to courseYearlyStats 2026-04-14 11:52:38 +03:00
Santeri Rajamäki 1c75204854 [Filters] Map "Koepisteet" to "Valintakoe" as intended 2026-04-14 10:46:14 +03:00
Santeri Rajamäki 80bba6e0fc [Backend] Upgrade from express v4 to v5 2026-04-13 17:27:45 +03:00
Santeri Rajamäki 47b0dabe75 [OT] Remove pre-emptive null returns
OT pagination now supports changing data models which makes these early returns redundant
2026-04-13 15:52:50 +03:00
Santeri Rajamäki da45c8c150 [Backend] Cull user route types 2026-04-13 15:34:57 +03:00
joonas-a a57f2f01ca [Charts][Credit accmltn] hops filter functionality 2026-04-13 14:40:44 +03:00
Aaro Koivukangas 8fd7383217 [Staging] Remove unnecessary staging iamgroups 2026-04-13 12:00:04 +03:00
Santeri Rajamäki baa6d87c2c [Filters] Fix student number filter allowlist 2026-04-13 11:48:13 +03:00
Santeri Rajamäki 5ae4c8c7a0 [OT] Change rowsPerPage provider 2026-04-10 14:15:51 +03:00
Santeri Rajamäki cbb7e82d23 [OT] Apply ellipsis to cells table-wide 2026-04-10 14:09:07 +03:00
Aaro KoivukangasandSanteri Rajamäki 2768e95920 [Class stats][Courses of class] Show Master-level modules by fixing incorrect nullish coalesing 2026-04-10 13:39:10 +03:00
Aaro KoivukangasandSanteri Rajamäki 5b7d641ad1 [E2E] More page title e2e test fixing 2026-04-10 13:39:10 +03:00
Aaro KoivukangasandSanteri Rajamäki 37a71d7580 [E2E] Fix changed titles in tests 2026-04-10 13:39:10 +03:00
Aaro KoivukangasandSanteri Rajamäki aaa7b5de27 [E2E] Fix tests 2026-04-10 13:39:10 +03:00
Aaro KoivukangasandSanteri Rajamäki 40bcbc59dc [Class stats][Courses of class] Change empty fields to be 0, not null 2026-04-10 13:39:10 +03:00
Aaro KoivukangasandSanteri Rajamäki 5e46ce0cb0 [Class stats] Improve page title 2026-04-10 13:39:10 +03:00
Santeri Rajamäki 517c08091e [META] Update @types/pg 2026-04-10 13:39:09 +03:00
Santeri Rajamäki e25dfb2a36 [META] Update lodash-es 2026-04-10 13:38:24 +03:00
Santeri Rajamäki aa4e0abb44 [META] Update cypress 2026-04-10 13:37:13 +03:00
Santeri Rajamäki 3e5dc9a9f0 [META] Update globals 2026-04-10 13:37:13 +03:00
Santeri Rajamäki afd9541151 [META] Update eslint-plugin-import 2026-04-10 13:37:13 +03:00
Santeri Rajamäki a5ab3b972c [META] Update stylelint-config-standard 2026-04-10 13:37:13 +03:00
Santeri Rajamäki 4f3350e4f9 [META] Update stylelint 2026-04-10 13:37:13 +03:00
Santeri Rajamäki 2b8e1ed98f [Backend] Convert oriProvider to use module exports instead of CJS module.exports 2026-04-10 13:37:13 +03:00
joonas-a 1b4bdff37e [Charts][poc] credit accumulation 2026-04-02 16:14:04 +03:00
Santeri Rajamäki 3f311dee69 [Frontend] Remove legacy RTKApi middleware 2026-04-01 13:57:39 +03:00
Santeri Rajamäki eb6b683296 [Frontend] Convert apiConnections to typescript 2026-04-01 13:56:02 +03:00
Aaro Koivukangas b2153ec3dd [ClassStats][Courses] Add better handling for null substitution_groups (only in test-data) 2026-04-01 13:34:40 +03:00
Aaro Koivukangas 8949e7baf0 [Class stats][Courses] Better substitution group type handling 2026-03-31 15:18:47 +03:00
joonas-a 1fa10f7812 [Scripts] fix incorrect container name 2026-03-31 14:47:33 +03:00
Aaro Koivukangas fbe5db8a43 [E2E] Fix lint errors and e2e tests 2026-03-31 14:42:57 +03:00
Aaro Koivukangas d4c7a4014b [Class stats][Courses] Handle substitution groups correctly + fix
include substitutions button
2026-03-31 14:42:57 +03:00
Santeri Rajamäki 1177c2f848 [OT] Use 0th index in pagination when table data changes 2026-03-31 14:40:53 +03:00
Santeri Rajamäki 0f8d72fd1d [Frontend] Remove manual pagination from OT implementations 2026-03-31 14:36:45 +03:00
Santeri Rajamäki 47e3e2e26e [OT] Fix pagination 2026-03-31 14:36:05 +03:00
Santeri Rajamäki a312a14944 [Frontend] Disallow prop forwarding for all internal StyledTable attributes 2026-03-31 12:01:33 +03:00
joonas-a e3ec33f8dc [Course stat tables] try to add 0s 2026-03-30 16:10:22 +03:00
joonas-a 9c55d3b2ab [Highcharts] fix median time bar chart threshold lines 2026-03-30 16:07:51 +03:00
joonas-a 17ce83660c [e2e] programme overview graduation time fixes 2026-03-30 15:40:59 +03:00
joonas-a 22d3877b8e [Faculty][Graduation times] update graduation time targets in highcharts 2026-03-30 14:58:20 +03:00
joonas-a c60bee34fd [Backend][Faculty] update graduation time targets 2026-03-30 14:51:32 +03:00
joonas-a 15240050b7 [Faculty] graduation time infobox update 2026-03-30 14:31:18 +03:00
joonas-a 7da09c8b02 [Programme overview] update graduation time infoboxes 2026-03-30 14:31:18 +03:00
joonas-a cfb2d4d5c0 [Frontend][Graduation times] replace months with semesters 2026-03-30 14:31:18 +03:00
joonas-a 39b594d795 [Backend] Use semesters with graduation times 2026-03-30 14:31:18 +03:00
Santeri Rajamäki ff5bce1713 [E2E] Scroll coursestatistics data table into view 2026-03-30 10:26:56 +03:00
Santeri Rajamäki 8e288f3b26 [Frontend] Remove columnOrder from course statistics tables 2026-03-30 10:07:06 +03:00
Santeri Rajamäki 4134c8b64b [Frontend] Use manualPagination on course stats tables 2026-03-27 15:47:17 +02:00
Santeri Rajamäki ddc1a22492 [Frontend] Cleanup 2026-03-27 15:47:17 +02:00
Santeri Rajamäki 8ff1cb54dc [Frontend] Convert AttemptsTable to OT 2026-03-27 15:47:17 +02:00
Santeri Rajamäki 777de520f9 [Frontend] Convert StudentsTable to OT 2026-03-27 15:47:17 +02:00
joonas-a a9e325fe72 [Updater] Add transfer info to study rights 2026-03-26 17:41:17 +02:00
joonas-a befc3db3a8 [Filters][Studentnumber] Fix blacklist blacklisting all students always 2026-03-26 17:41:17 +02:00
Santeri Rajamäki e1b8ebd17f [Frontend] Readd EquivalenceGroupChip 2026-03-26 13:13:05 +02:00
Santeri Rajamäki 883129bee0 [Frontend] Unify course page secondary chip with course search table chips 2026-03-26 12:13:51 +02:00
Santeri Rajamäki 18eb04beeb [Frontend] Rework course search table and equivalence group chips 2026-03-26 12:12:19 +02:00
Santeri Rajamäki eb12d7e5ec [OT] Add cell width under size attribute 2026-03-26 12:08:56 +02:00
Santeri Rajamäki 26ca0a3bc1 [META] Remove redundant reset_jami_data script from setup script 2026-03-25 17:34:47 +02:00
Santeri Rajamäki b79c97780b [META] Update JAMI url in docker-compose 2026-03-25 17:29:04 +02:00
Santeri Rajamäki 0cd3bf28ff [Frontend] Replace MRT with StyledTable in StudyProgrammeTable 2026-03-25 16:20:55 +02:00
Santeri Rajamäki 20b0ba5a30 [Frontend] Enhance StyledTable 2026-03-25 16:19:12 +02:00
Aaro Koivukangas 67001a9db1 [Updater] Remove limit of 1000 entries from update-meta in dev environment 2026-03-23 14:15:51 +02:00
Aaro Koivukangas 8c268395b0 [E2E] Fix broken test 2026-03-20 16:32:41 +02:00
Aaro Koivukangas 235d2b03b2 [CourseStat][Search] Use substitution_groups when searching for courses 2026-03-20 15:53:11 +02:00
Aaro Koivukangas cbe20f1ad3 [Updater] Handle substitution_groups with null/undefined codes better 2026-03-20 15:53:11 +02:00
Matti Luukkainen 47211dcf89 [faculty/study programme] moar compact credits produced table (fits in screen) 2026-03-20 12:48:41 +02:00
joonas-a 26c6c653e5 [Custom pop] Enable hetu filter 2026-03-19 15:52:56 +02:00
Aaro Koivukangas 13bdf698cf [CI] Skip very flaky test 2026-03-19 15:29:25 +02:00
Aaro Koivukangas fd6a65925a [CI][DegreeProgrammes] Fix hetu-e2e tests once again 2026-03-19 15:03:14 +02:00
Aaro Koivukangas 435d2920fb [CI][DegreeProgramme] More e2e test fixes 2026-03-19 14:47:27 +02:00
Aaro Koivukangas 4ae93e0b7e [Updater] Add latest executed migration to starting logs 2026-03-19 14:45:18 +02:00
Aaro Koivukangas dc5200da2f [CI] Fix broken hetu-tests 2026-03-19 14:27:20 +02:00
joonas-a 373c8f9de8 [CI] remove debug print 2026-03-19 13:34:00 +02:00
joonas-a 34cc022830 [Custom pop][General tab] Fetch hops credits from acual hops
Also integrated CI test
2026-03-19 13:32:03 +02:00
Aaro Koivukangas ab311ddc66 [CI] Don't trigger all workflows if lint fails 2026-03-19 13:27:50 +02:00
Aaro Koivukangas 833fdad3bb [CI] Fix lint errors 2026-03-19 13:21:52 +02:00
joonas-a 4b634e032d [Eslint] Disable root imports
These cause silent backend crashes which are hard to debug
2026-03-19 13:18:41 +02:00
Aaro Koivukangas a16fd13413 [CI] Fix broken import in mailServices 2026-03-19 13:11:46 +02:00
Aaro Koivukangas 58913b4684 [Docker] Make .dockerignore work by moving it to context root 2026-03-19 12:58:55 +02:00
joonas-a 05c8993ba3 [Backend] Fix path import 2026-03-19 12:52:49 +02:00
joonas-a d3090ae725 [Ooditable] Highlight hovered row 2026-03-19 12:18:49 +02:00
joonas-a c870061fec [Excel exports] Show acual values in preview
No more empty strings
2026-03-19 12:00:49 +02:00
Aaro Koivukangas ec43699c3c [CI] Add backend and frontend logs to e2e tests 2026-03-19 11:57:56 +02:00
joonas-a 1b1e380a13 [Frontend] make lowercasing consistent 2026-03-17 17:21:14 +02:00
joonas-a 5c13b5c0ab [Programme overview][Faculties] hetu/ton credits produced 2026-03-17 17:18:02 +02:00
joonas-a f35fd41bfb [Backend] Include hetu/ton split in provider credits computation 2026-03-17 17:15:31 +02:00
joonas-a 6548cdd42b [Course population] Enable HETU filter 2026-03-17 15:16:30 +02:00
joonas-a 49b0da760d [Filters] New filter: Personal identity code 2026-03-17 15:16:30 +02:00
joonas-a 34e9cf5667 [Backend] Add "has hetu" to students 2026-03-17 15:16:30 +02:00
Aaro Koivukangas 288d32c591 [Backend][MailService] Backend doesn't crash if it tries to send emails in non-production environments 2026-03-17 13:16:04 +02:00
joonas-a 5001d55843 [Programme courses] Display hetuttomat/hetulliset 2026-03-17 12:25:41 +02:00
joonas-a eaedfe99db [Backend][Programme courses] Split open uni students (hetuton/hetu on) 2026-03-17 12:25:41 +02:00
Aaro Koivukangas 6cf375936a [Meta] Lint fixes 2026-03-17 12:02:12 +02:00
Aaro Koivukangas 8bc5c3ea65 [Updater][Substitutions] Add og. substitutions back and move the new substitutions to substitution_groups 2026-03-17 11:49:49 +02:00
Santeri RajamäkiandAaro Koivukangas 3e4e749625 [CoursesSearch] Rebuild substitution combination 2026-03-17 11:49:49 +02:00
Santeri RajamäkiandAaro Koivukangas 114af7522d [Backend] Try apply fix for courseFinder 2026-03-17 11:49:49 +02:00
Santeri RajamäkiandAaro Koivukangas c0b29d69c7 [Updater] Fix substitution .map() for modules 2026-03-17 11:49:49 +02:00
Santeri RajamäkiandAaro Koivukangas fb541ffa3a [Updater] Build course unit substitutions as substitution groups/arrays 2026-03-17 11:49:20 +02:00
Santeri RajamäkiandAaro Koivukangas 0ed4ce7bb4 [Updater] Clarify updateCourses function 2026-03-17 11:49:20 +02:00
Santeri RajamäkiandAaro Koivukangas 87e1e332a8 [Frontend] Convert sentry util to typescript 2026-03-17 11:49:20 +02:00
joonas-a 914440cef9 [Programme courses] Enable stripes in table 2026-03-17 10:52:06 +02:00
Aaro Koivukangas 901a4de21c [Updater] Allow updater workflow to trigger 2026-03-12 13:35:45 +02:00
Aaro Koivukangas 2bc28c7dd3 [Student] Convert BachelorHonors to ts 2026-03-12 13:00:57 +02:00
Aaro Koivukangas 2dd43b1ccd [StudyGuidanceGroups] Typo fix 2026-03-12 10:51:59 +02:00
Aaro Koivukangas 3fc1bc161b [Models] Add substitution_groups to course table in sis-db 2026-03-11 18:54:38 +02:00
Aaro Koivukangas 1edffd437e [ClassStats][CoursesOfPopulation] Remove module stats from Show all view 2026-03-11 17:14:49 +02:00
Aaro Koivukangas 4ed5e610bf [StudyGuidanceGroups] Change close-to-graduation link once more 2026-03-11 13:09:54 +02:00
Aaro Koivukangas 90ab1e80f7 [ClassStats][CoursesOfPopulation] Remove unecessary fields from module stats 2026-03-11 12:31:54 +02:00
Santeri Rajamäki 5b9f3c0184 [Meta] Update docker CI actions 2026-03-10 13:35:17 +02:00
Santeri Rajamäki 3f81fff6aa [Meta] Update swc plugin for vite 2026-03-10 13:32:17 +02:00
Santeri Rajamäki 5058a54099 [Meta] Update Vite for frontend 2026-03-10 13:27:10 +02:00
Santeri Rajamäki c5fb781f50 [Meta] Remove Vite from backend 2026-03-10 13:26:03 +02:00
Santeri Rajamäki 6c45c5cd76 [Meta] Upgrade Redux toolkit
This upgrade should bring noticeable improvments to frontend mankelointi times
2026-03-10 13:11:43 +02:00
Santeri Rajamäki b7e3df2e6b [Meta] Update sentry packages 2026-03-10 12:58:32 +02:00
Santeri Rajamäki 35ce0388eb [Meta] Update sequelize
Patch for CVE in JSON where parsing
2026-03-10 12:31:04 +02:00
Santeri Rajamäki 5252f3b816 [Meta] Update node-postgres and add type package for redundancy 2026-03-10 12:31:04 +02:00
Aaro Koivukangas 48f108c41d [StudyGuidanceGroup] Move filtering to the correct component 2026-03-10 12:16:07 +02:00
Santeri Rajamäki bade936f3c [Meta] Upgrade actions checkout, upload-artifact and download-artifact 2026-03-10 12:00:50 +02:00
Aaro Koivukangas c17721bd54 [StudyGuidanceGroup] Remove duplicate entries from Associated programme selector 2026-03-10 11:53:45 +02:00
Santeri Rajamäki a2e9064376 [Frontend] Move primitive functions to primitive file 2026-03-10 11:33:04 +02:00
Aaro Koivukangas 4b92ce4bd9 [StudyGuidanceGroup] Add better description to a link to close to graduation 2026-03-10 11:05:01 +02:00
Aaro Koivukangas f2348db941 [StudyGuidanceGroup] Fix empty table and add courses <-> modules switch to the sgg population view 2026-03-06 15:14:42 +02:00
Aaro Koivukangas b828904c3d [StudyGuidanceGroup] Migrate SGG population courses to ts 2026-03-06 14:49:14 +02:00
Aaro Koivukangas ed82a26e45 [Meta] Update remaining useGetProgrammesQuery users 2026-03-06 11:24:17 +02:00
Aaro Koivukangas b87ee36ebf [Students] Fix style 2026-03-05 17:10:15 +02:00
Aaro Koivukangas 7695315843 [Meta] keyBy is now used, letsgoo! 2026-03-05 16:31:09 +02:00
Aaro Koivukangas 0a292057cb [StudyGuidanceGroups] Add all degree programmes as options when selecting associated programmes for SGG 2026-03-05 16:24:12 +02:00
Aaro Koivukangas 0f5c672f5d [StudyGuidanceGroups] Change getCombinedProgrammeName to use switch case 2026-03-04 17:42:17 +02:00
joonas-a 8ec509aeed [General tab] before starting -> prior to x 2026-03-03 20:57:14 +02:00
joonas-a 7f4eab62a7 [General tab] Rephrase start date col 2026-03-03 20:10:35 +02:00
Aaro Koivukangas 6d82475696 [StudyGuidance] Correctly set initial year when adding starting year to group 2026-03-03 11:36:31 +02:00
Santeri RajamäkiandAaro Koivukangas bdcdd43230 [META] Remove toska sub- and prefixes from function names 2026-02-27 16:36:24 +02:00
Santeri RajamäkiandAaro Koivukangas 80e91d7bfb [Chore] Remove unused modules re-emerged after rebase 2026-02-27 16:36:24 +02:00
Santeri RajamäkiandAaro Koivukangas b15a56b177 [Frontend] Remove unused modules 2026-02-27 16:36:24 +02:00
Santeri RajamäkiandAaro Koivukangas c2684a4e8e [META] Remove apparent service provider specific code 2026-02-27 16:36:24 +02:00
Aaro Koivukangas 4ed8b3d2e9 [LanguageCenter] Add a course name/code filter 2026-02-27 14:34:59 +02:00
Santeri Rajamäki d6c55ed4f9 [Filters] Fix courses filter .courses being read-only 2026-02-27 13:58:13 +02:00
Aaro Koivukangas 97804b645e [ClassStats][CoursesOfClass] Fix filter buttons not working 2026-02-26 14:31:45 +02:00
Aaro Koivukangas 6b1c3e358a [CloseToGraduation] Add placeholders for text filters 2026-02-25 14:47:54 +02:00
Aaro Koivukangas 1935e36a3a [Meta] Fix graduated count in ProgressBars 2026-02-25 13:49:26 +02:00
Aaro Koivukangas 15d0acae13 [ClassStats][ProgressTab] Remove accidental text from link 2026-02-25 13:02:53 +02:00
Aaro Koivukangas ca31e0ed31 [ClassStats][ProgressTab] Add link to create progress criteria if it doesn't exist 2026-02-25 12:57:53 +02:00
joonas-a ab4c7c9ec3 [Cleanup] Replace HOPS -> studyplan and fix typo 2026-02-24 18:29:18 +02:00
joonas-a 6cd3e1bf4f [General tab] Enable study time months col for bsc programmes 2026-02-24 18:28:18 +02:00
joonas-a f836b3dacd [General tab] Better column grouping and ordering 2026-02-24 17:54:00 +02:00
joonas-a 066e1267f1 [Custom pop General tab] Add new cols if programme explicitly set 2026-02-24 17:11:17 +02:00
joonas-a eb29a4d261 [SGG General tab] Show new cols if programme set 2026-02-24 17:05:04 +02:00
joonas-a 85fc25eec7 [General tab] New cols: credits and courses attained before starting in programme
* that are in study plan for current programme
2026-02-24 16:57:39 +02:00
Santeri Rajamäki 27d8d73e3d [Frontend] Move color lists to theme palette 2026-02-24 11:08:31 +02:00
Aaro Koivukangas 4df339fa25 [PopulationStatistics][Courses of class] Rename 'Show all courses or modules' 2026-02-24 10:48:31 +02:00
Aaro Koivukangas 0b2349d1c1 [ClassStats][Progress] Fix missing course names/codes 2026-02-20 13:35:48 +02:00
Aaro Koivukangas 55f18a4cef [ClassStats][Progress] Table filtering works on all columns 2026-02-20 13:35:48 +02:00
Aaro Koivukangas 022efc22bd [ClassStats][Progress] Implement types to ProgressTable 2026-02-20 13:35:48 +02:00
Aaro Koivukangas a02e4adfa7 [ClassStats][Progress] Convert Progress tab to typescript 2026-02-20 13:35:48 +02:00
joonas-a 92db3af85f [General tab] remove excess Date casting 2026-02-19 17:07:21 +02:00
joonas-a e112bd3482 [General tab] New col: Study time in months for masters programmes 2026-02-19 16:44:16 +02:00
joonas-a 205a3079ad [Courses of class] quick and dirty too narrow column fix 2026-02-18 15:10:21 +02:00
Aaro Koivukangas 29a0447b3f [ClassStats][CoursesOfX] Change default Link visibility 2026-02-18 15:00:32 +02:00
Aaro Koivukangas ee65aac42a [ClassStats][CoursesOfPopulation] Add stats to modules 2026-02-18 14:52:02 +02:00
joonas-a 56c59cb86b [Frontend / Ooditable] Fix too large boxshadow in multiline headers 2026-02-18 13:42:59 +02:00
Aaro Koivukangas e404fe09e3 [E2E][ProgrammeCourses] Change more header names in tests 2026-02-18 10:27:23 +02:00
Aaro Koivukangas 613164b3da [E2E][ProgrammeCourses] Fix header name in e2e test 2026-02-18 10:01:53 +02:00
joonas-a 7446760798 [Frontend][Programme courses] Add final info texts 2026-02-17 20:01:56 +02:00
joonas-a a86a99ef96 [Programme courses] Update infobox 2026-02-17 19:46:57 +02:00
joonas-a df7f2b75da [Programme courses] Add docstring and sanity check 2026-02-17 18:32:54 +02:00
joonas-a 3f40896f3b [E2E] adjust numbers 2026-02-17 16:29:59 +02:00
joonas-a 975d37142a [DX][Nodemon] Fix backend hotreload on helix/nvim 2026-02-17 14:55:49 +02:00
Matti Luukkainen 6026f04fcf try optimize workflow 2026-02-16 00:50:41 +02:00
Matti Luukkainen 12182193e1 feat: if course population covers only one semester, indicate it in the name 2026-02-16 00:20:03 +02:00
Aaro Koivukangas ca8ad3ea04 [PopulationStatistics][Courses of class] Show all courses/modules changes text based on Course <-> Module toggle 2026-02-14 12:57:42 +02:00
Aaro Koivukangas b21693ba1d [PopulationStatistics][Courses of class] Fix table dissapearing if Course <-> Module tab returns 0 rows 2026-02-14 12:57:42 +02:00
Aaro Koivukangas b99d3ff649 [PopulationStatistics][Courses of class] Add a toggle to show only courses or modules in "Show all courses" view 2026-02-14 12:57:42 +02:00
joonas-a 2ca49bbe32 [Faculty][Degree programme overview] Remove special category switch
Instead show everything by default, also add "other" category.

Commit powered by AI btw
2026-02-13 18:31:47 +02:00
joonas-a 9015be8458 [Programme courses] Remove merging of AY courses 2026-02-13 17:26:20 +02:00
joonas-a 8dc95c7a89 [Programme courses] Rework credit categories 2026-02-13 16:56:39 +02:00
Santeri Rajamäki 4537bd792b [Frontend] Hide progress tab (once again) from multi year populations 2026-02-13 15:09:27 +02:00
Santeri Rajamäki fcea050b9d [FilterView] Cleanup 2026-02-06 16:35:42 +02:00
Santeri Rajamäki 75049cc641 [Frontend] Fix progress criteria calculation source 2026-02-06 15:59:40 +02:00
Santeri Rajamäki 58a8c38c53 [Frontend] FilterDegreeCoursesModal "Save & Close" is actually "Close" 2026-02-06 13:48:19 +02:00
Aaro Koivukangas 29723b15aa [Population][ClassStats] Handle class stat query-arrays better and add more tests 2026-02-06 12:39:22 +02:00
Aaro Koivukangas 77c3271c25 [ApiTests] Add tests for ClassStats with years and programme 2026-02-04 17:48:36 +02:00
Aaro Koivukangas eaee2bac5a [ApiTests] Add tests PopulationStatistics and a skipped test semester bug #4959 2026-02-04 17:05:02 +02:00
Santeri Rajamäki 5a2e8ed4b1 [Frontend] Cull icons from @/theme 2026-02-03 14:07:09 +02:00
Santeri Rajamäki 128303cc70 [Backend] Import logger to worker correctly 2026-02-03 13:32:30 +02:00
Santeri Rajamäki 92dea1c19d [Frontend] Rename SellIcon to TagIcon to reflect use case 2026-02-03 13:01:23 +02:00
Santeri Rajamäki 94669ef98a [Frontend] Cull icons from @/theme 2026-02-03 13:00:24 +02:00
Santeri Rajamäki 7036d1be28 [Frontend] Consolidate icons under @/theme folder 2026-02-03 12:30:55 +02:00
Aaro Koivukangas 0aff1afcd6 [Api-tests] Add basic permission tests for populations 2026-02-02 17:14:38 +02:00
Aaro KoivukangasandAaro Koivukangas 6db47e0568 [CI] Update toska registry login envs in pull_request action 2026-02-02 14:30:46 +02:00
Aaro KoivukangasandAaro Koivukangas 2b855654a6 [CI] Login to toksa docker registry in pull_request action 2026-02-02 14:30:46 +02:00
Aaro KoivukangasandAaro Koivukangas 5acf0d3e2c [CI] Remove incorrect path from pull_request action 2026-02-02 14:30:46 +02:00
Aaro KoivukangasandAaro Koivukangas 76f80c7828 [CI] Rename npm script to the correct one in pull_request action 2026-02-02 14:30:46 +02:00
Aaro KoivukangasandAaro Koivukangas c0f4acf681 [Meta] Update docker compose container_names to use '-' as separator 2026-02-02 14:30:46 +02:00
Santeri Rajamäki 31348527a0 [META] Use correct entry for dev tsx --watch 2026-02-02 12:35:29 +02:00
joonas-aandSanteri Rajamäki 773c768805 [E2E][Deprecation] Remove coverage plugin requires 2026-02-02 11:40:52 +02:00
joonas-aandSanteri Rajamäki f335d38d27 [Meta][Deprecation] Remove instrumentation stuff 2026-02-02 11:40:52 +02:00
Aaro KoivukangasandSanteri Rajamäki 892f64f492 [CI] Action typo fix 2026-02-02 11:40:52 +02:00
Aaro KoivukangasandSanteri Rajamäki a12a86ad0e [CI] Run api+e2e tests even when previous jobs are skipped 2026-02-02 11:40:52 +02:00
Aaro KoivukangasandSanteri Rajamäki 3ed549cc75 [CI] Run frontend and backend while e2e testing 2026-02-02 11:40:52 +02:00
Aaro KoivukangasandSanteri Rajamäki 809a42d947 [CI] Login to toska docker registry while setting up api tests 2026-02-02 11:40:52 +02:00
Aaro KoivukangasandSanteri Rajamäki ceabbd0628 [CI] Fixing the same error 3rd time 2026-02-02 11:40:52 +02:00
Aaro KoivukangasandSanteri Rajamäki d4485ee3dd [CI] Fix the same error again 2026-02-02 11:40:52 +02:00
Aaro KoivukangasandSanteri Rajamäki 214df46c4d [CI] Typo fix 2026-02-02 11:40:52 +02:00
Aaro KoivukangasandSanteri Rajamäki 39709d41c7 [CI] Api test and e2e tests now run even when previous steps are skipped 2026-02-02 11:40:52 +02:00
joonas-aandSanteri Rajamäki 54464f12da [Lint] fix import in wrong order 2026-02-02 11:40:52 +02:00
joonas-aandSanteri Rajamäki 3a752a1f83 [TS/lint] betterment and fix 2026-02-02 11:40:52 +02:00
Aaro KoivukangasandSanteri Rajamäki 12003a9c0b [CI] Multiple ci fixes 2026-02-02 11:40:52 +02:00
Aaro KoivukangasandSanteri Rajamäki d5f3f86b2b [CI] Change api tests to use docker-compose.ci.yml 2026-02-02 11:40:52 +02:00
Aaro KoivukangasandSanteri Rajamäki f37617754d [CI] Add check services run correctly as a dependency in actions to api+e2e 2026-02-02 11:40:52 +02:00
joonas-aandSanteri Rajamäki 1a41d3d8fc [Backend][Programme courses] Refactor
Preparing for upcoming change in logic
2026-02-02 11:40:32 +02:00
Aaro Koivukangas b83dae4ead [Meta] Change run.sh to use docker compose, not docker-compose 2026-01-28 18:13:44 +02:00
Aaro Koivukangas b1d75d53d1 [CI] Remove working-directory, not run from test action 2026-01-28 18:07:42 +02:00
Aaro Koivukangas 52569cb402 [CI] Fix incorrect path in test action 2026-01-28 18:06:15 +02:00
Aaro Koivukangas cd88b6c41c [CI, Updater] Trigger updater workflow only on release 2026-01-28 18:00:06 +02:00
Aaro Koivukangas ea5543f712 [CI] Typo fix 2026-01-28 17:58:47 +02:00
Aaro Koivukangas e0fb0ab788 [CI] Add node/npm to api-tests action 2026-01-28 17:57:56 +02:00
Aaro Koivukangas adda1c5020 [CI] E2E acually checks if needed jobs succeed or are skipped 2026-01-28 17:45:45 +02:00
Aaro Koivukangas c15a113bbd [CI] Typo fix 2026-01-28 17:36:50 +02:00
Aaro Koivukangas d52be3e425 [CI] Add api tests to pull requests 2026-01-28 17:33:37 +02:00
Aaro Koivukangas 5643fe0c07 [CI] Remove duplicate setup-node from test actions 2026-01-28 17:18:39 +02:00
Aaro Koivukangas fb673950b7 [CI] Add api-tests to ci 2026-01-28 17:18:39 +02:00
Aaro Koivukangas 4b052a1b13 [Dev] Add a script to run api-tests in docker 2026-01-28 17:18:39 +02:00
Aaro Koivukangas 34d0509572 [api-tests] Add @types/supertest 2026-01-28 17:18:39 +02:00
Aaro Koivukangas 5e8a5022cd [api-test] Add basic api test for changelog 2026-01-28 17:18:39 +02:00
Aaro Koivukangas a989f98524 [Changelog] Change to fake changelogs in test env 2026-01-28 17:18:39 +02:00
Santeri Rajamäki 6076b3d45a [Faculties] Relax programme and yearIndex requirements for ExpandableRow data 2026-01-27 16:40:22 +02:00
Santeri Rajamäki 3d4a09f2ef [OT] Add basic documentation for features 2026-01-27 15:02:41 +02:00
Santeri Rajamäki c6e40d27a4 [Frontend] Commit the rest of the changes 2026-01-27 15:00:59 +02:00
Santeri Rajamäki 016d2b6ec8 [Frontend] Remove lodash chain from project 2026-01-27 12:51:30 +02:00
joonas-a 2754874e67 [Eslint] Unban all 1 char long identifiers
Harmful and inconsistent rule making some reduces/maps/filters bloated
for absolutely no reason
2026-01-27 00:23:47 +02:00
joonas-a 64352a6e24 [Chore] remove few lodash chains
lodash-es chain method does not work well with vite in prod/e2e
2026-01-27 00:19:43 +02:00
Santeri Rajamäki 4f290d45eb [CI] Use node 24 before docker-compose in run-tests/action.yml 2026-01-26 20:31:17 +02:00
Santeri Rajamäki 27a75877bd [Backend] Use imports instead of require in languageCenterData 2026-01-26 20:31:17 +02:00
santeri0200andGitHub 3f9af3b5f6 Merge pull request #4930 from UniversityOfHelsinkiCS/renovate/major-github-artifact-actions
Update GitHub Artifact Actions (major)
2026-01-26 20:20:30 +02:00
Santeri Rajamäki 359a18be92 [Frontend] Remove already replaced package immer 2026-01-26 19:58:14 +02:00
Santeri Rajamäki b6a1e07fb4 [Frontend] Unify data passing in Courses of Class tabs 2026-01-26 19:49:29 +02:00
Santeri Rajamäki 63d8b12273 [Backend] Convert logger to typescript 2026-01-26 17:56:43 +02:00
Aaro Koivukangas 124b203629 [META] Update remaining package-lock.jsons 2026-01-26 16:13:52 +02:00
Aaro Koivukangas ada19dc3a8 [META] Update package-lock.json to node 24 2026-01-26 16:07:32 +02:00
Aaro Koivukangas eba2a775ae [CI] Acual change cypress node version to 24 2026-01-26 15:47:25 +02:00
Aaro Koivukangas f74342d25d [CI] Update cypress node version to 24 2026-01-26 15:31:24 +02:00
Santeri Rajamäki 8b133fae64 [Backend] Use correct import style for local files 2026-01-26 12:22:31 +02:00
Santeri Rajamäki 6100aaaa0b [META] Update node env from 22 to 24 2026-01-23 17:20:23 +02:00
joonas-a 5a2f7196a2 [Programme overview] Fix sorting by type col flaky
sometimes there could be undefined which messes things up
2026-01-23 17:19:53 +02:00
Santeri Rajamäki 53684200ca [E2E] Remove deprecated option experimentalStudio
The option was deprecated in 15.4.0, where the studio was made available for everyone.
2026-01-23 16:50:41 +02:00
joonas-a c3b24d3d5d [E2E] Fix degree programme overview tests 2026-01-23 16:45:02 +02:00
joonas-a 20b40ec5c8 [RTK api] remove unnecessary ampersand from route 2026-01-23 16:45:02 +02:00
Santeri Rajamäki 4244e70d68 [META] Update imports from lodash to lodash-es 2026-01-23 16:43:25 +02:00
Santeri Rajamäki 7480a063c3 [META] Change type packages from to 2026-01-23 16:42:23 +02:00
Santeri Rajamäki 37b4b31a9a [META] Upgrade lodash and lodash-es to the latest lodash-es variant 2026-01-23 16:33:39 +02:00
Santeri Rajamäki 159ccc3655 [META] Remove lodash from global dependencies 2026-01-23 16:22:00 +02:00
Santeri Rajamäki 380168b425 [Courses of class] Simplify module/course parse logic 2026-01-23 16:13:43 +02:00
Santeri Rajamäki 14ed9fa99a [Filters] Reword studyRightType filter logic 2026-01-23 15:58:50 +02:00
Santeri Rajamäki adcac058a6 [Filters] Reword graduatedFromProgramme filter logic 2026-01-23 15:58:50 +02:00
Santeri Rajamäki 03efb92936 [Filters] Simplify studentNumber filter actions 2026-01-23 15:58:50 +02:00
Santeri Rajamäki a0f3a1d4ab [Frontend] Simplify getStudentTotalCredits 2026-01-23 15:58:50 +02:00
joonas-a 27c3291049 [Meta] Delete unused file 2026-01-23 15:58:21 +02:00
joonas-a 706ada2b6f [Programme courses] Remove default sort as unneeded 2026-01-23 15:58:21 +02:00
joonas-a 41ff70cd45 [Programme overview] Add excel export 2026-01-23 15:58:21 +02:00
Aaro Koivukangas c065cfd2ed [E2E] Remove deprecated test 2026-01-23 15:36:40 +02:00
Aaro Koivukangas 9ac6b3db84 [Meta][ProgressCharts] Remove Include graduated toggle from all views 2026-01-23 15:27:51 +02:00
Santeri Rajamäki b3d7722d79 [Filters] Use precompute in admissionType filter instead of recalculating the same value each render 2026-01-23 15:00:50 +02:00
Santeri Rajamäki 197fee44ad [Docs] Partial update to resources.md 2026-01-23 15:00:50 +02:00
joonas-a 5c6f6ec689 [Programme courses] Default sort by total credits 2026-01-23 14:31:52 +02:00
Santeri Rajamäki 6fbd93b97d [META] Update Cypress minor version to 15.9 2026-01-23 14:20:22 +02:00
joonas-a 95b3695fc5 [Programme overview][Programme courses] Hide text overflow in name col 2026-01-23 14:15:44 +02:00
joonas-a bb547e2f51 [Programme overview][Programme courses] ditch MRT 2026-01-23 14:09:23 +02:00
Santeri Rajamäki 858f82d93e [Filters] Use mutate attribute instead of mutating in filter attribute 2026-01-23 13:54:24 +02:00
Aaro KoivukangasandGitHub 33cea662b9 Merge pull request #4948 from UniversityOfHelsinkiCS/progress-chart-graduated-category
Progress chart graduated category
2026-01-23 12:46:20 +02:00
Aaro Koivukangas fc75dfd2dd [StudyTrackStats+E2E] Include graduated back in all students in charts 2026-01-23 12:37:23 +02:00
Santeri Rajamäki f0fc96f07f [Faculties] Fix bcMs credit intervals to take in count the SRE based timings 2026-01-22 18:05:54 +02:00
joonas-a df08a30f2d [Programme overview] Programme courses show credits correctly
remove default sort for now until ooditable migration
2026-01-22 17:49:57 +02:00
Aaro Koivukangas 97d332d50e [E2E] maybe soon 2026-01-22 17:45:14 +02:00
Aaro Koivukangas 860488977f [E2E] 4th e2e fix 2026-01-22 17:26:46 +02:00
Aaro Koivukangas 6b1ca59559 [E2E] Fix e2e 3rd time 2026-01-22 17:15:29 +02:00
Aaro Koivukangas 071038a1fa [E2E] More e2e fixes 2026-01-22 17:01:17 +02:00
Aaro Koivukangas 392e28010d [E2E] Fix e2e test 2026-01-22 16:38:58 +02:00
Aaro Koivukangas c1c7d3d254 [StudyTrackStats] Exclude graduated from credit counts 2026-01-22 16:34:02 +02:00
Aaro Koivukangas c35701bb5d [University+Faculty] Add graduated as category to ProgressCharts 2026-01-22 12:00:14 +02:00
Aaro Koivukangas b7e8b6fad1 [CI] manual lint fixes 2026-01-19 14:13:25 +02:00
Aaro Koivukangas f355577c8a [StudyProgramme] Add graduated as category to ProgressCharts 2026-01-19 13:22:29 +02:00
joonas-a 2bd78a587a [E2E] Fix graduation time calculation 2026-01-12 17:01:54 +02:00
joonas-a 294b624e5a [Graduations] Allow 4 absences if study right start before 1-8-2015 2026-01-12 16:20:20 +02:00
joonas-a 2eacb56ce2 [Graduations] Add 2 free absences
First two non-statutory absences will no longer drain study time
2026-01-12 16:20:20 +02:00
Santeri Rajamäki e384d734a6 [Filters][Grade] Use fallback if selectedGrade fails to show up 2026-01-12 14:29:17 +02:00
joonas-a a9ab7621bf [Programme overview] Fix labels in studies after programme chart 2026-01-12 13:19:47 +02:00
joonas-a 98ea010d64 [Graduations] Route typings 2026-01-12 13:11:45 +02:00
Aaro Koivukangas 304b16c73c [CourseStats] Fix incorrect precomputed use 2026-01-12 12:40:30 +02:00
Aaro Koivukangas 82a726190e [CourseStats] Get course name for title from FilterView 2026-01-12 12:15:02 +02:00
Santeri Rajamäki af21972e88 [Faculty] Progrss tab credit accumulation table for bachelor+master now shows credit limits correctly 2026-01-09 18:39:52 +02:00
Santeri Rajamäki cf7e34f4ce [Faculty] Add programme level color coding 2026-01-09 17:30:57 +02:00
Santeri Rajamäki 57cff95ba3 [Frontend] Swap AssignmentCheckedIcon with SwapHorizIcon 2026-01-09 16:43:20 +02:00
Santeri Rajamäki b7c7a24a3f [PopulationStats][Courses] Add transferred icon to APPROVED credits 2026-01-09 16:43:20 +02:00
joonas-a 0769b38b98 [Routes/programme overview] Type creditstats 2026-01-09 15:18:44 +02:00
Santeri Rajamäki 6557f7db55 [Populations] Add enrollments to course stats before credits 2026-01-09 14:42:39 +02:00
joonas-a 9e6b9be0e2 [Backend] Type creditstats 2026-01-09 14:32:43 +02:00
joonas-a 52b92e4cb6 [Routes] Type basicstats 2026-01-09 12:35:16 +02:00
Santeri Rajamäki e23cb37121 [Backend] Rewrite faculty route types 2026-01-07 18:31:29 +02:00
Aaro Koivukangas 1c6b9a60ed [StudyTrack] Add programme code and study track name to programme {sub,}title 2026-01-07 18:04:08 +02:00
Santeri Rajamäki 30e7f832f7 [Filters][StudyTrack] Add ability to read student study tracks 2026-01-07 15:48:18 +02:00
Aaro Koivukangas 62b5009391 [CI] Fix slackbot link url 2026-01-07 13:06:43 +02:00
joonas-a 6dd7dbc803 [Faculties/University] Remove legacy datafields
lastUpdated and Status have not been in use for a long time
2026-01-07 12:10:07 +02:00
Aaro Koivukangas e8f4ed8ae6 replace studytrack status 'inactive' to 'passive' 2026-01-07 11:26:44 +02:00
Santeri Rajamäki f4678a8714 [E2E][CourseStats] Use correct logic in summary checks 2026-01-03 17:33:39 +02:00
Santeri Rajamäki 25730450af [E2E][CourseStats] Use correct course name 2026-01-03 17:29:09 +02:00
Santeri Rajamäki e329f6e311 [E2E][CourseStats] Use BSCS instead of TKT to fetch course name from test data 2026-01-03 17:27:13 +02:00
Santeri Rajamäki abd48cb8c1 [Frontend] Remove unused redux endpoint query types 2026-01-03 17:07:06 +02:00
Santeri Rajamäki 033ab8f328 [CourseStats] Remove unnecessary checks from "passed" condition 2026-01-03 17:02:40 +02:00
Santeri Rajamäki 8fdac9827b [CourseStats] Fix grade parsing in yearly stats counter 2026-01-03 17:02:10 +02:00
Santeri Rajamäki 8baada888b [CourseStats] Readd credittypecode to credits 2026-01-03 17:01:09 +02:00
Santeri Rajamäki 01e6317386 [Frontend] Remove unused RTKApi endpoint 2026-01-03 16:42:40 +02:00
Santeri Rajamäki 17cff4dc5f [CourseStats] Show population button uses the same logic as links 2026-01-03 16:16:14 +02:00
Santeri Rajamäki 182a35079b [CourseStats] Remove unecessary yearly statistics call 2026-01-03 15:47:29 +02:00
Santeri Rajamäki 422e2776e3 [CourseStats] Set primary course as the first alternative 2026-01-03 15:46:21 +02:00
Santeri Rajamäki 2e6132e09c [CourseStats] CourseYearlyStats clean up 2026-01-03 14:57:55 +02:00
Santeri Rajamäki 885de8b64c [Backend] Fix auto added path 2026-01-02 16:40:14 +02:00
Santeri Rajamäki c632173a86 [CourseStats] Avoid data refresh on tab change 2026-01-02 16:31:48 +02:00
Santeri Rajamäki 2dc1797bc4 [Shared] Add courseExclusions route 2026-01-02 16:30:55 +02:00
Santeri Rajamäki bba918812c [CourseExclusion] Add rights check to exclusion post and delete 2026-01-02 12:53:09 +02:00
Santeri Rajamäki b8f77275ff [Importer] Add timeout to importer client and remove Promise.races 2026-01-02 12:23:03 +02:00
Santeri Rajamäki 39fdeb5321 [CloseToGraduation] Show loading section instead of empty table on fetch 2026-01-02 12:22:59 +02:00
Santeri Rajamäki edbd41afc7 [CloseToGraduation] Share backend types with frontend 2026-01-02 12:22:56 +02:00
Santeri Rajamäki 16c9580ed4 [Changelog] Redo types and first fetch logic 2026-01-02 12:22:51 +02:00
joonas-a 82b4e99f7b [Frontend] Fix atmospheric sciences
In faculties and in class overview
2025-12-19 14:31:36 +02:00
Santeri Rajamäki 1eb1527260 [Updater] Use selectFromByIds instead of hardcoded version to find missing course codes 2025-12-18 17:18:07 +02:00
Santeri Rajamäki 82a4d2a08d [Updater] Add JSDocstrings to worker db index 2025-12-18 17:15:59 +02:00
Santeri Rajamäki 91fa0106f5 [ExcelExport] Fix semester enrollments in all FilterViews 2025-12-17 19:33:32 +02:00
Santeri Rajamäki ad8c51fce6 [ProgressTab] Add fallback to header attribute to avoid crashing 2025-12-17 18:31:12 +02:00
Santeri Rajamäki 4d012699e2 [Filters][Date] Reword infotext 2025-12-17 17:38:34 +02:00
Santeri Rajamäki a5af4ffcae [CourseStats] Partially rewrite controlflow logic 2025-12-17 17:06:19 +02:00
Santeri Rajamäki cab573eebb [Backend] Fix invalid import
It seems that types are never actually resolved
2025-12-17 15:40:56 +02:00
Santeri Rajamäki 5d3c5c99c5 [CourseStats] Simplify alternatives type 2025-12-17 15:27:24 +02:00
Santeri Rajamäki 577de97741 [Filters][Date] Change filter title 2025-12-16 10:21:01 +02:00
Santeri Rajamäki 60d95e45d2 [Filters][Date] Change course credits to course participation 2025-12-16 10:08:19 +02:00
renovate[bot]andGitHub 67ab0db6d1 Update GitHub Artifact Actions 2025-12-12 22:57:34 +00:00
Santeri Rajamäki 34ae6f279b [DegreeProgramme] Fix visibility chip for module courses 2025-12-12 17:08:04 +02:00
joonas-a 1c91554a3b [Frontend] Theme refresh
Increased contrast and consistency

Also moved unihow export to ooditable toolbar in custom population
2025-12-12 16:54:18 +02:00
Santeri Rajamäki e53c0c2de4 [CourseStats] Move Redux hook to useState 2025-12-12 14:25:41 +02:00
Santeri Rajamäki 25deee6016 [CourseStats] Use full data in summary tab 2025-12-12 14:02:15 +02:00
Santeri Rajamäki f8cc05afda [Frontend][Highchart] Remove unused (due to data drawn) start date from CreditAccumulation 2025-12-12 14:02:15 +02:00
joonas-a 9b46c9f43f [Courses of population] Use new number selector
Fixes debounce etc
2025-12-12 13:38:47 +02:00
Santeri Rajamäki 654a2a6604 [E2E][CourseStats] Use correct column for grade matching 2025-12-12 10:00:27 +02:00
Santeri Rajamäki 27a9ac8225 [Frontend] Add retry on error card to Population statistics 2025-12-12 09:57:54 +02:00
Santeri Rajamäki b8d93af641 [Filters] Reword date filter mutate function 2025-12-12 09:23:48 +02:00
joonas-a 33ee9d142d [Course pop] New col to general tab: associated programme
Same programme that is used in computing the programme distribution tab
2025-12-11 18:07:14 +02:00
joonas-a 25ece19e36 [Course pop] lang dist: rephrase error text 2025-12-11 18:07:14 +02:00
Santeri Rajamäki 39ac7a2665 [Frontend] Introduce mutation as a standalone method in filters 2025-12-11 16:20:35 +02:00
joonas-a fb35bc0c2b [Course search] Show no results if no results 2025-12-11 15:32:35 +02:00
joonas-a ce08567bb4 [Course search] make multisearch notice fit in with rest of the app 2025-12-11 11:52:23 +02:00
joonas-a 89f08dabc5 [Course search] remove arbitrary limit in multisearch
limit of 99999 was nothing but clutter
2025-12-11 11:31:51 +02:00
Santeri Rajamäki d6f414dc71 Change comment to override GH issue 2025-12-10 21:46:02 +02:00
Santeri Rajamäki d9a8a87c2f [Frontend] Make Class statistics Students ellipsed spans respect set maxWidth 2025-12-10 20:06:46 +02:00
Santeri Rajamäki 2bab5c6ef0 [DegreeCourses] Use course.code instead of random id 2025-12-10 19:52:31 +02:00
Santeri Rajamäki 8cf9eae693 [Frontend] Make Class statistics Students study track respect set maxWidth 2025-12-10 19:39:59 +02:00
Santeri Rajamäki 535cd08593 [Frontend] Force courses of class percentages toFixed(2) 2025-12-10 19:32:57 +02:00
Santeri Rajamäki f3fd84598a [Frontend] Fix course filter toggle texts 2025-12-10 19:30:14 +02:00
Santeri Rajamäki ceb813636a [Backend] Simplify getCoursesByNameAndOrCode function 2025-12-10 19:13:26 +02:00
joonas-a c51f90ab1a Hotfix LSP powered file path 2025-12-10 19:12:58 +02:00
Santeri Rajamäki 12a0a38ce0 Hotfix 2025-12-10 19:05:01 +02:00
Santeri Rajamäki ef25eeae40 [E2E] Course statistics search form should use main course code for selection 2025-12-10 19:05:01 +02:00
joonas-a 70b10480a4 [Course search] fix substitutions fetched by not-main-course sometimes 2025-12-10 19:03:02 +02:00
joonas-a 11bfd0b8c5 [Course search] Add switch to include special courses
e.g. courses created from credit transfer
2025-12-10 18:25:14 +02:00
Santeri Rajamäki a45ec5537e [Backend] Fix population array to set conversion 2025-12-10 18:03:00 +02:00
Santeri Rajamäki 165fff0163 [Backend] Rework population obfuscation function 2025-12-10 18:02:59 +02:00
Santeri Rajamäki a6df2e8d50 [Filters] Retype course filter cards 2025-12-10 18:02:59 +02:00
Santeri Rajamäki bfeb3ef9b1 [Frontend] Extend FilteredCourse type 2025-12-10 18:02:59 +02:00
Santeri Rajamäki 4fdbba9fe1 [Filters] Remove course filter card population check 2025-12-10 18:02:59 +02:00
Santeri Rajamäki fe52b06696 [Backend] Remove obfuscation from course population data 2025-12-10 18:02:59 +02:00
Santeri Rajamäki 54e60279f7 [Shared] Remove unused module courseData 2025-12-10 18:02:59 +02:00
Santeri Rajamäki 2ef35ced6b [Meta] Rework class population data 2025-12-10 18:02:59 +02:00
Santeri Rajamäki 386290288f [Backend] Remove obfuscation from class population query 2025-12-10 18:02:59 +02:00
joonas-a b1d33dc64c [Course search] Fix sort order 2025-12-10 17:38:38 +02:00
joonas-a 7d4c8c99bf [Course search] Dont show special courses by default
Courses e.g. from result of hyväksilukeminen with random a-z - 0-9
codes, and other courses with no "main_course_code" in db
2025-12-10 17:00:36 +02:00
joonas-a ed7e03169a [Course stats] add/correct typing 2025-12-05 15:18:37 +02:00
joonas-a eaa9a17a49 [Lint] silence warning in case component still needed 2025-12-05 13:55:43 +02:00
joonas-a 6c463b6b3b [Cleanup] Delete unused tooltips 2025-12-05 13:41:36 +02:00
joonas-a 79c04ba2a6 [Course stats] style + tsx credit gains 2025-12-05 13:41:04 +02:00
joonas-a b0c4d8bb8c [Frontend] Add dummy facultyCode to NO_PROGRAMME 2025-12-05 13:40:18 +02:00
joonas-a cf0796bb88 [Course stats] Finish courses of population styling/debounce 2025-12-04 20:24:25 +02:00
joonas-a 9a23799972 [Frontend] finish StudentAmountLimiter component 2025-12-04 20:15:18 +02:00
joonas-a 010829b96c [Frontend] New component for numerical TextField
Has internal state and works with debounce
2025-12-04 20:05:15 +02:00
Santeri Rajamäki b921bc0891 [Populations] Add secondary email to other filterview student > general tab excels 2025-12-02 10:14:01 +02:00
Joonas AhovalliandGitHub aa6ce4ab28 Remove SortableTable documentation link
Removed link to SortableTable documentation from README.
2025-12-02 09:55:35 +02:00
joonas-a b5b211e750 [Frontend] Use same old/new programme filtering logic everywhere 2025-12-01 20:18:45 +02:00
joonas-a c114121a7a [General tab] filter out inactive study rights from primary programme column
This is to match the behaviour of previously changed programme
distribution panel
2025-12-01 20:00:31 +02:00
joonas-a 31eb9b2101 [Backend] Make student obfuscation fn return dummy studyplans array 2025-12-01 17:15:16 +02:00
Santeri Rajamäki 55c1a5f93b [Backend] Rename isOpenUniCourseCode to getOpenUniCourseCode to reflect what the function is actually used for 2025-12-01 17:12:06 +02:00
Santeri Rajamäki 2311a9c027 [E2E][SGG] Replace mirrored state with same the label text 2025-12-01 16:56:01 +02:00
Santeri Rajamäki 62b7864ffe [E2E][SGG] Fix contains dated field 2025-12-01 16:45:36 +02:00
Santeri Rajamäki f1000a8f25 [E2E] Readd nav-bar cypress tag to ToolBar 2025-12-01 16:41:58 +02:00
Santeri Rajamäki 4e566827f7 [Students] Fix backend crash when return value is null instead of valid value 2025-12-01 15:36:32 +02:00
Santeri Rajamäki 56f5f730c4 [CustomPopulation] Add secondary email to custom population excel 2025-12-01 15:29:20 +02:00
Santeri Rajamäki 556999614c [PopulationStatistics] Add secondary email column index 2025-12-01 15:29:20 +02:00
joonas-a 1c4e3721c6 [Custom population] Update distribution infobox accordingly 2025-11-28 16:36:51 +02:00
joonas-a 7032902c75 [Lint] Alphabetical ordering of props (???) 2025-11-28 16:21:08 +02:00
joonas-a e3928ebd5f [Programme distribution] remake component in custom/course population views 2025-11-28 16:15:14 +02:00
joonas-a 917931378a [Meta] Delete unused css file 2025-11-26 13:19:24 +02:00
joonas-a ff93d54470 [Course pop] Add missing margin 2025-11-25 17:47:41 +02:00
Santeri Rajamäki 402ee672a1 [StatisticsOf] Readd studyright_id to enrollments 2025-11-25 17:46:22 +02:00
joonas-a d5fc584d93 [Frontend] New component StyledCell to use with StyledTable 2025-11-25 16:46:31 +02:00
joonas-a 11a390128f [Course stats] Revisit language distribution 2025-11-25 16:08:19 +02:00
joonas-a f80ef6fb40 [Course stats] Reword tooltip 2025-11-25 16:08:19 +02:00
joonas-a 78e31f0a3e [Course stats] Revisit grade distribution 2025-11-25 16:08:19 +02:00
Santeri Rajamäki 651b3b5c0d [Filters] Clarify studyright status filtering 2025-11-25 16:07:24 +02:00
joonas-a 66e666c99a [Courses of class] UI tweaks 2025-11-25 15:29:37 +02:00
Santeri Rajamäki 464cdff246 [META] Fix lint errors 2025-11-25 15:26:43 +02:00
Santeri Rajamäki 58c35f21b0 [Students] Fix falsy query skip logic 2025-11-25 15:26:43 +02:00
Santeri Rajamäki ae1c7765a1 [StatisticOf] Fix mock date 2025-11-25 15:26:43 +02:00
joonas-a 59e2258da3 [SGG] Make credit cutoff button a switch 2025-11-25 14:34:06 +02:00
joonas-a 5f8dc44958 [Frontend] Remake toggleWithTooltip component 2025-11-25 14:25:12 +02:00
joonas-a c8b4586ea1 [Class stats] Credit accumulation panel betterments 2025-11-25 13:49:21 +02:00
joonas-a b04859639f [Panelview] Make more consistent 2025-11-25 13:25:26 +02:00
joonas-a 8fba294dd8 [Progress tab] Add student name toggle 2025-11-21 18:41:05 +02:00
joonas-a 714f3bc953 [Tags tab] center spinner + add name toggle 2025-11-21 18:39:31 +02:00
joonas-a 7247049564 [Class stats] Move table actions to OT toolbar 2025-11-21 18:21:15 +02:00
Santeri Rajamäki ff6646ebb4 [Frontend] Remove more wrapping divs 2025-11-21 17:03:10 +02:00
joonas-a d77c735030 [Ooditable] Integrate all excel buttons 2025-11-21 16:24:24 +02:00
joonas-a aaefbfa9fe [Language center view] revisit styles 2025-11-21 16:24:24 +02:00
joonas-a 7ec487412f [Ooditable] Integrate excel export 2025-11-21 16:24:24 +02:00
joonas-a f7c84ca06f [Ooditable] Delete unused css 2025-11-21 16:24:24 +02:00
joonas-a bc46a18151 [Ooditable] Add missing docstring 2025-11-21 16:24:24 +02:00
Santeri Rajamäki fcd8eb9284 [Frontend] Use CSS variables in themes 2025-11-21 16:19:40 +02:00
Santeri Rajamäki d0c52f301f [Frontend] Convert Container components to PageLayout and fix erroneous trees 2025-11-21 16:18:45 +02:00
Santeri Rajamäki a606c21cd3 [Frontend] Separate Layout component from App state 2025-11-21 16:10:50 +02:00
Santeri Rajamäki dc496618f3 [Backend] Update util types 2025-11-21 14:48:02 +02:00
Santeri Rajamäki 099e60b617 [Backend] Update student routing types 2025-11-21 14:37:49 +02:00
Santeri Rajamäki c39fdcd77a [Backend] Update course routing types 2025-11-21 14:36:50 +02:00
Santeri Rajamäki 907e24e26e [Shared] Add uniq util function 2025-11-21 13:38:43 +02:00
Santeri Rajamäki 664d8fb83a [Footer] Make DOM shallower and adapt design to NavBar 2025-11-21 11:01:38 +02:00
Santeri Rajamäki 3618d318eb [NavBar] Make DOM shallower 2025-11-21 10:38:52 +02:00
joonas-a cc09af092f [Backend] Remove benchmarking console.logs 2025-11-20 14:47:28 +02:00
Santeri Rajamäki 48a77d2b12 [META] Run install:local 2025-11-18 13:30:21 +02:00
Santeri Rajamäki 99a598e773 [META] Update node requirement to version 24 and update package-locks 2025-11-18 13:27:31 +02:00
Santeri Rajamäki c9455d2fde [Frontend] Make population course amount limiter consistent with debounced state 2025-11-18 13:04:09 +02:00
Santeri Rajamäki a343b2460a [Frontend] Fix :first-child rendering error 2025-11-18 12:37:45 +02:00
Santeri Rajamäki 7ca612364b [Backend] Remove unused type import
Validator types are used with sequelize-typescript, but they are imported at least by sequelize.
No explicit import is needed.
2025-11-18 12:17:24 +02:00
Santeri Rajamäki 6bed41dd9e [Frontend] Convert population roots to typescript 2025-11-18 12:06:27 +02:00
joonas-a 392d5ea3b0 [Frontpage] Remove mention of service break 2025-11-18 09:04:43 +02:00
Santeri Rajamäki 537ab19fb6 [CoursePopulation] Use PageTitle title correctly 2025-11-14 15:56:40 +02:00
Santeri Rajamäki 74b566f574 [Backend] Rework population shared maps 2025-11-14 15:31:30 +02:00
Santeri Rajamäki 509fdeadfa [Backend] Rewrite parts of uncached user login 2025-11-14 14:57:57 +02:00
Santeri Rajamäki 1f13389954 [Frontend] Remove unused SegmentContainer 2025-11-13 17:50:00 +02:00
Santeri Rajamäki e699139f4b [Frontend] Adapt FilterViews to use PageTitle and PageLayout 2025-11-13 17:22:50 +02:00
Santeri Rajamäki 9f9786a3bb [E2E] Fix Course of Class expansion tests 2025-11-13 14:55:31 +02:00
Santeri Rajamäki 7c79d23a47 [Frontend] Replace currently invalid font-weights 2025-11-13 14:36:23 +02:00
Santeri Rajamäki 85eb3c9634 [Frontend] Add Roboto 500 to the project
Font-weight 500 is used by MUI for weight "Medium"
2025-11-13 14:30:20 +02:00
Santeri Rajamäki 25f7f46a36 [Frontend] Replace default Roboto import with the correct weighted ones 2025-11-13 14:10:11 +02:00
Santeri Rajamäki 569e411f7c [DOCS] Remove references to Semantic UI 2025-11-13 04:21:33 +02:00
Santeri Rajamäki d7f8024d27 [Frontend] Add explicit Roboto font import
The font was loaded from google servers before.
2025-11-13 04:18:54 +02:00
Santeri Rajamäki ca429a7b68 [Frontend] Remove "New user interface" notification 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 01bc1f11f4 [Frontend] Move FacultyProgress and FacultyProgressTable components from components/material to components/Faculties 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 50aad14a39 [Frontend] Move ProgrammeDropdown component from components/material to components/CourseStatistics 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 76624da6c6 [Frontend] Merge HelpInfoCard component from components/material into the only parent 2025-11-13 04:18:54 +02:00
Santeri Rajamäki f7eba6b2d7 [Frontend] Move TableHeaderWithTooltip component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 6aa7762acb [Frontend] Merge CheckIconWithTitle component from components/material into the only parent 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 1f61ab7b81 [Frontend] Move DataTable component from components/material to components/StudyProgramme 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 5681ef9f97 [Frontend] Move CurriculumPicker component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 7e903456ae [Frontend] Move MRTExcelExport component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki ece089b5b6 [Frontend] Move FilterOldProgrammesToggle component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 177b2c39a5 [Frontend] Merge RightsNotification component from components/material into the only parent 2025-11-13 04:18:54 +02:00
Santeri Rajamäki f399041163 [Frontend] Move StudentNameVisibilityToggle component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 7f238c5ccb [Frontend] Move StudentInfoItem component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki ce41a9a3e3 [Frontend] Move MedianTimeBarChart component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki a117ce0fd4 [Frontend] Move ProgressBarChart component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki fe5418575e [Frontend] Move StackedBarChart component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 08adaed6d7 [Frontend] Move LineGraph component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki fc8d59af72 [Frontend] Move BreakdownBarChart component from components/material to components/GraduationTimes
BreakdownBarChart is moved and renamed as BreakdownBarChartV2 as the file contains intent to merge both BreakdownBarChart files.
2025-11-13 04:18:54 +02:00
Santeri Rajamäki 9e7c84d053 [Frontend] Move TotalsDisclaimer component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 3e69cfae20 [Frontend] Move MockButton component from components/material to components/Users 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 69b9301c17 [Frontend] Move PageTitle component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 4cae8eaac2 [Frontend] Move ProgressBar component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 6ea3f9a922 [Frontend] Move UpdateButton component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki ea7d4b1554 [Frontend] Move SearchHistory component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 426658f8b0 [Frontend] Move ExpandableRow component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 72a6778ae5 [Frontend] Move RoleChip component from components/material to components/Users 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 8c2a2d113e [Frontend] Move StyledAccordion component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 27dbf0b575 [Frontend] Move StyledTable component from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 2ff9ae7843 [Frontend] Move loading components from components/material to components/Loading 2025-11-13 04:18:54 +02:00
Santeri Rajamäki ee8a7b016a [Frontend] Move infobox component from components/material to components/InfoBox 2025-11-13 04:18:54 +02:00
Santeri Rajamäki c7831b7f66 [Frontend] Move navigation components from components/material to components/NavigationBar 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 5895021a48 [Frontend] Move graduation time components from components/material to components/GraduationTimes 2025-11-13 04:18:54 +02:00
Santeri Rajamäki ced54e7a9b [Frontend] Move section components from components/material to components/Section 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 25cd18e025 [Frontend] Move footer components from components/material to components/Footer 2025-11-13 04:18:54 +02:00
Santeri Rajamäki c08b2cf37c [Frontend] Move status notification components from components/material to components/StatusNotifications 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 2da42ed93e [Frontend] Move error components from components/material to components/ 2025-11-13 04:18:54 +02:00
Santeri Rajamäki f2e7893c60 [Frontend] Move toggle components from components/material to components/common/toggle 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 5b348c37db [Frontend] Move link components from components/material to components/common 2025-11-13 04:18:54 +02:00
Santeri Rajamäki bb6ca028c7 [Frontend] Remove class items from custom.css 2025-11-13 04:18:54 +02:00
Santeri Rajamäki 340d46c739 [Frontend] Remove Semantic UI 2025-11-13 04:18:53 +02:00
Santeri Rajamäki 52f77935fc [Frontend] Remove Semantic UI icon from Highcharts charts 2025-11-13 04:17:50 +02:00
Santeri Rajamäki d35cbc1085 [Frontend] Remove SortableTable 2025-11-13 04:17:50 +02:00
Santeri Rajamäki 6f17bb002a [CoursesOfPopulation] Context cleanup 2025-11-13 04:17:50 +02:00
Santeri Rajamäki 692e704108 [Frontend] Make filter toggle button small 2025-11-13 04:17:50 +02:00
Santeri Rajamäki 31c45581e0 [Teachers] Convert SortableTable to OodiTable 2025-11-13 04:17:50 +02:00
Santeri Rajamäki cb3ed86231 [OpenUni] Convert SortableTable to OodiTable 2025-11-13 04:17:50 +02:00
Santeri Rajamäki de85a38abe [ProgressTab] Convert SortableTable to OodiTable 2025-11-13 04:17:50 +02:00
Santeri Rajamäki e28a1e5e6f [CoursesOfClass] Style "when passed" OodiTable export row 2025-11-13 04:17:50 +02:00
Santeri Rajamäki 66bd4be4ed [CoursesOfClass] Convert "when passed" SortableTable to OodiTable 2025-11-13 04:17:50 +02:00
Santeri Rajamäki 37fbb5cbb3 [CoursesOfClass] Remove on-demand rendering from tabs 2025-11-13 04:17:50 +02:00
Santeri Rajamäki c31fac4419 [CoursesOfClass] Convert pass/fail SortableTable to OodiTable 2025-11-13 04:17:50 +02:00
Santeri Rajamäki 7760f60ff5 [CoursesOfClass] Convert grades SortableTable to OodiTable 2025-11-13 04:17:50 +02:00
Santeri Rajamäki 7b65539873 [Frontend] Remove unnecessary imported package
Scheduler is a react internal package, never used in the project.
2025-11-12 23:23:31 +02:00
joonas-a fe4bda7ed8 [Backend] Add temp logging to statisticsOf 2025-11-11 18:12:12 +02:00
joonas-a 6ccaec1643 [Backend] Add missing function 2025-11-11 18:03:32 +02:00
joonas-a df01c3b693 [Backend] make class stat req mankeling faster 2025-11-11 17:37:23 +02:00
joonas-a b7235ff4b4 HOTFIX: capital O in OODIkone 2025-11-10 18:10:30 +02:00
joonas-a 84d8a6bfec [Frontpage] Add announcement on service break 2025-11-10 18:10:29 +02:00
joonas-a 24ccb9de58 [E2E / lang center] Fix tests + remove useless test 2025-11-10 17:50:44 +02:00
joonas-a faac3e968e [E2E] Add missing cy selectors 2025-11-10 17:42:22 +02:00
Santeri Rajamäki 3407af8215 [ColorizedCourseTable] Add semester length check to "By semesters" table column memoization 2025-11-10 17:24:50 +02:00
Santeri Rajamäki 94d289194a [ColorizedCourseTable] Move total column in "By semesters" table 2025-11-10 17:17:24 +02:00
Santeri Rajamäki 39c21b261a [ColorizedCourseTable] Fix column pinning in "By faculties" table 2025-11-10 17:01:09 +02:00
Santeri Rajamäki 74ae6f3d46 [ColorizedCourseTable] Move total column in "By faculties" table 2025-11-10 17:00:32 +02:00
Santeri Rajamäki 3f9ce90834 [ColorizedCourseTable] Add totla sorting to "By faculties" table 2025-11-10 16:57:46 +02:00
Santeri Rajamäki b235fa7883 [SortableTable] Remove unused features 2025-11-10 15:40:46 +02:00
joonas-a e2070248be [E2E / completed courses] flakiness reduce 🪄 2025-11-10 15:28:44 +02:00
Santeri Rajamäki 97068be78e [ColorizedCourseTable] Convert "By faculty" table to OodiTable 2025-11-10 15:26:56 +02:00
joonas-a 53dd55f874 [E2E / teachers] Fix tests 2025-11-10 15:13:04 +02:00
joonas-a 3c99a30a8c [E2E / open uni] skip all tests 2025-11-10 15:05:39 +02:00
joonas-a 5fd94fd0d0 [E2E / SGG] Fix tests 2025-11-10 15:02:04 +02:00
joonas-a c36ce33f7f [E2E / class stats] fix tests 2025-11-10 14:55:06 +02:00
joonas-a ff6bcd6658 [E2E / custom population] Fix tests 2025-11-10 14:00:30 +02:00
joonas-a 53e75b6164 [E2E filters] fix tests 2025-11-10 12:43:01 +02:00
joonas-a a65c4d448b [Custom population] facelift + refactor + TS + MUI 2025-11-10 12:18:11 +02:00
joonas-a f9a4d85ce2 [Frontend] Type betterments to class stats views*
*with a twist
2025-11-10 12:18:11 +02:00
Santeri Rajamäki c610a53168 [ColorizedCourseTable] Convert "By semester" table to OodiTable 2025-11-07 13:37:44 +02:00
Santeri Rajamäki ad40e02442 [OodiTable] Add theming for zebra stripes 2025-11-07 13:26:01 +02:00
Santeri Rajamäki 6b6e070deb [ColorizedCourseTable] Update getColor to use simpler logic 2025-11-07 13:13:03 +02:00
Santeri Rajamäki 253f31d7d0 [OodiTable] Add Zebra stripe feature 2025-11-07 13:06:11 +02:00
joonas-a 551775bcec [Course population] Fix broken layout 2025-11-04 15:49:54 +02:00
joonas-a 70e8d6f033 [Class stats] style unification vol 1 2025-11-04 15:44:44 +02:00
Santeri Rajamäki ea505b5d84 [Frontend] Hide ColorizedCoursesTable tabs when only one option is available 2025-11-03 12:17:35 +02:00
Santeri Rajamäki 344a3c00ef [SortableTable] Remove actions entry from README 2025-11-03 12:09:32 +02:00
Santeri Rajamäki b29984f589 [SortableTable] Remove unnecessary features 2025-11-03 12:08:19 +02:00
Santeri Rajamäki 0ad6d6f9a2 [Frontend] Fix validation to spec and move util to shared 2025-11-03 11:47:21 +02:00
Santeri Rajamäki 6bf1d9a590 [Frontend] Remove unused util 2025-11-03 11:36:17 +02:00
Santeri Rajamäki 430fc97917 [Teachers] Convert Sortabletable to MUI table 2025-11-03 11:35:17 +02:00
joonas-a 7ba2d3489e [StyledMessage] Correct prop naming 2025-10-31 17:55:47 +02:00
joonas-a 1e68c959b9 [SGG] Fix redundant loading spinner logic 2025-10-31 17:54:02 +02:00
joonas-a 55b900d1f6 [SGG overview] Migrate to MUI+TS 2025-10-31 17:52:46 +02:00
joonas-a 1ff637ab9d [Types] fix GroupsWithTags type 2025-10-31 13:53:04 +02:00
joonas-a cff045fd3b [SGG] Fix content incorrectly centered in panels 2025-10-30 18:11:43 +02:00
joonas-a 7fce9fd5be [SGG] Delete unused css import 2025-10-30 17:58:09 +02:00
joonas-a a2569d1525 [SGG] Delete unused css file 2025-10-30 17:54:08 +02:00
joonas-a 70e220ba58 [SGG] Delete unused file 2025-10-30 17:52:21 +02:00
joonas-a 9482f58930 [SGG] Move all utils to same file (utils) 2025-10-30 17:51:37 +02:00
joonas-a fbc92b6949 [SGG] Split large file 2025-10-30 17:16:35 +02:00
joonas-a 2b452429c9 [SGG] Refactoring 2025-10-30 17:09:43 +02:00
joonas-a 2eb542a61a [Lint] Fix errors 2025-10-29 11:08:43 +02:00
joonas-a 10ba9153ed [Frontpage] Remove alert on updating data tables 2025-10-28 17:48:53 +02:00
Santeri Rajamäki 4eb3946d4f [CoursePopulation] Remove extra collapsing attribute from grade dist. 2025-10-25 14:01:11 +03:00
santeri0200andGitHub 691233a91e Merge pull request #4905 from UniversityOfHelsinkiCS/renovate/major-github-artifact-actions
Update GitHub Artifact Actions (major)
- Adds support for Node 24
2025-10-25 13:56:15 +03:00
renovate[bot]andGitHub cda44b321c Update GitHub Artifact Actions 2025-10-25 00:34:16 +00:00
joonas-a bdbed16047 [Programme overview/courses] Make pagination choices meaningful 2025-10-24 18:13:41 +03:00
joonas-a ec6dd9d672 [SGG] minimal tsx conversion for DX 2025-10-24 17:43:14 +03:00
joonas-a 95ff21a09b [Types] Add re-export for filterview course type 2025-10-24 17:43:14 +03:00
joonas-a a464d8cd49 [SGG] Index cleanup 2025-10-24 17:43:14 +03:00
joonas-a d97e1757b1 [Credit stats] Remove sgg special treatment 2025-10-24 17:43:14 +03:00
joonas-a e91239c4e4 [AgeStats] Add missing typing 2025-10-24 17:43:14 +03:00
Santeri Rajamäki b0d358db78 [CustomPopulation] Upgrade custom population search to MUI 2025-10-24 17:02:42 +03:00
Santeri Rajamäki 22643fa590 [CoursePopulation] Upgraded population courses wrapper to MUI 2025-10-24 17:02:42 +03:00
Santeri Rajamäki 9c5665c216 [OpenUni] Upgrade custom open uni population result wrapper to MUI 2025-10-24 17:02:42 +03:00
Santeri Rajamäki 5549202299 [OpenUni] Upgrade custom open uni population wrapper to MUI 2025-10-24 17:02:42 +03:00
Santeri Rajamäki 31004bed07 [Frontend] Upgrade credit accumulation graph wrapper to MUI 2025-10-24 17:02:41 +03:00
Santeri Rajamäki 5cdfea1bc2 [CoursePopulation] Upgrade credit gain table and tabs to MUI 2025-10-24 17:02:41 +03:00
joonas-a cbb17ef00f [Frontend] Add general purpose styled message component 2025-10-24 16:45:56 +03:00
joonas-a a75b3b1d5a [SGG/types] correctly make some fields optional 2025-10-24 16:31:38 +03:00
joonas-a b34f42efd9 [Routes] Type custom population/sgg rtk endpoint 2025-10-24 16:26:15 +03:00
joonas-a 34e2293bc8 [SGG] jsx->tsx preparation 2025-10-24 15:23:48 +03:00
Santeri Rajamäki 71ae263e40 [Frontend] Remove react-datetime 2025-10-24 15:20:45 +03:00
Santeri Rajamäki b87b129766 [OpenUni] Upgrade custom open uni search to MUI 2025-10-24 15:20:45 +03:00
Santeri Rajamäki 76c707c322 [Frontend] Convert DateRangeSelector to use Stack 2025-10-24 15:20:45 +03:00
joonas-a 1d0d9f91bf [Frontend] Move full screen loading spinner to reusable component 2025-10-24 15:01:32 +03:00
Santeri Rajamäki da22043197 [PopulationStudents] Upgrade check student list admin component to MUI 2025-10-24 14:35:15 +03:00
joonas-a db567235a9 [Modules tab] consistent row naming 2025-10-24 14:00:31 +03:00
Santeri Rajamäki 08d42c8efd [Frontend] Upgraded tag population component to MUI 2025-10-24 13:16:08 +03:00
Santeri Rajamäki 53003b2c93 [Frontend] Remove unused useTabChangeAnalytics hook 2025-10-24 12:05:04 +03:00
Santeri Rajamäki 9049160b02 [SGG] Upgrade population students tabs to MUI 2025-10-24 12:04:14 +03:00
Santeri Rajamäki b40ba0846a [SGG] Upgrade SGG overview to MUI 2025-10-24 11:51:22 +03:00
Santeri Rajamäki cae1fe1550 [Frontend] Enable enrollment date selector to work in other views 2025-10-24 11:43:04 +03:00
Santeri Rajamäki 180bea21c1 [Frontend] Upgrade ColorizedCoursesTable selector components to MUI 2025-10-24 09:21:00 +03:00
Santeri Rajamäki f861d55aab [Frontend] Upgrade ColorizedCoursesTable component to MUI 2025-10-24 09:02:55 +03:00
Santeri Rajamäki 9f689d5ec7 [SGG] Upgrade single SGG view to MUI 2025-10-24 08:53:51 +03:00
Santeri Rajamäki 43d67713a6 [CustomPopulation] Upgrade Unihow data export button to MUI 2025-10-23 23:46:38 +03:00
Santeri Rajamäki ff15862aaf [Frontend] Remove unused Semantic UI segment from population courses container 2025-10-23 22:36:28 +03:00
Santeri Rajamäki 2d00381b0d [CustomPopulation] Replace search history component with the MUI version 2025-10-23 22:31:43 +03:00
Santeri Rajamäki 9186ca5b40 [SGG] Remove loading attribute wrapping paper 2025-10-23 22:21:35 +03:00
Santeri Rajamäki 7b43b7bb06 [CustomPopulation] Upgrade index to MUI 2025-10-23 22:15:17 +03:00
Santeri Rajamäki 533b6b17bd [Frontend] Upgrade rights notification to MUI 2025-10-23 16:53:36 +03:00
Santeri Rajamäki 1ffa846243 [SGG] Upgrade population course selector to MUI 2025-10-23 16:47:39 +03:00
Santeri Rajamäki 9f7f037e90 [SGG] Upgrade common to MUI 2025-10-23 16:26:13 +03:00
Santeri Rajamäki 795bae945d [SGG] Upgrade index to Typescript 2025-10-23 16:20:03 +03:00
Santeri Rajamäki c2b0dc46ca [SGG] Upgrade index to MUI 2025-10-23 16:18:52 +03:00
renovate[bot]andGitHub f22fe44f5a Update dependency react-markdown to v10 2025-10-23 12:41:04 +00:00
santeri0200andGitHub 2139286a49 Merge pull request #4901 from UniversityOfHelsinkiCS/renovate/actions-setup-node-6.x
Update actions/setup-node action to v6
2025-10-23 15:36:25 +03:00
Santeri Rajamäki 4e1ad9441a [META] Upgrade vite to 7.1.12
Closes #4903
2025-10-23 15:35:13 +03:00
Santeri Rajamäki 3a021e07a7 [E2E][Teachers] Get the row, not the index of MAT12004 2025-10-23 14:59:52 +03:00
Santeri Rajamäki 8b14c21f3c [Frontend] Add curriculum guard to population details 2025-10-23 14:04:34 +03:00
Santeri Rajamäki 6bbd53ade9 [E2E][Teachers] Update tests to work with MUI components 2025-10-23 13:55:23 +03:00
Santeri Rajamäki e4e20c8e3c [OodiTable] Ignore unused export on feature interface 2025-10-23 13:13:02 +03:00
Santeri Rajamäki 3337192b8a [CoursePopulation] Upgrade course grade distribution table to MUI 2025-10-23 13:10:17 +03:00
Santeri Rajamäki 61ee4475e7 [Frontend] Add loading section wrapper 2025-10-23 12:57:34 +03:00
Santeri Rajamäki 43c3dd2998 [Teachers] Upgraded teacher index to MUI 2025-10-23 12:54:33 +03:00
Santeri Rajamäki de5e23fdc6 [Teachers] Upgraded teacher statistics view to MUI 2025-10-23 12:54:33 +03:00
Santeri Rajamäki 355b034a4a [Teachers] Upgraded teacher statistics table to MUI 2025-10-23 12:54:33 +03:00
Santeri Rajamäki 2321ab7242 [Teachers] Upgraded teacher details to MUI 2025-10-23 12:54:33 +03:00
Santeri Rajamäki ecc610bf6d [Teachers] Upgraded teacher search tab to MUI 2025-10-23 12:54:33 +03:00
Santeri Rajamäki bef5e39d22 [Teachers] Upgraded teacher leaderboard form to Typescript 2025-10-23 12:54:33 +03:00
Santeri Rajamäki b7d3fbab09 [Teachers] Upgraded teacher leaderboard to MUI 2025-10-23 12:54:33 +03:00
Santeri Rajamäki c0d7538cab [Teachers] Upgrade other wrappers' info boxes to MUI 2025-10-23 12:54:33 +03:00
Santeri Rajamäki 7858845b60 [Teachers] Upgrade leaderboard wrapper to MUI 2025-10-23 12:54:33 +03:00
joonas-a 4c87250206 [Ooditable] use same copy button with notification in all tables 2025-10-21 17:34:59 +03:00
joonas-a 8c52ff707a [General tab] Use better notification when copying student numbers 2025-10-21 17:11:23 +03:00
joonas-a 92d9040260 [General tab] Fix few column alignments 2025-10-21 16:53:47 +03:00
joonas-a 874e75670e [Stylelint] Modify css selector regex 2025-10-21 16:30:50 +03:00
joonas-a 9f60d026ad [Ooditable] Dom optimizations and cleanup 2025-10-21 16:29:54 +03:00
Santeri Rajamäki 0bb2da9137 [Frontend] Fix filter toggle forwardRef error and commit to icon design 2025-10-21 12:33:36 +03:00
Santeri Rajamäki 0977eefbd3 [Frontend] Convert filter toggle to use common filter toggle icon 2025-10-20 22:24:05 +03:00
Santeri Rajamäki 99d2e803b6 [Frontend] Upgrade common filter toggle icon to MUI 2025-10-20 22:23:16 +03:00
Santeri Rajamäki 5a4998ce84 [PopulationStatistics] Upgrade course filter toggle to MUI 2025-10-20 22:22:07 +03:00
Santeri Rajamäki 316c17745d [CustomPopulation] Changed language of search result table to typescript 2025-10-20 21:49:44 +03:00
Santeri Rajamäki bfad75d03e [CustomPopulation] Upgraded search result table to MUI 2025-10-20 21:48:29 +03:00
Santeri Rajamäki c802e3827f [PopulationStatistics] Upgraded Courses of Class to use MUI tabs 2025-10-20 21:26:12 +03:00
Santeri Rajamäki 78f1b2a3e4 [CoursePopulation] Upgrade Language distribution table to MUI 2025-10-20 20:50:52 +03:00
Santeri Rajamäki 6b7b9da7c8 [PopulationStatistics][Tags] Rework Semantic UI Dropdown to MUI 2025-10-20 17:10:37 +03:00
Santeri Rajamäki dfadf975d0 [DegreeProgramme] Make study track selection lazy to remove warnings 2025-10-20 14:04:05 +03:00
Santeri Rajamäki 3809d0e284 [DegreeProgramme] Reorder items to create more dependable render order 2025-10-20 13:59:36 +03:00
Santeri Rajamäki b2338981a9 [Frontend] Remove SisuLinkItem 2025-10-20 13:58:20 +03:00
Santeri Rajamäki be56f8b388 [Frontend] Remove unused modules 2025-10-20 13:26:32 +03:00
Santeri Rajamäki bf4c8fa2fb [Frontend] Apply ForwardRef to Link component
Use https://mui.com/material-ui/integrations/routing/ as guidance.
2025-10-20 13:18:56 +03:00
Santeri Rajamäki d9b8bad314 [PopulationStatistics] Convert SortableTable button to MUI 2025-10-20 12:18:01 +03:00
Santeri Rajamäki 2149cad79c [Teachers] Upgrade frontpage tab feature to MUI 2025-10-20 12:18:00 +03:00
joonas-a 718393cb25 [Material UI] Courses tab 2025-10-16 14:48:59 +03:00
joonas-a bb56f3e801 [Ooditable] make export able to parse objects 2025-10-15 13:32:43 +03:00
joonas-a 08a3f50a5a [Ooditable] streamline exports accessorkey handling 2025-10-15 12:59:09 +03:00
joonas-a e04315e72b [Ooditable] consistent coloring from theme 2025-10-15 12:29:05 +03:00
joonas-a 56a8463871 [Ooditable] better copy button 2025-10-15 12:22:18 +03:00
joonas-a 3895ca5143 [Ooditable] Hide horizontal overflow in export view 2025-10-15 12:17:02 +03:00
Santeri Rajamäki dbb89ad2d8 [E2E][PopulationStatistics] Update searched icon 2025-10-14 16:52:35 +03:00
Santeri Rajamäki 422f6464fc [E2E][LanguageCenter] Remove "hasLanded" inline helper 2025-10-14 16:47:55 +03:00
Santeri Rajamäki 84cd9956ff [CustomPopulation] Use existing MUI percentage bar 2025-10-14 16:41:12 +03:00
Santeri Rajamäki 3473d8e546 [SGG] Center SGG list link icons 2025-10-14 16:33:33 +03:00
Santeri Rajamäki d5e44d4df4 [Frontend] Abstract react-router Link behind wrapper component 2025-10-14 16:26:17 +03:00
Santeri Rajamäki 843f9d9716 [Frontend] Add Link wrapper for MUI/react-router implementation 2025-10-14 16:26:17 +03:00
joonas-a 521ec7e302 [Class stats] Center no students found info box 2025-10-14 16:03:43 +03:00
Santeri Rajamäki 7217f9666d [LanguageCenterView] Convert info message to common MUI info button 2025-10-14 15:20:26 +03:00
Santeri Rajamäki a40cd6a38c [CoursePopulation] Upgrade population course tab feature to MUI 2025-10-14 14:51:04 +03:00
Santeri Rajamäki 8258fa3cf8 [PopulationStatistics] Remove CoursesTab container to Semantic UI contents 2025-10-14 14:18:54 +03:00
Santeri Rajamäki 65b0c83778 [PopulationStatistics] Upgraded progress tab skeleton to MUI 2025-10-14 14:04:22 +03:00
Santeri Rajamäki 7eb5511a77 [PopulationStatistics] Upgraded progress tab links to MUI version 2025-10-14 13:53:36 +03:00
Santeri Rajamäki 378df9cfcd [Frontend] Upgraded SISU link component to MUI 2025-10-14 13:36:36 +03:00
Santeri Rajamäki 0ba959b99e [Frontend] Upgrade dimmer to MUI 2025-10-14 13:16:17 +03:00
Santeri Rajamäki 609fd2e3bc [Frontend] Update StyledTable styling 2025-10-14 12:43:27 +03:00
Santeri Rajamäki 7a43f3f522 [PopulationStatistics] Upgraded student tags table to MUI 2025-10-14 12:41:09 +03:00
renovate[bot]andGitHub 61959c25b9 Update actions/setup-node action to v6 2025-10-14 04:59:32 +00:00
Santeri Rajamäki 5146ff9c48 [Charts] Upgrade highcharts tooltips to MUI
This does not include icons as MUI for some reason does not want to behave with react element and string parsing.
2025-10-13 19:19:07 +03:00
Santeri Rajamäki 481acc3f27 [Frontend] Upgrade progress bar to MUI 2025-10-13 17:56:26 +03:00
Santeri Rajamäki df7761e6e7 [Frontend] Upgrade infobox to MUI 2025-10-13 17:11:40 +03:00
Santeri Rajamäki 7c2c6478b5 [PopulationStatistics] Upgraded student progress icons to MUI 2025-10-13 16:00:37 +03:00
Santeri Rajamäki 284a24c937 [PopulationStatistics] Upgraded student courses icons to MUI 2025-10-13 15:20:15 +03:00
Santeri Rajamäki eedfe5a971 [PopulationStatistics] Upgrade courses of class course management modal to MUI 2025-10-13 15:06:39 +03:00
Santeri Rajamäki e958ba7a5e [PopulationStatistics] Upgrade courses of class course management buttons to MUI 2025-10-13 15:06:33 +03:00
Santeri Rajamäki f5ce505a34 [StudyProgramme] Make CI happy again 2025-10-13 13:13:30 +03:00
Santeri Rajamäki d596db6f15 [StudyProgramme] Reorder hooks for more reliable execute order 2025-10-10 17:47:22 +03:00
Santeri Rajamäki a8da08bd82 [StudyProgramme] Reformat programmeId split 2025-10-10 17:43:27 +03:00
Santeri Rajamäki f4cf3f2c71 [StudyProgramme] Replace excess cypress tag with key 2025-10-10 17:30:47 +03:00
Santeri Rajamäki d2190579fe [SGG] Credit statistics clean up left from query rework 2025-10-10 17:18:59 +03:00
Santeri Rajamäki 3805ed1ffe [Redux] Replace actionHistory reducer with actionHistory middleware 2025-10-10 16:33:12 +03:00
Santeri Rajamäki 14d24dd320 [Redux] skip focus/unfocus state changes in action history item 2025-10-10 15:01:27 +03:00
santeri0200andGitHub da54f92261 Merge pull request #4898 from UniversityOfHelsinkiCS/OOD-97-hide-graduation-title-in-fd
[Programmes] Hide graduation title when not default service provider
2025-10-09 13:52:33 +03:00
Matti Räsänen 248627d809 Moved service provider check to top 2025-10-07 12:51:18 +03:00
Matti Räsänen bc177a124c [Programmes] Hide graduation title when not default service provider 2025-10-07 11:45:16 +03:00
Santeri Rajamäki b5592f4a6c Resolve #4896 2025-10-03 15:36:07 +03:00
Santeri Rajamäki 31c2ecfbd5 [SENTRY] Update sentry config in production.yaml 2025-10-03 15:00:33 +03:00
Santeri Rajamäki 57dca3f6c4 [Frontend][API] Extract base headers to be one time computed value 2025-10-03 14:42:12 +03:00
Santeri Rajamäki 501604ee7e [REDUX] Convert handleRequest callApi to then..catch instead of blocking await after action chain completes 2025-10-03 14:36:55 +03:00
Santeri Rajamäki 257d2c313c Stop stalling semestercodes from crashing frontend
Instead if semestercode request fails crash deliberately to show user something actually went wrong.
2025-10-03 13:01:29 +03:00
Santeri Rajamäki 3f51d6c073 Fixed typo in SGG prettifyTagName 2025-10-01 17:03:53 +03:00
Santeri Rajamäki fe5269f0be Completed courses of students now respects user specified course ordering 2025-10-01 16:22:01 +03:00
joonas-a 1b8cc1c69f [Frontend] Add types a bit 2025-09-30 16:27:41 +03:00
joonas-a fca3c11191 [Backend] assert type where otherwise lost during mankelointi 2025-09-30 16:05:05 +03:00
joonas-a b2f0976e14 [Frontend/FilterView] Type filteredCourses 2025-09-30 15:51:00 +03:00
Santeri Rajamäki 07b8b813ce Fix SGG obfuscated formatter now shows "degree programme" instead of "study programme" 2025-09-30 12:33:18 +03:00
Santeri Rajamäki 0c45f7f480 Completed courses of students now respects course ordering 2025-09-30 12:21:24 +03:00
Santeri Rajamäki 29112f11e4 [META] Replace "study programme" with "degree programme"
Closes #4751
2025-09-30 11:59:10 +03:00
Santeri Rajamäki 817eff0490 [Filters] enrollmentStatus now filters out cancelled studyrights 2025-09-30 11:40:01 +03:00
joonas-a f8c665ca5e [Date filter] fix needing to click twice 2025-09-26 14:45:37 +03:00
joonas-a 5abac85e81 [Filters] Make setting filter programmatically open the relevant card 2025-09-26 14:21:32 +03:00
joonas-a cf15dfab72 [Updater] Use most recent iteration of each study track, instead of the oldest 2025-09-25 17:27:53 +03:00
joonas-a 9ca60cacc5 [Class statistics] Add button to copy all student numbers to clipboard 2025-09-25 14:16:47 +03:00
joonas-a 8747161f0c [Filters/student number] Enable user input and allow pasting many at once 2025-09-24 15:36:51 +03:00
joonas-a 03c13cedd2 [Filters] Finishing touches fix padding etc 2025-09-23 17:48:53 +03:00
joonas-a 086d1f89f4 [E2E] Fix course filter tests 2025-09-23 17:23:49 +03:00
joonas-a 3335891b5a [Cypress] Add command to select from FilterSearchableSelect (mui autocomplete) 2025-09-23 17:17:44 +03:00
joonas-a 1b36ac9dfe [Filters] Show substituted courses in course filter cards 2025-09-23 16:23:37 +03:00
joonas-a 60d08ed9eb [Filters] Make course filter searchable 2025-09-23 15:21:30 +03:00
joonas-a dc51480e27 [Eslint] fix error 2025-09-19 13:22:56 +03:00
joonas-a aa30506df2 [CI] Remove invalid container declaration 2025-09-19 13:16:39 +03:00
joonas-a c929cd2325 [Meta] delete node-actionlint as abandoned and outdated 2025-09-19 12:01:11 +03:00
joonas-a 0fef278dd8 [CI] retry fix e2e 2025-09-19 11:59:45 +03:00
joonas-a 1e9cab4b46 [CI] require tests for staging and fix reusable workflow call 2025-09-19 11:42:28 +03:00
joonas-a 2beea33a4c [CI/e2e] Try to up node version 2025-09-19 11:32:54 +03:00
joonas-a 6d0acb4091 [Chore] Silence eslint for types with potential use cases 2025-09-18 15:51:34 +03:00
joonas-a 6d8d41dbe1 [Chore] Cleanup 2025-09-18 15:40:01 +03:00
joonas-a e3f6c4a244 [Backend] explicitly declare model fields to prevent shadowing 2025-09-18 15:31:18 +03:00
joonas-a 818b9e4e1c [Infra] Revert RHEL 10 -> 9 for compatibility 2025-09-18 11:20:42 +03:00
joonas-a b8bbe7634e [CI] Deploy to staging with failing tests 2025-09-18 10:48:17 +03:00
joonas-a 6e8e63b38d [Fix] Add safeguard for missing user 2025-09-17 17:28:54 +03:00
joonas-aandSanteri Rajamäki 04deef0c2b [Course filter] Show course code in addition to its name in card 2025-09-16 15:36:02 +03:00
joonas-aandSanteri Rajamäki a8e75754cd [Class statistics] Re-introduce summary rows in modules tab 2025-09-16 15:36:02 +03:00
joonas-aandSanteri Rajamäki c350adac64 [OodiTable] Implement aggregation/summary rows 2025-09-16 15:36:02 +03:00
Santeri Rajamäki 59b79d26d2 [META] Update gelf-pro override on sis-updater-scheduler 2025-09-16 15:36:02 +03:00
Santeri Rajamäki cd8f5b11b1 [META] Run npm update on sis-updater-scheduler 2025-09-16 15:36:02 +03:00
Santeri Rajamäki b8e110e39a [META] Run npm update on sis-updater-worker 2025-09-16 15:36:02 +03:00
Santeri Rajamäki 95b60ed378 [META] Run npm update on backend 2025-09-16 15:36:02 +03:00
Santeri Rajamäki 9dac180b95 [META] Update typescript to version 5.9.2 2025-09-16 15:36:02 +03:00
Santeri Rajamäki 908985410b [META] Update unfuck-spa-shibboleth-session to version 5.0.0 2025-09-16 15:34:33 +03:00
Santeri Rajamäki 9f686fafd5 [META] Run npm update on frontend 2025-09-12 11:20:50 +03:00
Santeri Rajamäki 59963e4961 [META] Update xlsx blob to version 0.20.3 2025-09-12 11:12:39 +03:00
Santeri Rajamäki 1f1dc624e4 [META] Update unfuck-spa-shibboleth-session to version 4.1.1 2025-09-12 11:08:21 +03:00
Santeri Rajamäki 3e374f29d0 [META] Run npm update on root package 2025-09-12 10:29:58 +03:00
santeri0200andGitHub 9c060874eb Merge pull request #4884 from UniversityOfHelsinkiCS/renovate/npm-vite-vulnerability
Update dependency vite to v7.0.7 [SECURITY]
2025-09-12 10:23:26 +03:00
renovate[bot]andGitHub 3a04397e5d Update dependency vite to v7.0.7 [SECURITY] 2025-09-09 21:34:40 +00:00
736 changed files with 28701 additions and 29365 deletions
+2 -2
View File
@@ -4,9 +4,9 @@ description: 'Runs ESLint and Stylelint for frontend and backend'
runs:
using: composite
steps:
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- name: 'Install dependencies'
shell: bash
run: npm ci && npm ci --prefix services/shared && npm ci --prefix services/frontend && npm ci --prefix services/backend
+5 -5
View File
@@ -19,7 +19,7 @@ runs:
using: composite
steps:
- name: 'Login to toska Docker registry'
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi
username: ${{ inputs.username }}
@@ -27,10 +27,10 @@ runs:
- name: 'Run services, pull rest of the needed images'
shell: bash
run: docker compose --file docker-compose.ci.yml up --detach --quiet-pull
run: docker compose --file docker-compose.ci.yml --profile oodikone up --detach --quiet-pull
- name: 'Run cypress'
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7
with:
browser: chrome
spec: ${{ inputs.spec }}
@@ -38,7 +38,7 @@ runs:
CYPRESS_baseUrl: http://localhost:1337
- name: 'Upload video if tests fail'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: failure()
with:
name: 'Cypress videos - ${{ inputs.test_name }}'
@@ -47,4 +47,4 @@ runs:
- name: 'Print logs on failure'
shell: bash
if: failure()
run: docker compose --file docker-compose.ci.yml logs
run: docker compose --file docker-compose.ci.yml --profile oodikone logs && sleep 15
+11 -11
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: 'Get current time'
id: get_current_time
@@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Extract release version
id: extract_version
@@ -85,7 +85,7 @@ jobs:
- release_backend
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Extract release version
id: extract_version
@@ -100,9 +100,9 @@ jobs:
- name: Create Sentry release
uses: getsentry/action-release@v3
env:
SENTRY_URL: https://toska.cs.helsinki.fi/
SENTRY_URL: https://toska.it.helsinki.fi/
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }}
SENTRY_ORG: toska
SENTRY_ORG: sentry
SENTRY_PROJECT: oodikone-frontend
with:
environment: production
@@ -119,7 +119,7 @@ jobs:
- release_backend
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Extract release version
id: extract_version
@@ -134,9 +134,9 @@ jobs:
- name: Create Sentry release
uses: getsentry/action-release@v3
env:
SENTRY_URL: https://toska.cs.helsinki.fi/
SENTRY_URL: https://toska.it.helsinki.fi/
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }}
SENTRY_ORG: toska
SENTRY_ORG: sentry
SENTRY_PROJECT: oodikone-backend
with:
environment: production
@@ -157,13 +157,13 @@ jobs:
if: success()
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: 'Send notification to Slack'
uses: UniversityOfHelsinkiCS/slackbot-action@v1.5
with:
webhook-url: ${{ secrets.WEBHOOK_URL }}
message-type: deployment
softa-url: https://oodikone.cs.helsinki.fi/
softa-url: https://oodikone.helsinki.fi/
deployment-target: production
report_failure:
@@ -178,7 +178,7 @@ jobs:
if: failure()
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: 'Send notification to Slack'
uses: UniversityOfHelsinkiCS/slackbot-action@v1.5
with:
+33 -7
View File
@@ -8,7 +8,6 @@ on:
- 'cypress/**'
- 'cypress.config.json'
- 'services/**'
- '!services/backend/src/rapodiff/**'
- 'docker-compose.ci.yml'
- 'package*.json'
@@ -17,14 +16,43 @@ jobs:
name: 'Lint packages'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Lint backend, frontend and shared
uses: ./.github/actions/lint
test_api:
needs:
- lint_packages
strategy:
fail-fast: false
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install node-24 for cypress
uses: actions/setup-node@v6
with:
node-version: 24
- name: 'Login to toska docker registry'
uses: docker/login-action@v4
with:
registry: registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi
username: ${{ secrets.TOSKAREGISTRY_USERNAME }}
password: ${{ secrets.TOSKAREGISTRY_PASSWORD }}
- name: Start oodikone detached
run: npm run oodikone:ci:setup
- name: Run api-tests
run: npm run oodikone:ci:api
test_end_to_end:
runs-on: ubuntu-latest
needs:
- lint_packages
- test_api
strategy:
fail-fast: false
matrix:
@@ -41,16 +69,14 @@ jobs:
spec: cypress/e2e/Students.js
- name: 'Test completed courses'
spec: cypress/e2e/Completed_courses.js
- name: 'Test study programme overview'
spec: cypress/e2e/Study_programme_overview.js
- name: 'Test degree programme overview'
spec: cypress/e2e/Degree_programme_overview.js
- name: 'Test teachers and users'
spec: |
cypress/e2e/Teachers.js
cypress/e2e/Users.js
- name: 'Test faculty statistics'
spec: cypress/e2e/Faculty_statistics.js
- name: 'Test open uni search'
spec: cypress/e2e/Openuni.js
- name: 'Test language center view'
spec: cypress/e2e/Language_center.js
- name: 'Test study guidance groups'
@@ -59,7 +85,7 @@ jobs:
spec: cypress/e2e/University.js
name: ${{ matrix.name }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: 'Run tests'
uses: ./.github/actions/run-tests
with:
+5 -6
View File
@@ -11,14 +11,13 @@ on:
- 'cypress/**'
- 'cypress.config.json'
- 'services/**'
- '!services/backend/src/rapodiff/**'
- 'updater/sis-updater-worker/src/db/migrations/**'
- 'docker-compose.ci.yml'
- 'package*.json'
jobs:
test:
uses: ./.github/workflows/test.yaml
uses: UniversityOfHelsinkiCS/oodikone/.github/workflows/test.yaml@master
secrets:
username: ${{ secrets.TOSKAREGISTRY_USERNAME }}
password: ${{ secrets.TOSKAREGISTRY_PASSWORD }}
@@ -29,7 +28,7 @@ jobs:
needs: test
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Get current time
id: get_current_time
@@ -64,7 +63,7 @@ jobs:
needs: test
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Build backend image
id: build-image
@@ -99,7 +98,7 @@ jobs:
if: success()
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: 'Send notification to Slack'
uses: UniversityOfHelsinkiCS/slackbot-action@v1.5
with:
@@ -118,7 +117,7 @@ jobs:
if: failure()
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: 'Send notification to Slack'
uses: UniversityOfHelsinkiCS/slackbot-action@v1.5
with:
+218 -40
View File
@@ -16,7 +16,7 @@ jobs:
name: 'Lint packages'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Lint backend, frontend and shared
uses: ./.github/actions/lint
@@ -25,10 +25,10 @@ jobs:
name: 'Build backend'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
- name: 'Build image and export it'
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
build-args: |
SENTRY_RELEASE=${{ github.sha }}
@@ -40,9 +40,11 @@ jobs:
outputs: type=docker,dest=/tmp/backend.tar
env:
DOCKER_BUILD_SUMMARY: false
- name: 'Upload image for other jobs'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
if-no-files-found: error
name: backend-image
path: /tmp/backend.tar
@@ -50,24 +52,70 @@ jobs:
name: 'Build frontend'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
- name: Get current time
id: get_current_time
run: echo "NOW=$(date --iso-8601=seconds)" >> $GITHUB_OUTPUT
- name: Build frontend image
id: build-image
uses: redhat-actions/buildah-build@v2
uses: docker/build-push-action@v7
with:
image: oodikone-frontend
tags: staging ${{ github.sha }}
context: services
containerfiles: |
services/frontend/Dockerfile
build-args: |
BUILT_AT=${{ steps.get_current_time.outputs.NOW }}
BASE_PATH=/oodikone
STAGING=true
context: services
file: services/frontend/Dockerfile
push: false
tags: oodikone-frontend:staging
outputs: type=docker,dest=/tmp/frontend.tar
env:
DOCKER_BUILD_SUMMARY: false
# - name: 'Upload image for other jobs'
# uses: actions/upload-artifact@v7
# with:
# if-no-files-found: error
# name: frontend-image
# path: /tmp/frontend.tar
pull_docker_images:
name: 'Pull all needed docker images and cache them'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: 'Login to toska docker registry'
uses: docker/login-action@v4
with:
registry: registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi
username: ${{ secrets.username }}
password: ${{ secrets.password }}
- name: 'Pull required images'
run: docker compose --file docker-compose.ci.yml --profile oodikone --profile updater pull --ignore-buildable --quiet
- name: 'Save Docker images for downstream jobs'
run: |
docker save \
registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi/kone-db \
registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi/sis-db \
registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi/user-db \
redis:7.2.6 \
postgres:15.10 \
"quay.io/toska/jami:production" \
nginx:latest \
| zstd -T0 -3 -o /tmp/docker-images.tar.zst
- name: 'Upload Docker images artifact'
uses: actions/upload-artifact@v7
with:
name: docker-images
path: /tmp/docker-images.tar.zst
retention-days: 1
# === Update databases ===
check_migration_files:
name: Check if new migrations have been added
runs-on: ubuntu-latest
@@ -76,7 +124,7 @@ jobs:
user_db_migrations: ${{ steps.check_migrations.outputs.user_db_migrations }}
sis_db_migrations: ${{ steps.check_migrations.outputs.sis_db_migrations }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Check if new migrations have been added
id: check_migrations
uses: dorny/paths-filter@v3
@@ -94,45 +142,69 @@ jobs:
build_and_push_anon_sis_db:
name: 'Build anon sis-db and push to toska hub if new migrations have been added'
needs: check_migration_files
needs:
- pull_docker_images
- check_migration_files
runs-on: ubuntu-latest
if: needs.check_migration_files.outputs.sis_db_migrations == 'true'
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: 'Login to toska docker registry'
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi
username: ${{ secrets.username }}
password: ${{ secrets.password }}
- name: 'Download Docker images'
uses: actions/download-artifact@v8
with:
name: docker-images
path: /tmp
- name: 'Load Docker images'
run: zstd -d /tmp/docker-images.tar.zst --stdout | docker load
- name: 'Let sis-updater-worker run migrations, then push new version of image to toska hub'
run: |
docker compose --file docker-compose.ci.yml up --detach sis-updater-worker
docker compose --file docker-compose.ci.yml up --detach --quiet-pull sis-updater-worker
sleep 30 # wait for sis-updater-worker to start and run migrations
./anonyymioodi/commit_and_push_to_toska_hub.sh sis-db
build_and_push_anon_kone_db:
name: 'Build anon kone-db and push to toska hub if new migrations have been added'
needs:
- pull_docker_images
- build_backend
- build_frontend
- check_migration_files
runs-on: ubuntu-latest
if: needs.check_migration_files.outputs.kone_db_migrations == 'true'
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: 'Download Docker images'
uses: actions/download-artifact@v8
with:
name: docker-images
path: /tmp
- name: 'Download backend image'
uses: actions/download-artifact@v5
uses: actions/download-artifact@v8
with:
name: backend-image
- name: 'Load Docker images'
run: zstd -d /tmp/docker-images.tar.zst --stdout | docker load
- name: 'Load built backend image'
run: docker load --input backend.tar
- name: 'Login to toska docker registry'
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi
username: ${{ secrets.username }}
password: ${{ secrets.password }}
- name: 'Let backend run migrations, then push new version of image to toska hub'
run: |
docker compose --file docker-compose.ci.yml up --detach backend
@@ -142,13 +214,14 @@ jobs:
build_and_push_anon_user_db:
name: 'Build anon user-db and push to toska hub if new migrations have been added'
needs:
- pull_docker_images
- build_backend
- build_frontend
- check_migration_files
runs-on: ubuntu-latest
if: needs.check_migration_files.outputs.user_db_migrations == 'true'
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v3
id: check_migrations
with:
@@ -156,33 +229,87 @@ jobs:
migrations:
- 'services/backend/src/database/migrations_user/**'
- '.github/workflows/test.yaml'
- name: 'Download Docker images'
uses: actions/download-artifact@v8
with:
name: docker-images
path: /tmp
- name: 'Download backend image'
uses: actions/download-artifact@v5
uses: actions/download-artifact@v8
with:
name: backend-image
- name: 'Load Docker images'
run: zstd -d /tmp/docker-images.tar.zst --stdout | docker load
- name: 'Load built backend image'
run: docker load --input backend.tar
- name: 'Login to toska docker registry'
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi
username: ${{ secrets.username }}
password: ${{ secrets.password }}
- name: 'Let backend run migrations, then push new version of image to toska hub'
run: |
docker compose --file docker-compose.ci.yml up --detach backend
sleep 30 # wait for backend to start and run migrations
./anonyymioodi/commit_and_push_to_toska_hub.sh user-db
# === End-to-end tests ===
test_end_to_end:
runs-on: ubuntu-latest
# === Backend api-tests ===
test_api:
needs:
- pull_docker_images
- build_and_push_anon_sis_db
- build_and_push_anon_kone_db
- build_and_push_anon_user_db
# Always run the tests, even if some of the needed jobs failed or were skipped
if: always()
- lint_packages
if: always() && !cancelled() && needs.lint_packages.result == 'success'
strategy:
fail-fast: false
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install node-24
uses: actions/setup-node@v6
with:
node-version: 24
- name: 'Download Docker images'
uses: actions/download-artifact@v8
with:
name: docker-images
path: /tmp
- name: 'Load Docker images'
run: zstd -d /tmp/docker-images.tar.zst --stdout | docker load
- name: 'Login to toska docker registry'
uses: docker/login-action@v4
with:
registry: registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi
username: ${{ secrets.username }}
password: ${{ secrets.password }}
- name: Start oodikone (without frontend and backend)
run: npm run oodikone:ci:setup -- --quiet-pull
- name: Run api-tests
run: npm run oodikone:ci:api
# === End-to-end tests ===
test_end_to_end:
needs:
- pull_docker_images
- build_and_push_anon_sis_db
- build_and_push_anon_kone_db
- build_and_push_anon_user_db
- lint_packages
if: always() && !cancelled() && needs.lint_packages.result == 'success'
strategy:
fail-fast: false
matrix:
@@ -199,25 +326,53 @@ jobs:
spec: cypress/e2e/Students.js
- name: 'Test completed courses'
spec: cypress/e2e/Completed_courses.js
- name: 'Test study programme overview'
spec: cypress/e2e/Study_programme_overview.js
- name: 'Test degree programme overview'
spec: cypress/e2e/Degree_programme_overview.js
- name: 'Test teachers and users'
spec: |
cypress/e2e/Teachers.js
cypress/e2e/Users.js
- name: 'Test faculty statistics'
spec: cypress/e2e/Faculty_statistics.js
- name: 'Test open uni search'
spec: cypress/e2e/Openuni.js
- name: 'Test language center view'
spec: cypress/e2e/Language_center.js
- name: 'Test study guidance groups'
spec: cypress/e2e/Study_guidance_groups.js
- name: 'Test university'
spec: cypress/e2e/University.js
name: ${{ matrix.name }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- name: Checkout
uses: actions/checkout@v6
- name: Install node-24 for cypress
uses: actions/setup-node@v6
with:
node-version: 24
- name: 'Download Docker images'
uses: actions/download-artifact@v8
with:
name: docker-images
path: /tmp
- name: 'Download backend image'
uses: actions/download-artifact@v8
with:
name: backend-image
# - name: 'Download frontend image'
# uses: actions/download-artifact@v8
# with:
# name: frontend-image
- name: 'Load Docker images'
run: zstd -d /tmp/docker-images.tar.zst --stdout | docker load
- name: 'Load built backend image'
run: docker load --input backend.tar
# - name: 'Load built frontend image'
# run: docker load --input frontend.tar
- name: 'Run tests'
uses: ./.github/actions/run-tests
with:
@@ -230,22 +385,45 @@ jobs:
test_service_exit_statuses:
name: 'Check services run correctly'
needs:
- pull_docker_images
- build_and_push_anon_sis_db
- build_and_push_anon_kone_db
- build_and_push_anon_user_db
if: always()
- lint_packages
if: always() && !cancelled() && needs.lint_packages.result == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: 'Login to toska docker registry'
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi
username: ${{ secrets.username }}
password: ${{ secrets.password }}
- name: 'Run services, pull rest of the needed images'
run: docker compose --file docker-compose.ci.yml up --detach --quiet-pull
- name: 'Download Docker images'
uses: actions/download-artifact@v8
with:
name: docker-images
path: /tmp
- name: 'Download backend image'
uses: actions/download-artifact@v8
with:
name: backend-image
# - name: 'Download frontend image'
# uses: actions/download-artifact@v8
# with:
# name: frontend-image
- name: 'Load Docker images'
run: zstd -d /tmp/docker-images.tar.zst --stdout | docker load
- name: 'Load built backend image'
run: docker load --input backend.tar
# - name: 'Load built frontend image'
# run: docker load --input frontend.tar
- name: 'Run services, build rest of the needed images'
run: docker compose --file docker-compose.ci.yml --profile oodikone up --detach --quiet-pull
- name: 'Check that exit statuses are not errors'
run: |
@@ -257,4 +435,4 @@ jobs:
- name: 'Print logs on failure'
if: failure()
run: docker compose --file docker-compose.ci.yml logs
run: docker compose --file docker-compose.ci.yml --profile oodikone logs && sleep 15
+22 -27
View File
@@ -1,29 +1,26 @@
name: 'Test (and release) Updater'
on:
push:
branches:
- master
release:
types: [published]
paths:
- '.github/workflows/updater.yaml'
- 'updater/sis-updater-worker/**'
- 'updater/sis-updater-scheduler/**'
pull_request:
branches:
- master
paths:
- 'updater/sis-updater-worker/**'
- 'updater/sis-updater-scheduler/**'
jobs:
lint_scheduler_and_worker:
name: 'Lint scheduler and worker'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- name: Install safe-chain
run: set -o pipefail && curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
- name: 'Install dependencies'
run: npm ci && npm ci --prefix updater/sis-updater-scheduler && npm ci --prefix updater/sis-updater-worker
- name: 'Run linter'
@@ -33,10 +30,10 @@ jobs:
name: 'Build Updater worker'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
- name: 'Build the image'
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: updater/sis-updater-worker/
push: false
@@ -45,7 +42,7 @@ jobs:
env:
DOCKER_BUILD_SUMMARY: false
- name: 'Upload image for other jobs'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: updater-worker-image
path: /tmp/worker.tar
@@ -54,10 +51,10 @@ jobs:
name: 'Build Updater scheduler'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
- name: 'Build the image'
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: updater/sis-updater-scheduler/
push: false
@@ -66,7 +63,7 @@ jobs:
env:
DOCKER_BUILD_SUMMARY: false
- name: 'Upload image for other jobs'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: updater-scheduler-image
path: /tmp/scheduler.tar
@@ -79,15 +76,15 @@ jobs:
- build_worker
- build_scheduler
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: 'Login to toska docker registry'
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: registry-toska.ext.ocp-prod-0.k8s.it.helsinki.fi
username: ${{ secrets.TOSKAREGISTRY_USERNAME }}
password: ${{ secrets.TOSKAREGISTRY_PASSWORD }}
- name: 'Download built images'
uses: actions/download-artifact@v5
uses: actions/download-artifact@v8
with:
pattern: 'updater-{worker,scheduler}-image'
merge-multiple: true
@@ -122,9 +119,8 @@ jobs:
name: 'Build and release Updater worker'
runs-on: ubuntu-latest
needs: test_updater
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Build worker image
id: build-image
@@ -152,9 +148,8 @@ jobs:
name: 'Build and release Updater scheduler'
runs-on: ubuntu-latest
needs: test_updater
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Build scheduler image
id: build-image
-3
View File
@@ -65,9 +65,6 @@ instrumented/
.vscode/launch.json
.vscode/terminals.json
# Funidata specific
environments/
# Target of shared symlink
services/**/src/shared
-1
View File
@@ -14,7 +14,6 @@ export default {
Dockerfile: files =>
`${dockerCmdBase} hadolint/hadolint:v2.12.0 hadolint --ignore DL3006 ${relativeFilePaths(files)}`,
'*.sh': files => `${dockerCmdBase} koalaman/shellcheck:v0.7.2 ${relativeFilePaths(files)} -x`,
'.github/{workflows,actions}/*': files => `npm run actionlint ${relativeFilePaths(files)}`,
'docker-compose*': files => {
const composeFiles = file => {
if (['docker-compose.ci.yml', 'docker-compose.test.yml'].some(f => file.includes(f))) {
+3
View File
@@ -0,0 +1,3 @@
ignore-scripts=true
min-release-age=7
save-prefix=
-5
View File
@@ -1,5 +0,0 @@
{
"all": true,
"include": ["services/frontend/src/**/*.js", "services/frontend/src/**/*.jsx"],
"extends": "./nyc-config.js"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"extends": ["stylelint-config-standard"],
"rules": {
"selector-class-pattern": "^[a-z][a-zA-Z-]*$"
"selector-class-pattern": "^(?:[a-z][a-zA-Z-]*|Mui[A-Za-z-]+)$"
}
}
+1 -5
View File
@@ -1,4 +1,5 @@
# Oodikone
[![Maintainability](https://qlty.sh/gh/UniversityOfHelsinkiCS/projects/oodikone/maintainability.svg)](https://qlty.sh/gh/UniversityOfHelsinkiCS/projects/oodikone)
An application for analyzing university data, running at [https://oodikone.helsinki.fi/](https://oodikone.helsinki.fi/).
@@ -112,8 +113,6 @@ The `documentation` directory contains some notes on the sis-db schema and is me
For internal meeting notes and sensitive information, see Toska GitLab.
[SortableTable documentation](./services/frontend/src/components/SortableTable/README.md)
### Basics
The development environment is entirely configured in the `docker-compose.yml` file located in this repository. The file defines the services for Oodikone's two main components: **oodikone** and **updater**.
@@ -131,7 +130,6 @@ Some useful commands are defined in `package.json` and can be run with `npm run
- `npm run docker:down`: stops the whole environment
- `npm run testupdater`: runs tests for updater
- `npm run flushredis`: clears Redis, which forces big calculations to be redone in e.g. studyprogramme overview and faculty views
- `npm run rapodiff`: runs [Rapodiff](./services/backend/src/rapodiff/)
- `npm run install:local`: installs `node_modules` for each package (backend, frontend, sis-updater-scheduler and sis-updater-worker) locally
- `npm run install:docker`: installs `node_modules` for each package inside containers
- `npm run install:both`: runs both `npm run install:local` and `npm run install:docker`
@@ -189,8 +187,6 @@ Continuous integration (CI) works with Github actions and is defined in workflow
- Updater is deployed to production when pushed if tests pass
- Test databases can be updated, instructions in [anonyymioodi README](anonyymioodi/README.md)
[Rapodiff](./services/backend/src/rapodiff/) can be used to check differences between Rapo and Oodikone data.
## ❓FAQ
### Modules are missing after updating package.json
+3 -3
View File
@@ -6,14 +6,14 @@ The quick guide should be enough for most purposes: updating schema, data, or th
## Quick guide to updating a test database
1. Ensure you have the latest test-db. Just pulling images might not replace your existing volume, so you may need to remove it manually. This command might work: `docker volume rm oodikone_sis-db-data` (replace `oodikone_sis-db-data` with the correct volume name). The new image, including data, should be pulled the next time you launch Oodikone normally.
1. Ensure you have the latest test-db. Just pulling images might not replace your existing volume, so you may need to remove it manually. This command might work: `docker volume rm oodikone-sis-db-data` (replace `oodikone-sis-db-data` with the correct volume name). The new image, including data, should be pulled the next time you launch Oodikone normally.
2. Do your dev work as usual with the test database, for example, using `npm run both`. Edit data as needed, (for example, run the updater via the frontend), and then run tests to see if they pass (follow the instructions in the [README](../README.md) to run the tests more quickly).
3. If you're updating `sis-db`:
- Modify the scheduler limit in [config.js](../updater/sis-updater-scheduler/src/config.js) by setting `module.exports.DEV_SCHEDULE_COUNT = null`.
- Follow the `sis-updater-worker` logs to ensure everything is running smoothly (`docker logs --follow oodikone_sis-updater-worker_1`).
- Follow the `sis-updater-worker` logs to ensure everything is running smoothly (`docker logs --follow oodikone-sis-updater-worker`).
4. When you're satisfied with the database, create a dump:
@@ -67,6 +67,6 @@ The quick guide should be enough for most purposes: updating schema, data, or th
5. Navigate to <http://localhost:3000/updater> and click the three buttons (Update meta, Update students, Update curriculums) one by one. This will populate `sis-db` with data from the new `sis-importer-db`. You might want to do each update a couple of times, as the database tables affect each other, and not all necessary data might be generated on the first try.
6. Follow the `sis-updater-worker` logs to ensure everything is running smoothly (`docker logs --follow oodikone_sis-updater-worker_1`). If you see any errors, try clicking the buttons again.
6. Follow the `sis-updater-worker` logs to ensure everything is running smoothly (`docker logs --follow oodikone-sis-updater-worker`). If you see any errors, try clicking the buttons again.
7. Run all the tests to ensure everything is working as expected (follow the instructions in the [README](../README.md) to run the tests more quickly). If any tests fail, update the test files accordingly.
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env bash
# This script will first make db dumps of the test environment and upload the dumps to staging
. ../scripts/utils.sh
set -euo pipefail
main() {
declare -r db_names="sis-db kone-db user-db"
declare -A db_url_names
db_url_names["sis-db"]="SIS_DB_URL"
db_url_names["kone-db"]="DB_URL_KONE"
db_url_names["user-db"]="DB_URL_USER"
declare -r dump_directory="dumps"
# Takes the first db-tools pod
check_oc_logged_staging
db_tools_name=$(oc get pods -l app=db-tools --output jsonpath='{.items[0].metadata.name}')
readonly db_tools_name
create_dumps
upload_and_migrate_dumps
}
function create_dumps() {
echo "Starting test/anon containers..."
(npm run docker:down && npm run both) || die "Failed to start containers"
echo
echo "Waiting for containers to start..."
sleep 10 # wait for containers to start
# Create dumps for all dbs
echo
echo "Creating dumps for $db_names"
echo
# Create dir if not exists
if [ ! -d $dump_directory ]; then mkdir -p $dump_directory; fi
for db in $db_names
do
create_dump_from_db "$db"
if [ ! -f "$dump_directory/$db.sqz" ]; then die "Db dump not created"; fi
done
echo "Dumps can be found in $PWD/\*.sqz"
}
# $1 db-name
function create_dump_from_db() {
echo "Dumping db $1..."
docker exec "$1" pg_dump -Fc -U postgres "$1" > "$dump_directory/$1.sqz" || die "Failed to dump db $1"
}
function check_oc_logged_staging() {
echo -n "Checking if oc is logged into toska staging..."
[[ $(oc config current-context) == "toska/api-ocp-test-0-k8s-it-helsinki-fi"* ]] || die "\nNot logged in to toska staging environment: $(oc config current-context)"
echo " OK"
}
function upload_and_migrate_dumps() {
local target_dump_directory="$db_tools_name:/tmp/oodikone-staging-dumps"
echo -n "Dumping $dump_directory/ to $target_dump_directory..."
oc rsync "$dump_directory/" "$target_dump_directory" 1> /dev/null # show errors
echo " OK"
echo
for db in $db_names
do
migrate_dumps "$db"
done
}
function migrate_dumps() {
local db_url_b64
local db_url_raw
local db_url
db_url_b64=$(oc get secrets -l app.kubernetes.io/instance=oodikone --output jsonpath="{.items[0].data.${db_url_names[$1]}}")
db_url_raw=$(echo "$db_url_b64" | base64 --decode)
db_url="${db_url_raw%\?*}"
[[ $db_url != *"oodikone_*_test"* ]] || die "Trying to use non-test database: $db_url"
echo "Migrating $1 to url: $db_url"
oc exec "$db_tools_name" -- pg_restore --clean --no-owner -d "$db_url" "/tmp/oodikone-staging-dumps/$1.sqz"
}
# Run script
main
-2
View File
@@ -3,7 +3,6 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
experimentalStudio: process.env.NODE_ENV === 'development',
projectId: 'c3jsph',
defaultCommandTimeout: 30000,
requestTimeout: 30000,
@@ -14,7 +13,6 @@ module.exports = defineConfig({
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
require('@cypress/code-coverage/task')(on, config)
require('./cypress/plugins/index.js')(on, config)
return config
},
+30 -30
View File
@@ -17,32 +17,32 @@ const openSearch = () => {
}
const selectSavedCourselist = name => {
cy.get('[data-cy=history-search]').click()
cy.get('[data-cy=history-search]').type(name)
cy.get('[data-cy=history-search]').type('{downarrow}')
cy.get('[data-cy=history-search]').type('{enter}')
cy.cs('history-search').click()
cy.cs('history-search').type(name)
cy.cs('history-search').type('{downarrow}')
cy.cs('history-search').type('{enter}')
}
const createCourseList = (courseCodes, courseListName) => {
openSearch()
openCompletedCoursesModal()
cy.get('[data-cy=course-list-input]').type(courseCodes.join('\n'))
cy.get('[data-cy=search-name]').type(courseListName)
cy.get('[data-cy=save-courselist]').click()
cy.cs('course-list-input').type(courseCodes.join('\n'))
cy.cs('search-name').type(courseListName)
cy.cs('save-courselist').click()
}
const generateCourseListName = () => `TEST-course-list-${new Date().getTime()}`
const deleteAllPreviousSearches = () => {
cy.get('[data-cy=history-search]').click()
cy.cs('history-search').click()
cy.get('.MuiAutocomplete-popper li').then($options => {
if ($options.length > 0) {
cy.wrap($options).each(($option, index, $list) => {
cy.wrap($option).click()
cy.get('[data-cy=delete-courselist]').click()
cy.cs('delete-courselist').click()
if (index < $list.length - 1) {
cy.get('[data-cy=history-search]').click()
cy.cs('history-search').click()
}
})
} else {
@@ -57,7 +57,7 @@ describe('When search modal is opened', () => {
hasLanded()
cy.url().should('include', '/completedcoursessearch')
cy.contains('Search completed courses of students')
cy.get('[data-cy=open-completed-courses-modal-button]').click()
cy.cs('open-completed-courses-modal-button').click()
})
it('Modal opens correctly', () => {
@@ -74,8 +74,8 @@ describe('When search modal is opened', () => {
cy.init('/completedcoursessearch?courseList=TKT10001&courseList=TKT10002&studentList=433237&studentList=457144')
hasLanded()
openCompletedCoursesModal()
cy.contains('[data-cy=student-no-input]', '433237, 457144')
cy.contains('[data-cy=course-list-input]', 'TKT10001, TKT10002')
cy.cs('student-no-input').contains('433237, 457144')
cy.cs('course-list-input').contains('TKT10001, TKT10002')
})
describe('When a search is executed with invalid input', () => {
@@ -83,16 +83,16 @@ describe('When search modal is opened', () => {
cy.init('/completedcoursessearch')
hasLanded()
openCompletedCoursesModal()
cy.get('[data-cy=student-no-input]').type('1')
cy.get('[data-cy=course-list-input]').type('1')
cy.get('[data-cy=completed-courses-search-button]').click()
cy.get('[data-cy=rights-notification]').should(
cy.cs('student-no-input').type('1')
cy.cs('course-list-input').type('1')
cy.cs('completed-courses-search-button').click()
cy.cs('rights-notification').should(
'contain.text',
'The information for the following students could not be displayed'
)
cy.contains('[data-cy=rights-notification]', '1')
cy.contains('[data-cy=completed-courses-table-div]', 'Student number')
cy.contains('[data-cy=completed-courses-table-div]', '1').should('not.exist')
cy.cs('rights-notification').contains('1')
cy.cs('completed-courses-table-div').contains('Student number')
cy.cs('completed-courses-table-div').contains('1').should('not.exist')
})
})
@@ -102,9 +102,9 @@ describe('When search modal is opened', () => {
hasLanded()
openCompletedCoursesModal()
cy.fixture('completedCoursesData').then(({ studentSet1, coursesSet1 }) => {
cy.get('[data-cy=student-no-input]').type(studentSet1.join('\n'))
cy.get('[data-cy=course-list-input]').type(coursesSet1.join('\n'))
cy.get('[data-cy=completed-courses-search-button]').click()
cy.cs('student-no-input').type(studentSet1.join('\n'))
cy.cs('course-list-input').type(coursesSet1.join('\n'))
cy.cs('completed-courses-search-button').click()
})
})
@@ -120,10 +120,10 @@ describe('When search modal is opened', () => {
it('Finds correct students and courses', () => {
cy.fixture('completedCoursesData').then(({ studentSet1, coursesSet1 }) => {
for (const studentNumber of studentSet1) {
cy.contains('[data-cy=completed-courses-table-div]', studentNumber)
cy.cs('completed-courses-table-div').contains(studentNumber)
}
for (const courseCode of coursesSet1) {
cy.contains('[data-cy=completed-courses-table-div]', courseCode)
cy.cs('completed-courses-table-div').contains(courseCode)
}
})
})
@@ -150,7 +150,7 @@ describe('Courselist saving-related functions work', () => {
openSearch()
openCompletedCoursesModal()
selectSavedCourselist(courseList)
cy.contains('[data-cy=course-list-input]', courses.join(', '))
cy.cs('course-list-input').contains(courses.join(', '))
})
it('Course list can be deleted', () => {
@@ -159,7 +159,7 @@ describe('Courselist saving-related functions work', () => {
openSearch()
openCompletedCoursesModal()
selectSavedCourselist(courseList)
cy.get('[data-cy=delete-courselist]').click()
cy.cs('delete-courselist').click()
cy.contains('You have no previous searches.')
})
@@ -169,11 +169,11 @@ describe('Courselist saving-related functions work', () => {
openSearch()
openCompletedCoursesModal()
selectSavedCourselist(courseList)
cy.get('[data-cy=course-list-input]').type(',TKT10001')
cy.get('[data-cy=save-courselist]').click()
cy.cs('course-list-input').type(',TKT10001')
cy.cs('save-courselist').click()
openSearch()
openCompletedCoursesModal()
selectSavedCourselist(courseList)
cy.contains('[data-cy=course-list-input]', 'CSM14204, TKT10004, TKT10001')
cy.cs('course-list-input').contains('CSM14204, TKT10004, TKT10001')
})
})
File diff suppressed because it is too large Load Diff
+25 -40
View File
@@ -1,13 +1,9 @@
/// <reference types="cypress" />
const openCustomPopupForm = () => {
cy.get('[data-cy="custom-pop-search-button"]').click()
}
const fillName = () => {
const name = `TEST-${new Date().getTime()}`
cy.contains('Insert name for this custom population if you wish to save it')
cy.get('[data-cy="custom-population-name-input"]').type(name)
cy.contains('Insert a name for this custom population if you wish to save it')
cy.cs('custom-population-name-input').type(name)
return name
}
@@ -16,38 +12,33 @@ const save = () => {
}
const selectSavedPopulation = name => {
cy.get('[data-cy="history-search"]').children().eq(0).type(`${name}{enter}`)
cy.cs('history-search').type(`${name}{enter}`)
}
const deleteAllSearches = () => {
cy.get('[data-cy="history-search"]').children().eq(0).click()
cy.contains('Saved populations')
.parent()
.parent()
.parent()
.parent()
.get('.dropdown')
.then(dropdown => {
const searchItems = dropdown.find('div[role=option] > span[class=text]')
for (let i = 0; i < searchItems.length; i++) {
if (searchItems[i].textContent.includes('TEST-')) {
cy.get('[data-cy="history-search"]').children().eq(0).type(`${searchItems[i].textContent}{enter}`)
cy.get('button').contains('Delete').click()
}
}
cy.cs('history-search').click()
cy.get('.MuiAutocomplete-popper > .MuiPaper-root')
.find('li[role=option]')
.then($lis => {
const labels = [...$lis].map(li => li.textContent?.trim() || '')
labels.forEach((label, index) => {
if (index !== 0) cy.cs('history-search').click()
cy.contains('.MuiAutocomplete-popper li[role=option]', label).click()
cy.contains('button', 'Delete').click()
})
})
}
const fillForm = (content, separator) => {
cy.get('[data-cy="student-number-input"]').type(content.join(separator))
cy.cs('student-number-input').type(content.join(separator))
}
const search = () => {
cy.get('[data-cy="search-button"]').click()
cy.cs('search-button').click()
}
const searchFor = (studentnumbers, separator) => {
openCustomPopupForm()
fillForm(studentnumbers, separator)
search()
}
@@ -74,7 +65,7 @@ const containsSpecificStudents = (studentnumbers = []) => {
}
const checkRightsNotification = studentNumbers => {
cy.get('[data-cy="rights-notification"]').within(() => {
cy.cs('rights-notification').within(() => {
cy.contains(
'The following students information could not be displayed. This could be either because they do not exist, or you do not have the right to view their information.'
)
@@ -96,7 +87,6 @@ describe('Custom population tests', () => {
cy.visit('/custompopulation')
cy.url().should('include', '/custompopulation')
cy.contains('Custom population')
cy.get('button').contains('Custom population').click()
deleteAllSearches()
})
@@ -126,7 +116,7 @@ describe('Custom population tests', () => {
cy.contains('Credit accumulation').should('not.exist')
cy.contains('Programme distribution').should('not.exist')
cy.contains('Courses of population').should('not.exist')
cy.get('button').contains('Custom population')
cy.get('button').contains('Back to search form')
})
it("Doesn't return students user has no right to", () => {
@@ -138,16 +128,15 @@ describe('Custom population tests', () => {
cy.contains('Credit accumulation').should('not.exist')
cy.contains('Programme distribution').should('not.exist')
cy.contains('Courses of population').should('not.exist')
cy.get('button').contains('Custom population')
cy.get('button').contains('Back to search form')
})
})
describe('Custom population search saving', () => {
it('Saves a custom population search', { retries: 2 }, () => {
cy.fixture('customPopulations').then(({ studentNumbersForCSStudentsSet1: students }) => {
openCustomPopupForm()
cy.contains(
'Insert student numbers you wish to use for population. Separate each number with a comma, semicolon, space, or newline.'
'Insert student numbers to use for the population. Each student number needs to be separated with a comma, a semicolon, a space, or a line break.'
)
const name = fillName()
// A comma and a newline on purpose, the page should be able to handle it
@@ -157,17 +146,16 @@ describe('Custom population tests', () => {
// Round 1
selectSavedPopulation(name)
search()
cy.contains(`Custom population "${name}"`)
cy.contains(`Custom population: ${name}`)
containsAmountOfStudents(students.length)
containsSpecificStudents(students)
// Round 2
cy.visit('/custompopulation')
cy.contains('Custom population')
openCustomPopupForm()
selectSavedPopulation(name)
search()
cy.contains(`Custom population "${name}"`)
cy.contains(`Custom population: ${name}`)
containsAmountOfStudents(students.length)
containsSpecificStudents(students)
})
@@ -176,28 +164,25 @@ describe('Custom population tests', () => {
it('Updates a custom population search', () => {
cy.fixture('customPopulations').then(
({ studentNumbersForCSStudentsSet1: students1, studentNumbersForCSStudentsSet2: students2 }) => {
openCustomPopupForm()
const name = fillName()
fillForm(students1, ' ')
save()
selectSavedPopulation(name)
search()
cy.contains(`Custom population "${name}"`)
cy.contains(`Custom population: ${name}`)
containsAmountOfStudents(students1.length)
containsSpecificStudents(students1)
cy.contains('Custom population')
openCustomPopupForm()
cy.contains('Back to search form').click()
selectSavedPopulation(name)
fillForm(['\n', ...students2], ', ')
save()
cy.visit('/custompopulation')
cy.contains('Custom population')
openCustomPopupForm()
selectSavedPopulation(name)
search()
cy.contains(`Custom population "${name}"`)
cy.contains(`Custom population: ${name}`)
containsAmountOfStudents(students1.length + students2.length)
containsSpecificStudents([...students1, ...students2])
}
@@ -30,8 +30,8 @@ const deleteTag = tagName => {
cy.cs('confirm-delete-tag-button').click()
}
describe('Study programme overview', () => {
describe('Study programme selector', () => {
describe('Degree programme overview', () => {
describe('Degree programme selector', () => {
beforeEach(() => {
cy.init('/study-programme', 'admin')
})
@@ -43,7 +43,7 @@ describe('Study programme overview', () => {
cy.contains('Doctoral programmes')
})
it('Study programme search filter', () => {
it('Degree programme search filter', () => {
cy.contains('Tietojenkäsittelytieteen kandiohjelma').should('exist')
cy.contains('Matemaattisten tieteiden kandiohjelma').should('exist')
cy.cs('study-programme-filter').type('Tietojenkäsittelytieteen')
@@ -93,31 +93,14 @@ describe('Study programme overview', () => {
it('Basic information contains correct credits', () => {
const years = getEmptyYears()
const tableContents = [
...years.map(year => [year, 0, 0, 0, 0, 0]),
[2023, 1519, 1519, 0, 0, 222],
[2022, 3235, 3205, 0, 30, 209],
[2021, 5133, 5108, 0, 25, 428],
[2020, 5801, 5796, 0, 5, 94],
[2019, 5305, 5305, 0, 0, 162],
[2018, 3442, 3432, 0, 10, 21],
[2017, 1211, 1211, 0, 0, 189],
]
cy.checkTableStats(tableContents, 'credits-produced-by-the-study-programme')
})
it("Toggling 'Show special categories' on displays additional information", () => {
cy.cs('special-categories-toggle').click()
const years = getEmptyYears()
const tableContents = [
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0]),
[2023, 1519, 1519, 0, 0, 0, 0, 222],
[2022, 3235, 3205, 0, 30, 0, 0, 209],
[2021, 5133, 5108, 0, 25, 0, 0, 428],
[2020, 5801, 5796, 0, 5, 0, 0, 94],
[2019, 5305, 5305, 0, 0, 0, 0, 162],
[2018, 3442, 3432, 0, 10, 0, 0, 21],
[2017, 1211, 1211, 0, 0, 0, 0, 189],
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
[2023, 1519, 1519, 0, 0, 0, 0, 0, 222, 0],
[2022, 3235, 3205, 0, 0, 30, 0, 0, 209, 0],
[2021, 5133, 5108, 0, 0, 25, 0, 0, 428, 25],
[2020, 5801, 5796, 0, 0, 5, 0, 0, 94, 0],
[2019, 5305, 5305, 0, 0, 0, 0, 0, 162, 0],
[2018, 3442, 3432, 0, 0, 10, 0, 0, 21, 0],
[2017, 1211, 1211, 0, 0, 0, 0, 0, 189, 0],
]
cy.checkTableStats(tableContents, 'credits-produced-by-the-study-programme')
@@ -190,14 +173,14 @@ describe('Study programme overview', () => {
cy.checkTableStats(studentTableContents, 'students-of-the-study-programme')
const creditTableContents = [
...years.map(year => [year, 0, 0, 0, 0, 0]),
['2023 - 2024', 160, 160, 0, 0, 67],
['2022 - 2023', 2725, 2720, 0, 5, 337],
['2021 - 2022', 4092, 4042, 0, 50, 198],
['2020 - 2021', 5420, 5415, 0, 5, 321],
['2019 - 2020', 6043, 6043, 0, 0, 101],
['2018 - 2019', 4856, 4851, 0, 5, 107],
['2017 - 2018', 2350, 2345, 0, 5, 26],
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
['2023 - 2024', 160, 160, 0, 0, 0, 0, 0, 67, 0],
['2022 - 2023', 2725, 2720, 0, 0, 5, 0, 0, 337, 0],
['2021 - 2022', 4092, 4042, 0, 0, 50, 0, 0, 198, 0],
['2020 - 2021', 5420, 5415, 0, 0, 5, 0, 0, 321, 25],
['2019 - 2020', 6043, 6043, 0, 0, 0, 0, 0, 101, 0],
['2018 - 2019', 4856, 4851, 0, 0, 5, 0, 0, 107, 0],
['2017 - 2018', 2350, 2345, 0, 0, 5, 0, 0, 26, 0],
]
cy.checkTableStats(creditTableContents, 'credits-produced-by-the-study-programme')
@@ -224,19 +207,20 @@ describe('Study programme overview', () => {
.should('contain', 47)
.should('contain', 76)
cy.cs('bachelor-breakdown-bar-chart-section')
cy.cs('unset-breakdown-bar-chart')
cy.cs('graduation-time-toggle').click()
cy.cs('bachelor-median-time-bar-chart-section').within(() => {
cy.contains('Graduation year')
cy.contains('2022')
cy.contains('48 graduated').trigger('mouseover')
cy.contains('48 students graduated in year 2022')
cy.contains('median study time: 44.5 months')
cy.contains('22 graduated on time')
cy.contains('12 graduated max year overtime')
cy.contains('14 graduated over year late')
cy.cs('unset-median-bar-chart').within(() => {
cy.contains('48 graduated').should('be.visible')
cy.contains('48 graduated').hover()
})
cy.get('.grad-vals').contains('48 students graduated in year 2022').should('be.visible')
cy.contains('median study time: 6.5 semesters')
cy.contains('24 graduated on time')
cy.contains('12 graduated max year overtime')
cy.contains('12 graduated over year late')
cy.cs('programmes-before-or-after-stacked-bar-chart-section')
.should('contain', 'Tietojenkäsittelytieteen maisteriohjelma')
.should('contain', 'Datatieteen maisteriohjelma')
@@ -252,33 +236,37 @@ describe('Study programme overview', () => {
cy.init('/study-programme')
cy.contains('a', 'Matematiikan ja tilastotieteen maisteriohjelma').click({ force: true })
cy.cs('bachelor-breakdown-bar-chart-section')
cy.cs('master-breakdown-bar-chart-section')
cy.cs('unset-breakdown-bar-chart')
cy.cs('unset-breakdown-bar-chart')
cy.cs('graduation-time-toggle').click()
cy.cs('master-median-time-bar-chart-section').within(() => {
cy.contains('Master study right')
cy.contains('Graduation year')
cy.contains('2021')
cy.contains('2 graduated').trigger('mouseover')
cy.contains('2 students graduated in year 2021')
cy.contains('median study time: 25 months')
cy.contains('1 graduated on time')
cy.contains('1 graduated max year overtime')
cy.contains('0 graduated over year late')
})
cy.cs('unset-graduation-times-section')
.eq(1)
.within(() => {
cy.contains('Master study right')
cy.contains('2 graduated').should('be.visible')
cy.contains('2 graduated').hover()
})
cy.cs('bachelor-median-time-bar-chart-section').within(() => {
cy.contains('Bachelor + master study right')
cy.contains('Graduation year')
cy.contains('2023')
cy.contains('11 graduated').trigger('mouseover')
cy.contains('11 students graduated in year 2023')
cy.contains('median study time: 69 months')
cy.contains('3 graduated on time')
cy.contains('7 graduated max year overtime')
cy.contains('1 graduated over year late')
})
cy.get('.grad-vals').contains('2 students graduated in year 2021').should('be.visible')
cy.contains('median study time: 4.5 semesters')
cy.contains('1 graduated on time')
cy.contains('1 graduated max year overtime')
cy.contains('0 graduated over year late')
cy.cs('unset-graduation-times-section')
.eq(0)
.within(() => {
cy.contains('Bachelor + master study right')
cy.contains('11 graduated').should('be.visible')
cy.contains('11 graduated').hover()
})
cy.get('.grad-vals').contains('11 students graduated in year 2023').should('be.visible')
cy.contains('median study time: 12 semesters')
cy.contains('4 graduated on time')
cy.contains('6 graduated max year overtime')
cy.contains('1 graduated over year late')
})
})
@@ -286,11 +274,11 @@ describe('Study programme overview', () => {
beforeEach(() => {
cy.intercept(
'GET',
'api/v2/studyprogrammes/KH50_001/studytrackstats?graduated=GRADUATED_INCLUDED&special_groups=SPECIAL_INCLUDED&combined_programme='
'api/studyprogrammes/KH50_001/studytrackstats?special_groups=SPECIAL_INCLUDED&combined_programme='
).as('stQuery')
cy.init('/study-programme')
cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click()
cy.cs('study-tracks-and-class-statistics-tab').click()
cy.cs('StudyTracksAndClassStatisticsTab').click()
})
describe('Info boxes', () => {
@@ -317,9 +305,9 @@ describe('Study programme overview', () => {
cy.cs('average-graduation-times-section')
})
it('Students of the study programme are shown correctly', () => {
it('Students of the degree programme are shown correctly', () => {
const tableContents = [
// [Year, All, Started studying, Present, Absent, Inactive, Graduated, Men, Women, Other/Unknown, Finland, Other]
// [Year, All, Started studying, Present, Absent, Passive, Graduated, Men, Women, Other/Unknown, Finland, Other]
['2023 - 2024', 8, 8, 0, 0, 8, 0, 5, 3, 0, 8, 1],
['2022 - 2023', 26, 25, 0, 0, 24, 2, 19, 7, 0, 25, 1],
['2021 - 2022', 38, 29, 0, 0, 33, 5, 29, 9, 0, 36, 4],
@@ -372,14 +360,14 @@ describe('Study programme overview', () => {
describe('Population link button works for', () => {
it('a single year', () => {
cy.cs('2023-population-link-button').first().click()
cy.contains('Matemaattisten tieteiden kandiohjelma 2023 - 2024')
cy.contains('Class size 8 students')
cy.contains('Matemaattisten tieteiden kandiohjelma')
cy.contains('Class of 2023 - 2024, 8 students')
})
it('total', () => {
cy.cs('total-population-link-button').click()
cy.contains('Matemaattisten tieteiden kandiohjelma')
cy.contains('Class size 227 students')
cy.contains('Class of 2017 - 2026, 227 students')
})
it('Links to class statistics page with study track info included work', () => {
@@ -388,9 +376,9 @@ describe('Study programme overview', () => {
cy.get('a').click()
})
cy.contains('Matemaattisten tieteiden kandiohjelma 2022 - 2023')
cy.contains('Matemaattisten tieteiden kandiohjelma')
cy.contains('Studytrack MAT-MAT')
cy.contains('Class size 26 students')
cy.contains('Class of 2022 - 2023, 26 students')
cy.contains('Showing 3 out of 26 students')
})
})
@@ -398,55 +386,68 @@ describe('Study programme overview', () => {
it('Student progress data is shown correctly', () => {
const years = getEmptyYears(true)
const tableContents = [
// [Year, All, < 30 credits, 30–60 credits, 60–90 credits, 90–120 credits, 120–150 credits, 150–180 credits, ≥ 180 credits]
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0, 0]),
['2023 - 2024', 8, 8, 0, 0, 0, 0, 0, 0],
['2022 - 2023', 26, 9, 9, 4, 3, 0, 1, 0],
['2021 - 2022', 38, 8, 6, 11, 8, 5, 0, 0],
['2020 - 2021', 30, 2, 1, 3, 7, 4, 4, 9],
['2019 - 2020', 35, 1, 0, 2, 1, 0, 4, 27],
['2018 - 2019', 45, 0, 1, 1, 2, 0, 3, 38],
['2017 - 2018', 47, 0, 1, 3, 0, 1, 2, 40],
['Total', 229, 28, 18, 24, 21, 10, 14, 114],
// [Year, All, < 30 credits, 30–60 credits, 60–90 credits, 90–120 credits, 120–150 credits, 150–180 credits, ≥ 180 credits, Graduated]
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
['2023 - 2024', 8, 8, 0, 0, 0, 0, 0, 0, 0],
['2022 - 2023', 26, 9, 8, 4, 3, 0, 0, 0, 2],
['2021 - 2022', 38, 8, 4, 9, 8, 4, 0, 0, 5],
['2020 - 2021', 30, 2, 1, 2, 5, 0, 0, 1, 19],
['2019 - 2020', 35, 1, 0, 2, 1, 0, 0, 4, 27],
['2018 - 2019', 45, 0, 1, 1, 2, 0, 0, 2, 39],
['2017 - 2018', 47, 0, 1, 3, 0, 0, 1, 0, 42],
['Total', 229, 28, 15, 21, 19, 4, 1, 7, 134],
]
cy.checkTableStats(tableContents, 'study-programme-progress')
})
it('Progress section', () => {
cy.cs('programme-progress-bar-chart-section').scrollIntoView()
cy.cs('programme-progress-bar-chart-section')
.should('contain', 'Less than 30 credits')
.should('contain', '30–60 credits')
.should('contain', 'At least 180 credits')
.should('contain', '49.8%') // The percentage for total, at least 180 credits, to check that the graph renders
.should('contain', '58.5%') // The percentage for total graduated, to check that the graph renders
cy.cs('programme-progress-bar-chart-section').contains('49.8%').trigger('mouseover', { force: true })
cy.contains('At least 180 credits: 114')
cy.cs('programme-progress-bar-chart-section').contains('58.5%').hover()
cy.contains('Graduated: 134')
})
it('Average graduation times section', () => {
// CI contains different data than locally
it.skip('Average graduation times section', () => {
cy.cs('average-graduation-times-section').scrollIntoView()
cy.cs('average-graduation-times-section').within(() => {
cy.contains('2020 - 2021')
cy.get('[aria-label="2020 - 2021, 15. On time."]').trigger('mouseover')
cy.contains('On time: 15')
cy.contains("Click a bar to view that year's study track level breakdown")
cy.get('[aria-label="2019 - 2020, 22. On time."]').click()
cy.cs('unset-graduation-times-section').within(() => {
cy.contains('2020 - 2021')
})
cy.get('text').contains('16').hover()
cy.contains('On time: 16')
cy.cs('unset-graduation-times-section').within(() => {
cy.contains("Click a bar to view that year's study track level breakdown")
cy.get('text').contains('22').click()
})
cy.contains('Year 2019 - 2020 by start year')
cy.get('[aria-label="MAT-MAT, 14. On time."]').trigger('mouseover')
cy.get('text').contains('13').hover()
cy.contains('Matematiikka')
cy.contains('MAT-MAT')
cy.contains('On time: 14')
cy.contains('On time: 13')
})
cy.cs('graduation-time-toggle').click()
cy.cs('average-graduation-times-section').within(() => {
cy.contains('2020 - 2021')
cy.contains('19 graduated').trigger('mouseover')
cy.contains('19 graduated').hover()
cy.contains('From class of 2020 - 2021, 19/30 students have graduated')
cy.contains('median study time: 33 months')
cy.contains('15 graduated on time')
cy.contains('3 graduated max year overtime')
cy.contains('median study time: 6 semesters')
cy.contains('16 graduated on time')
cy.contains('2 graduated max year overtime')
cy.contains('1 graduated over year late')
})
})
@@ -465,7 +466,7 @@ describe('Study programme overview', () => {
it('Students of the study track are shown correctly', () => {
cy.cs('study-track-overview-section').contains('Students of the study track MAT-MAT by starting year')
const tableContents = [
// [Year, All, Started studying, Present, Absent, Inactive, Graduated, Men, Women, Other/Unknown, Finland, Other]
// [Year, All, Started studying, Present, Absent, Passive, Graduated, Men, Women, Other/Unknown, Finland, Other]
['2022 - 2023', 3, 3, 0, 0, 1, 2, 2, 1, 0, 3, 0],
['2021 - 2022', 5, 1, 0, 0, 1, 4, 3, 2, 0, 5, 0],
['2020 - 2021', 13, 10, 0, 0, 1, 12, 7, 6, 0, 12, 3],
@@ -480,15 +481,16 @@ describe('Study programme overview', () => {
it('Links to class statistics page with study track info included work', () => {
cy.cs('2020-population-link-button').first().click()
cy.contains('Matemaattisten tieteiden kandiohjelma 2020 - 2021')
cy.contains('Matemaattisten tieteiden kandiohjelma')
cy.contains('Studytrack MAT-MAT')
cy.contains('Class size 30 students')
cy.contains('Class of 2020 - 2021, 30 students')
cy.contains('Showing 10 out of 30 students')
})
it('Info message about missing progress stats is displayed', () => {
// FIXME: This feature was implemented. The data is no-good, so maybe reimplement this when it's clean.
it.skip('Info message about missing progress stats is displayed', () => {
cy.contains('Progress of students of the study track MAT-MAT by starting year')
cy.contains('Progress data is currently only available for all students of the study programme.')
cy.contains('Progress data is currently only available for all students of the degree programme.')
})
it.skip('Average graduation times are displayed correctly', () => {
@@ -496,7 +498,7 @@ describe('Study programme overview', () => {
cy.get("[data-cy='graduation-times-graph-breakdownBachelor']").within(() => {
cy.contains('Start year')
cy.contains('2020 - 2021')
cy.get('[aria-label="2020 - 2021, 9. On time."]').trigger('mouseover')
cy.get('[aria-label="2020 - 2021, 9. On time."]').hover()
cy.contains('Graduated On time: 9 students')
})
@@ -504,7 +506,7 @@ describe('Study programme overview', () => {
cy.cs('bachelor-median-time-bar-chart').within(() => {
cy.contains('Start year')
cy.contains('2020 - 2021')
cy.contains('12 graduated').trigger('mouseover')
cy.contains('12 graduated').hover()
cy.contains('From class of 2020 - 2021, 12/13 students have graduated')
cy.contains('median study time: 34 months')
cy.contains('9 graduated on time')
@@ -519,7 +521,7 @@ describe('Study programme overview', () => {
beforeEach(() => {
cy.init('/study-programme')
cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click()
cy.cs('programme-courses-tab').click()
cy.cs('ProgrammeCoursesTab').click()
cy.cs('by-credit-type-section')
cy.cs('by-credit-type-tab')
@@ -528,14 +530,21 @@ describe('Study programme overview', () => {
describe('By credit type tab', () => {
beforeEach(() => {
cy.get('tbody').within(() => {
cy.get('thead').within(() => {
cy.get('tr')
.eq(0)
.within(() => {
cy.get('th').eq(3).click()
})
})
cy.get('tbody').within(() => {
cy.get('tr')
.eq(8)
.within(() => {
cy.get('td').eq(0).contains('Course')
cy.get('td').eq(1).contains('MAT21001')
cy.get('td').eq(2).contains('Lineaarialgebra ja matriisilaskenta II')
cy.get('td').eq(3).contains('341')
cy.get('td').eq(1).contains('MAT11004')
cy.get('td').eq(2).contains('Differentiaalilaskenta')
cy.get('td').eq(3).contains('1170')
})
})
})
@@ -548,12 +557,12 @@ describe('Study programme overview', () => {
cy.get('tbody').within(() => {
cy.get('tr')
.eq(0)
.eq(9)
.within(() => {
cy.get('td').eq(0).contains('Course')
cy.get('td').eq(1).contains('MAT21001')
cy.get('td').eq(2).contains('Lineaarialgebra ja matriisilaskenta II')
cy.get('td').eq(3).contains('121')
cy.get('td').eq(1).contains('MAT21009')
cy.get('td').eq(2).contains('Kandidaatintutkielma')
cy.get('td').eq(3).contains('414')
})
})
})
@@ -568,12 +577,12 @@ describe('Study programme overview', () => {
cy.get('tbody').within(() => {
cy.get('tr')
.eq(0)
.eq(5)
.within(() => {
cy.get('td').eq(0).contains('Course')
cy.get('td').eq(1).contains('MAT21001')
cy.get('td').eq(2).contains('Lineaarialgebra ja matriisilaskenta II')
cy.get('td').eq(3).contains('311')
cy.get('td').eq(1).contains('MAT11001')
cy.get('td').eq(2).contains('Johdatus yliopistomatematiikkaan')
cy.get('td').eq(3).contains('1220')
})
})
})
@@ -632,31 +641,67 @@ describe('Study programme overview', () => {
})
it("'Show credits/Show students' toggle works", () => {
cy.get('thead tr').within(() => {
cy.get('th').should('have.length', 8)
const headers = ['Type', 'Code', 'Name', 'Total', 'Major', 'Non-major', 'Non-degree', 'Transferred']
headers.forEach((header, index) => {
cy.get('th').eq(index).contains(header)
cy.get('thead tr')
.eq(0)
.within(() => {
cy.get('th').should('have.length', 6)
const headers = ['Type', 'Code', 'Name', 'Total credits', 'Credits produced by', 'Transferred credits']
headers.forEach((header, index) => {
cy.get('th').eq(index).contains(header)
})
})
cy.get('thead tr')
.eq(1)
.within(() => {
cy.get('th').should('have.length', 7)
const headers = [
'Degree students',
'Open university (hetu)',
'Open university (no hetu)',
'Exchange students',
'Other universities',
'Separate studies',
'Other',
]
headers.forEach((header, index) => {
cy.get('th').eq(index).contains(header)
})
})
})
cy.cs('show-credits-students-toggle').click()
cy.get('thead tr')
.eq(0)
.within(() => {
cy.get('th').should('have.length', 7)
const headers = [
'Type',
'Code',
'Name',
'Total students',
'Breakdown of total',
'Breakdown of passed',
'Not included in total or passed',
]
headers.forEach((header, index) => {
cy.get('th').eq(index).contains(header)
})
})
cy.get('thead tr')
.eq(1)
.within(() => {
cy.get('th').should('have.length', 10)
const headers = [
'Type',
'Code',
'Name',
'Total',
'Passed',
'Not',
'Major',
'Non-major',
'Non-degree',
'Transferred',
'Not completed',
'Degree students',
'Open university students (with hetu)',
'Open university students (without hetu)',
'Exchange students',
'Other university students',
'Separate studies',
'Other students',
'Students with transferred credits',
]
headers.forEach((header, index) => {
cy.get('th').eq(index).contains(header)
@@ -665,12 +710,12 @@ describe('Study programme overview', () => {
cy.get('tbody').within(() => {
cy.get('tr')
.eq(0)
.eq(5)
.within(() => {
cy.get('td').eq(0).contains('Course')
cy.get('td').eq(1).contains('MAT21001')
cy.get('td').eq(2).contains('Lineaarialgebra ja matriisilaskenta II')
cy.get('td').eq(3).contains('341')
cy.get('td').eq(1).contains('MAT11001')
cy.get('td').eq(2).contains('Johdatus yliopistomatematiikkaan')
cy.get('td').eq(3).contains('375')
})
})
})
@@ -682,7 +727,7 @@ describe('Study programme overview', () => {
})
// FIXME: Flaky
it('test', () => {
it.skip('test', () => {
// TODO: Implement some real tests for this tab
cy.contains('From')
})
@@ -693,7 +738,7 @@ describe('Study programme overview', () => {
beforeEach(() => {
cy.init('/study-programme')
cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click()
cy.cs('degree-courses-tab').click()
cy.cs('DegreeCoursesTab').click()
})
it('Curriculum section', () => {
@@ -729,7 +774,7 @@ describe('Study programme overview', () => {
beforeEach(() => {
cy.init('/study-programme')
cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click({ force: true })
cy.cs('tags-tab').click()
cy.cs('TagsTab').click()
})
it('info box', () => {
@@ -739,13 +784,13 @@ describe('Study programme overview', () => {
describe('Adding tags to populations and removing them works', () => {
beforeEach(() => {
cy.contains('This study programme does not have any tags yet')
cy.contains('This degree programme does not have any tags yet')
cy.cs('tag-name-text-field').type(tagName)
})
afterEach(() => {
deleteTag(tagName)
cy.contains('This study programme does not have any tags yet')
cy.contains('This degree programme does not have any tags yet')
})
it('can create and delete tags without start year', () => {
@@ -797,7 +842,8 @@ describe('Study programme overview', () => {
cy.contains('Students added to tag')
cy.cs('population-link-button').click()
cy.contains('Matemaattisten tieteiden kandiohjelma 2022 - 2023')
cy.contains('Matemaattisten tieteiden kandiohjelma')
cy.contains('Class of 2022 - 2023')
cy.contains(`Tagged with: ${tagName}`)
cy.contains('Students (5)')
.parent()
@@ -856,7 +902,7 @@ describe('Study programme overview', () => {
})
it('can access basic information', () => {
cy.cs('basic-information-tab').click()
cy.cs('BasicInformationTab').click()
cy.cs('students-of-the-study-programme-section')
cy.cs('credits-produced-by-the-study-programme-section')
@@ -866,7 +912,7 @@ describe('Study programme overview', () => {
})
it('can access study tracks', () => {
cy.cs('study-tracks-and-class-statistics-tab').click()
cy.cs('StudyTracksAndClassStatisticsTab').click()
cy.cs('study-track-overview-section')
cy.cs('progress-of-students-section')
@@ -874,9 +920,9 @@ describe('Study programme overview', () => {
})
it("doesn't see other tabs", () => {
cy.cs('degree-courses-tab').should('not.exist')
cy.cs('tags-tab').should('not.exist')
cy.cs('update-statistics-tab').should('not.exist')
cy.cs('DegreeCoursesTab').should('not.exist')
cy.cs('TagsTab').should('not.exist')
cy.cs('UpdateStatisticsTab').should('not.exist')
})
})
})
+1 -1
View File
@@ -273,7 +273,7 @@ describe('Faculty statistics', () => {
// TODO: Implement
})
it.skip('Study programme toggle', () => {
it.skip('Degree programme toggle', () => {
// TODO: Implement
})
+11 -12
View File
@@ -179,12 +179,12 @@ describe.skip('Population Statistics', { testIsolation: false }, () => {
]
cy.cs('courseFilter-header').click()
cy.selectFromDropdown('courseFilter', `${courses[0].code} - ${courses[0].name}`)
cy.selectFromAutocomplete('courseFilter', `${courses[0].code} - ${courses[0].name}`)
checkFilteredStudentCount(9)
cy.selectFromDropdown(`courseFilter-${courses[0].code}`, 1)
checkFilteredStudentCount(7)
cy.selectFromDropdown('courseFilter', `${courses[1].code} - ${courses[1].name}`)
checkFilteredStudentCount(5)
cy.selectFromAutocomplete('courseFilter', `${courses[1].code} - ${courses[1].name}`)
checkFilteredStudentCount(6)
cy.selectFromDropdown(`courseFilter-${courses[1].code}`, 1)
checkFilteredStudentCount(4)
@@ -273,12 +273,12 @@ describe('Population Statistics with Bachelor + Master', { testIsolation: false
checkFilteredStudentCount(0)
// Non-Bachelor
cy.get('input[name="studyRightStatusFilter"][value="Inactive Bachelor study right"]').click()
cy.get('input[name="studyRightStatusFilter"][value="Inactive Bachelor study right"]').should('be.checked')
cy.get('input[name="studyRightStatusFilter"][value="Passive Bachelor study right"]').click()
cy.get('input[name="studyRightStatusFilter"][value="Passive Bachelor study right"]').should('be.checked')
checkFilteredStudentCount(5)
// Non-Master (combined)
cy.get('input[name="studyRightStatusFilter"][value="Inactive Master study right"]').click()
cy.get('input[name="studyRightStatusFilter"][value="Inactive Master study right"]').should('be.checked')
cy.get('input[name="studyRightStatusFilter"][value="Passive Master study right"]').click()
cy.get('input[name="studyRightStatusFilter"][value="Passive Master study right"]').should('be.checked')
checkFilteredStudentCount(24)
cy.cs('studyRightStatusFilter-clear').click()
@@ -385,7 +385,6 @@ describe('Custom Population Statistics', { testIsolation: false }, () => {
before(() => {
cy.init('/custompopulation')
cy.cs('custom-pop-search-button').click()
cy.fixture('customPopulations').then(({ studentNumbersForCSStudentsSet1, studentNumbersForCSStudentsSet2 }) => {
const students = [...studentNumbersForCSStudentsSet1, ...studentNumbersForCSStudentsSet2]
defaultAmountOfStudents = students.length
@@ -449,14 +448,14 @@ describe('Custom Population Statistics', { testIsolation: false }, () => {
]
cy.cs('courseFilter-header').click()
cy.selectFromDropdown('courseFilter', `${courses[0].code} - ${courses[0].name}`)
cy.selectFromAutocomplete('courseFilter', `${courses[0].code} - ${courses[0].name}`)
checkFilteredStudentCount(7)
cy.selectFromDropdown(`courseFilter-${courses[0].code}`, 1)
checkFilteredStudentCount(6)
cy.selectFromDropdown('courseFilter', `${courses[1].code} - ${courses[1].name}`)
checkFilteredStudentCount(5)
cy.selectFromAutocomplete('courseFilter', `${courses[1].code} - ${courses[1].name}`)
checkFilteredStudentCount(6)
cy.selectFromDropdown(`courseFilter-${courses[1].code}`, 3)
checkFilteredStudentCount(2)
checkFilteredStudentCount(6)
cy.cs(`courseFilter-${courses[1].code}-clear`).click()
checkFilteredStudentCount(6)
+19 -56
View File
@@ -1,42 +1,23 @@
/// <reference types="cypress" />
const hasLanded = () => {
cy.contains('This view displays amounts')
cy.contains('Show number of')
}
const chooseSemester = (semester, fromOrTo) => {
cy.get(`[data-cy="semester-${fromOrTo}"]`).click()
cy.get(`[data-cy="semester-${fromOrTo}"]`).within(() => {
cy.get('div.visible.menu.transition').within(() => {
cy.contains('div.item', semester).click()
})
cy.get('div.menu.transition').should('not.have.class', 'visible')
})
cy.cs(`semester-${fromOrTo}`).click()
cy.cs(`select-opt-${semester}`).click()
cy.cs(`select-opt-${semester}`).should('not.have.class', 'visible')
}
const checkNumbers = (numbers, numberOfColumns, mode) => {
const offset = mode === 'semesters' ? 2 : 1
cy.get('table > tbody > tr:first').within(() => {
cy.get('td').should('have.length', numberOfColumns)
cy.get('td').eq(0).contains('All courses total')
numbers.forEach((number, index) => {
cy.get('td')
.eq(index + offset)
.contains(number)
})
})
}
const offset = 1 // skip first col with codes / names
cy.cs(`ooditable-${mode}`).find('table').find('tbody').as('tbody')
cy.get('@tbody').find('tr').first().as('totalRow')
cy.get('@totalRow').find('td').should('have.length', numberOfColumns)
cy.get('@totalRow').find('td').eq(0).should('contain', 'All courses total')
const checkStyle = (styles, numberOfColumns) => {
cy.get('table > tbody > tr:first').within(() => {
cy.get('td').should('have.length', numberOfColumns)
styles.forEach((style, index) => {
cy.get('td')
.eq(index + 2)
.should('have.attr', 'style')
.and('include', style)
})
numbers.forEach((number, index) => {
cy.get('@totalRow')
.find('td')
.eq(index + offset)
.should('contain', number)
})
}
@@ -44,8 +25,7 @@ describe('When language center is opened', () => {
describe('as an admin user', () => {
beforeEach(() => {
cy.init('/languagecenterview', 'admin')
cy.get('[data-cy="completions-button"]').click()
hasLanded()
cy.cs('completions-button').click()
})
describe('Faculties tab', () => {
@@ -64,7 +44,7 @@ describe('When language center is opened', () => {
cy.get('table > tbody > tr:first').within(() => {
cy.get('td').should('have.length', 15)
cy.get('td').eq(0).contains('All courses total')
const numbers = [36, 9, 33, 756, 15, 40, 6, 48, 1, 24, 1059, 49, 0, 2076]
const numbers = [2076, 36, 9, 33, 757, 15, 40, 6, 48, 1, 24, 1059, 48, 0]
numbers.forEach((number, index) => {
cy.get('td')
.eq(index + 1)
@@ -73,16 +53,14 @@ describe('When language center is opened', () => {
})
})
// FIXME: Flaky
it('Faculties tab "exceeding" button works', () => {
cy.get('[data-cy="difference-button"]').click()
checkNumbers([...new Array(12).fill(0), 1, 221], 15)
cy.cs('difference-button').click()
checkNumbers([63, 2, 0, 3, 13, 0, 1, 1, 2, 0, 0, 30, 10, 1], 15, 'faculties')
})
// FIXME: Flaky
it('Faculties tab semester selector changes numbers', () => {
chooseSemester('Syksy 2020', 'from')
checkNumbers([28, 6, 27, 496, 4, 10, 2, 33, 0, 9, 535, 34, 0, 1184], 15)
checkNumbers([1184, 28, 6, 27, 497, 4, 10, 2, 33, 0, 9, 535, 33, 0], 15, 'faculties')
})
})
@@ -93,23 +71,8 @@ describe('When language center is opened', () => {
chooseSemester('Kevät 2024', 'to')
})
// FIXME: Flaky
it('Semester tab shows numbers', () => {
checkNumbers([69, 26, 298, 58, 343, 98, 438, 138, 310, 90, 123, 74, 10, 1, 2076], 17, 'semesters')
})
// FIXME: Flaky
it('Coloring mode works on semester tab', () => {
cy.contains('Compare to average of course').click()
const expectedAlphas = [0.04, 0.016, 0.17, 0.03, 0.192, 0.055, 0.247, 0.08, 0.173, 0.05, 0.07, 0.043, 0.004, 0]
checkStyle(
expectedAlphas.map(alpha => `background-color: rgba(0, 170, 0, ${alpha})`),
17
)
cy.get('table > tbody > tr:first').within(() => {
cy.get('td').eq(16).should('have.attr', 'style').and('not.include', 'background-color')
})
checkNumbers([2076, 69, 26, 298, 58, 343, 98, 438, 138, 310, 90, 123, 74, 10, 1], 16, 'semesters')
})
})
})
-124
View File
@@ -1,124 +0,0 @@
/// <reference types="cypress" />
const deleteAllSearches = () => {
cy.cs('open-uni-search-button').click()
cy.contains('Saved populations').parent().parent().parent().parent().get('.search').as('search')
cy.get('@search').then($el => {
const searchItems = $el.find('div[role=option] > span[class=text]')
for (let i = searchItems.length - 1; 0 <= i; i--) {
if (searchItems[i].textContent.includes('TEST-')) {
cy.get('[data-cy="history-search"]').children().eq(0).type(`${searchItems[i].textContent}{enter}`)
cy.get('button').contains('Delete').click()
}
}
})
}
describe('Open uni population tests', () => {
describe('Open uni form can be used', () => {
// When openUniUser is created, change it here
beforeEach(() => {
cy.init('/openunipopulation', 'admin')
})
after(() => {
cy.init('/openunipopulation', 'admin')
cy.contains('Open uni student population')
deleteAllSearches()
})
// TODO: Rewrite the whole component
it.skip('Finds a proper population', () => {
cy.fixture('openUniPopulation').then(({ courseCodesSet1 }) => {
cy.get('[data-cy="open-uni-search-button"]').click()
cy.contains('Insert course code(s)').siblings().get('textarea').as('textarea')
cy.get('@textarea').type(courseCodesSet1.join('\n'))
cy.cs('begin-of-search').click()
cy.cs('begin-of-search').clear()
cy.cs('begin-of-search').type('01.01.2015')
cy.cs('end-of-search').click()
cy.cs('end-of-search').clear()
cy.cs('end-of-search').type('01.01.2020')
cy.get('button').contains('Search population').click()
cy.contains('Beginning of the search for all fields:')
cy.contains('01.01.2015')
cy.contains('End of the search for enrollments:')
cy.contains('01.01.2020')
cy.location('pathname').should('eq', '/openunipopulation')
cy.location('search').then(search => {
const params = new URLSearchParams(search)
const courseCodes = params.getAll('courseCode')
expect(courseCodes).to.include.members(['TKT10001', 'TKT10002'])
expect(params.get('startdate')).to.eq('01-01-2015')
expect(params.get('enddate')).to.eq('01-01-2020')
})
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', courseCodesSet1[0])
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', courseCodesSet1[1])
})
})
it('Textarea can handle different kinds of input', () => {
cy.get('[data-cy="open-uni-search-button"]').click()
cy.contains('Insert course code(s)')
.siblings()
.get('textarea')
.type('tkt10001 tkt10002,tkt10003\n TKT10004,,TKt10005\nTKT-1234')
cy.get('button').contains('Search population').click()
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', 'TKT10001')
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', 'TKT10002')
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', 'TKT10003')
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', 'TKT10004')
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', 'TKT10005')
cy.get('[data-cy="open-uni-table-div"]').should('not.contain', 'TKT-1234')
})
it('Saves a custom population search', () => {
cy.fixture('openUniPopulation').then(({ courseCodesSet1 }) => {
cy.get('[data-cy="open-uni-search-button"]').click()
cy.get('[data-cy="search-name"]').type('TEST-Avoimen-populaatio')
cy.contains('Insert course code(s)').siblings().get('textarea').type(courseCodesSet1.join('\n'))
cy.get('[data-cy="save-search"]').click()
cy.get('[data-cy="history-search"]').children().eq(0).type('TEST-Avoimen-populaatio{enter}')
cy.contains('TEST-Avoimen-populaatio')
cy.get('button').contains('Search population').as('btn')
cy.get('@btn').click()
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', courseCodesSet1[0])
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', courseCodesSet1[1])
})
})
it('Updates a custom population search', () => {
cy.fixture('openUniPopulation').then(({ courseCodesSet1, courseCodesSet2 }) => {
cy.get('[data-cy="open-uni-search-button"]').click()
cy.get('[data-cy="search-name"]').type('TEST-Avoimen-populaatio')
cy.contains('Insert course code(s)').siblings().get('textarea').type(courseCodesSet1.join('\n'))
cy.get('[data-cy="save-search"]').click()
cy.get('button').contains('Search population').click()
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', courseCodesSet1[0])
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', courseCodesSet1[1])
cy.get('[data-cy="open-uni-search-button"]').click()
cy.get('[data-cy="history-search"]').children().eq(0).type('TEST-Avoimen-populaatio{enter}')
cy.contains('Insert course code(s)').siblings().get('textarea').should('contain', courseCodesSet1.join(', '))
cy.contains('Insert course code(s)').siblings().get('textarea').type('\n')
cy.contains('Insert course code(s)').siblings().get('textarea').type(courseCodesSet2.join('\n'))
cy.get('[data-cy="save-search"]').click()
cy.get('button').contains('Search population').click()
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', courseCodesSet1[0])
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', courseCodesSet1[1])
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', courseCodesSet2[0])
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', courseCodesSet2[1])
cy.get('[data-cy="open-uni-table-div"]').should('contain.text', courseCodesSet2[2])
})
})
it('View does not crash with non-existing courses', () => {
cy.get('[data-cy="open-uni-search-button"]').click()
cy.contains('Insert course code(s)').siblings().get('textarea').type('cypress-fun')
cy.get('button').contains('Search population').click()
cy.get('[data-cy="open-uni-table-div"]').should('not.contain', 'cypress-fun')
})
})
})
+31 -33
View File
@@ -27,14 +27,14 @@ describe('Population statistics tests', () => {
describe('When using basic user', () => {
beforeEach(() => {
cy.init('/populations')
cy.intercept('/api/v3/populationstatistics/studyprogrammes').as('studyprogrammes')
cy.intercept('/api/v2/studyprogrammes/**/studytracks').as('studytracks')
cy.intercept('/api/populationstatistics/studyprogrammes').as('studyprogrammes')
cy.intercept('/api/studyprogrammes/**/studytracks').as('studytracks')
})
describe('Population search', () => {
it('Info box works', () => {
cy.cs('PopulationSearch-info-box-content').should('not.exist')
cy.cs('PopulationSearch-info-box-button').trigger('mouseover')
cy.cs('PopulationSearch-info-box-button').hover()
cy.cs('PopulationSearch-info-box-content').should('be.visible')
cy.cs('PopulationSearch-info-box-content').contains('Tässä osiossa voi tarkastella')
cy.cs('PopulationSearch-info-box-button').trigger('mouseout')
@@ -74,8 +74,8 @@ describe('Population statistics tests', () => {
selectStudyProgramme('Matemaattisten tieteiden kandiohjelma')
cy.wait('@studytracks').its('response.statusCode').should('be.oneOf', [200, 304])
cy.contains('See class').click()
cy.contains('Matemaattisten tieteiden kandiohjelma 2017 - 2018')
cy.contains('Class size 47 students')
cy.contains('Matemaattisten tieteiden kandiohjelma')
cy.contains('Class of 2017 - 2018, 47 students')
})
// TODO: Delete MUI
@@ -95,7 +95,7 @@ describe('Population statistics tests', () => {
cy.visit(pathToMathBSc2020)
cy.cs('filtered-students')
cy.contains('Matemaattisten tieteiden kandiohjelma')
cy.contains('Class size 30 students')
cy.contains('Class of 2020 - 2021, 30 students')
cy.contains('Excludes exchange students')
cy.contains('Excludes students with non-degree study right')
cy.contains('Excludes students who have transferred out of this programme')
@@ -251,15 +251,15 @@ describe('Population statistics tests', () => {
it('Is displayed and link to individual course stats page works', () => {
cy.visit(pathToMathBSc2020)
cy.contains('Courses of class').click()
cy.intercept('/api/v3/courseyearlystats**').as('coursePage')
cy.get('[data-cy=toggle-group-module-MAT110]').click()
cy.contains('td', 'MAT11001').siblings().find('i.level.up').click()
cy.intercept('/api/courseyearlystats**').as('coursePage')
cy.cs('toggle-group-module-MAT110').click()
cy.contains('td', 'MAT11001').siblings().find('[data-testid="NorthEastIcon"]').click()
cy.wait('@coursePage')
cy.url().should('include', '/coursestatistics')
cy.contains('MAT11001 • Johdatus yliopistomatematiikkaan')
cy.contains('AYMAT11001 • Avoin yo: Johdatus yliopistomatematiikkaan')
cy.contains('57033 • Johdatus yliopistomatematiikkaan')
cy.contains('A57033 • Avoin yo: Johdatus yliopistomatematiikkaan')
cy.contains('MAT11001') // Johdatus yliopistomatematiikkaan
cy.contains('AYMAT11001') // Avoin yo: Johdatus yliopistomatematiikkaan
cy.contains('57033') // Johdatus yliopistomatematiikkaan
cy.contains('A57033') // Avoin yo: Johdatus yliopistomatematiikkaan
})
it('Curriculum selection works', () => {
@@ -319,7 +319,7 @@ describe('Population statistics tests', () => {
})
it("'General tab' is usable", () => {
cy.cs('student-table-tabs').within(() => {
cy.cs('ooditable-general').within(() => {
cy.contains('522142')
cy.contains('Tilastotiede')
cy.contains('Matematiikka')
@@ -330,18 +330,19 @@ describe('Population statistics tests', () => {
})
})
// FIXME: Flaky
it("'Courses tab' is usable", () => {
cy.cs('student-table-tabs').within(() => {
cy.contains('Courses').click()
})
cy.cs('ooditable-courses').within(() => {
cy.contains('MAT12001')
cy.contains('MAT21001')
})
})
it("'Modules tab' Displays correct modules based on the selected programme", () => {
cy.cs('student-table-tabs').within(() => {
cy.contains('Modules').click()
cy.cs('student-table-tabs').contains('Modules').click()
cy.cs('ooditable-modules').within(() => {
cy.contains('MAT011')
cy.contains('MAT110')
})
@@ -349,18 +350,17 @@ describe('Population statistics tests', () => {
cy.cs('curriculum-picker').click()
cy.contains('2023–2026').click()
cy.contains('Courses of class').click()
cy.cs('student-table-tabs').within(() => {
cy.cs('ooditable-modules').within(() => {
cy.contains('MAT011').should('not.exist')
cy.contains('MAT110')
})
})
it.skip("Empty 'tags' tab has a link to the page where tags can be created", () => {
// TODO: This fails in the pipeline, but works locally. Investigate.
it("Empty 'tags' tab has a link to the page where tags can be created", () => {
cy.cs('student-table-tabs').within(() => {
cy.contains('Tags').click()
cy.contains('No tags defined. You can define them here.').find('a').click()
})
cy.contains('No tags defined. You can define them here.').find('a').click()
cy.url().should('include', '/study-programme/KH50_001?tab=4')
cy.contains('Matemaattisten tieteiden kandiohjelma')
cy.contains('Create new tag')
@@ -383,17 +383,15 @@ describe('Population statistics tests', () => {
cy.contains(nonExisting).should('not.exist')
cy.contains('button', 'Check student numbers').click()
cy.contains('Check for student numbers')
cy.get('textarea').type(existing)
cy.get('textarea').type('{enter}')
cy.get('textarea').type(nonExisting)
cy.cs('check-student-numbers').type(`${existing}{enter}${nonExisting}`)
cy.contains('button', 'Check students').click()
cy.contains('#checkstudentsresults', 'Results').within(() => {
cy.contains('Student numbers in list and in Sisu').click()
cy.contains('#found', existing)
cy.contains('Student numbers in list but not in Sisu').click()
cy.contains('#notfound', nonExisting)
cy.contains('Student numbers in Sisu but not in list').click()
cy.contains('#notsearched', '457144')
cy.get('#checkstudentsresults').within(() => {
cy.cs('found-title').click()
cy.cs('found-data').contains(existing)
cy.cs('not-found-title').click()
cy.cs('not-found-data').contains(nonExisting)
cy.cs('not-searched-title').click()
cy.cs('not-searched-data').contains('457144')
})
cy.contains('button', 'Close').click()
cy.contains('Student numbers in list and in Sisu').should('not.exist')
@@ -403,8 +401,8 @@ describe('Population statistics tests', () => {
describe('When using IAM user', () => {
beforeEach(() => {
cy.init(pathToMathBSc2020, 'onlyiamrights')
cy.contains('Matemaattisten tieteiden kandiohjelma 2020 - 2021')
cy.contains('Class size 30 students')
cy.contains('Matemaattisten tieteiden kandiohjelma')
cy.contains('Class of 2020 - 2021, 30 students')
})
it('Population statistics is visible', () => {
+5 -14
View File
@@ -114,9 +114,9 @@ describe('Students tests', () => {
cy.get('a').click()
})
cy.url().should('include', '/coursestatistics')
cy.contains('MAT12004 • Tilastollinen päättely I')
cy.contains('AYMAT12004 • Avoin yo: Tilastollinen päättely I')
cy.contains('57046 • Johdatus tilastolliseen päättelyyn')
cy.contains('MAT12004') // Tilastollinen päättely I
cy.contains('AYMAT12004') // Avoin yo: Tilastollinen päättely I
cy.contains('57046') // Johdatus tilastolliseen päättelyyn
})
it('Has correct Sisu link', () => {
@@ -162,8 +162,8 @@ describe('Students tests', () => {
.parent()
.within(() => cy.get('a').click())
})
cy.contains('Matemaattisten tieteiden kandiohjelma 2020 - 2021')
cy.contains('Class size 30 students')
cy.contains('Matemaattisten tieteiden kandiohjelma')
cy.contains('Class of 2020 - 2021, 30 students')
})
it('Grade graph works in all three different modes', () => {
@@ -174,18 +174,13 @@ describe('Students tests', () => {
cy.contains('label', 'Group size')
cy.get('input').should('have.value', '5')
})
cy.contains('.highcharts-container text', "Nov '22")
cy.contains('.highcharts-container text', "Jul '22").should('not.exist')
cy.cs('group-size-input').within(() => {
cy.contains('label', 'Group size')
cy.get('input').clear()
cy.get('input').type('10')
cy.get('input').should('have.value', '10')
})
cy.contains('.highcharts-container text', "Jul '22")
cy.contains('.highcharts-container text', "Nov '22").should('not.exist')
cy.contains('button', 'Show semester mean').click()
cy.contains('.highcharts-container text', "Jan '22")
})
describe('Bachelor Honours section', () => {
@@ -277,13 +272,9 @@ describe('Students tests', () => {
it('When a study plan is selected, the time frame of the credit graph is updated', () => {
cy.clock(new Date('2024-08-30').getTime(), ['Date'])
cy.visit('/students/550789')
cy.contains('.highcharts-container text', '1 Aug 2020')
cy.contains('.highcharts-container text', '30 Aug 2024')
cy.contains('table tbody tr', 'Kulttuurien tutkimuksen kandiohjelma (01.08.2020–30.06.2023)').within(() => {
cy.get('td').eq(0).click()
})
cy.contains('.highcharts-container text', '1 Aug 2020')
cy.contains('.highcharts-container text', '19 Aug 2023')
})
it("If there's a study plan corresponding to the degree programme, completed credits are displayed", () => {
+14 -14
View File
@@ -31,7 +31,7 @@ describe('Study guidance group tests', () => {
const studyGuidanceGroupsTableContent = [
['MAT kandit 2020', 3, 'Matemaattisten tieteiden kandiohjelma', '2020 - 2021'],
['Oma ohjausryhmä', 2, 'Add study programme', 'Add year'],
['Oma ohjausryhmä', 2, 'Add degree programme', 'Add starting year'],
]
cy.checkTableStats(studyGuidanceGroupsTableContent, 'study-guidance-group-overview')
@@ -52,11 +52,11 @@ describe('Study guidance group tests', () => {
it('has a working back button', () => {
cy.init('/studyguidancegroups/sgg-cypress-1', 'onlystudyguidancegroups')
cy.get('button.ui.button').contains('Back').click()
cy.get('button').contains('Back to groups').click()
cy.url().should('eq', `${Cypress.config().baseUrl}/studyguidancegroups`)
})
describe('without associated study programme and year', () => {
describe('without associated degree programme and year', () => {
beforeEach(() => {
cy.init('/studyguidancegroups/sgg-cypress-2', 'onlystudyguidancegroups')
})
@@ -84,7 +84,7 @@ describe('Study guidance group tests', () => {
'All credits',
'Credits since 1.1.1970',
'Start year at uni',
'Study programmes',
'Degree programmes',
'Tags',
]
cy.contains('Students (2)').click()
@@ -94,15 +94,15 @@ describe('Study guidance group tests', () => {
})
})
describe('with associated study programme and year', () => {
describe('with associated degree programme and year', () => {
beforeEach(() => {
cy.init('/studyguidancegroups/sgg-cypress-1', 'onlystudyguidancegroups')
})
it('shows the correct labels', () => {
cy.get('.ui.medium.header').contains('MAT kandit 2020')
cy.get('.ui.blue.tag.label:first').contains('Matemaattisten tieteiden kandiohjelma')
cy.get('.ui.blue.tag.label:last').contains('2020 - 2021')
cy.get('h4').contains('MAT kandit 2020')
cy.get('.MuiChip-root:first').contains('Matemaattisten tieteiden kandiohjelma')
cy.get('.MuiChip-root:last').contains('2020 - 2021')
})
it('shows the correct panes', () => {
@@ -113,17 +113,17 @@ describe('Study guidance group tests', () => {
cy.contains('Students (3)')
})
it("clicking the 'Show starting from associated year' button activates the 'Date of Course Credits' filter", () => {
cy.get('[data-cy="CreditDate-filter-card"][data-active="false"]')
it("clicking the 'Show credits starting from the associated academic year' filter", () => {
cy.get('[data-cy="ParticipationDate-filter-card"][data-active="false"]')
cy.cs('Credit accumulation (for 3 students)').click()
cy.get('.ui.primary.button').contains('Show starting from associated year').click()
cy.get('[data-cy="CreditDate-filter-card"][data-active="true"]')
cy.get('label').contains('Show credits starting from the associated academic year').click()
cy.get('[data-cy="ParticipationDate-filter-card"][data-active="true"]')
cy.get('.date-picker')
.eq(0)
.within(() => cy.get('input').should('have.value', '01.08.2020'))
cy.contains('Reset All Filters')
cy.get('.ui.primary.button').contains('Show all credits').click()
cy.get('[data-cy="CreditDate-filter-card"][data-active="false"]')
cy.get('label').contains('Show credits starting from the associated academic year').click()
cy.get('[data-cy="ParticipationDate-filter-card"][data-active="false"]')
})
it.skip('students table has the correct tabs', () => {
+19 -19
View File
@@ -10,17 +10,19 @@ describe('Teachers page tests', () => {
const teacher1 = 'Luokkanen Liisa Viljami'
const teacher2 = 'Perälä Juhani Susanna'
const statisticsHeaders = ['Name', 'Credits', 'Credits transferred', 'Passed']
it('Check Statistics', () => {
cy.get(':nth-child(1) > .ui > .search').click()
cy.cs('semester-start').click()
cy.contains('Syksy 2020').click()
cy.cs('course-providers').click()
cy.contains('Matemaattisten tieteiden kandiohjelma').click()
cy.cs('course-providers').children('.icon').click()
cy.contains('button', 'Search').click()
cy.get('body').click(0, 0) // Click outside of the select
cy.cs('search-statistics').click()
cy.contains('Teacher')
const headers = ['Name', 'Credits', 'Credits transferred', 'Passed']
cy.get('table thead tr th').should('have.length', 4)
headers.forEach((header, index) => {
statisticsHeaders.forEach((header, index) => {
cy.get('table thead tr th').eq(index).should('contain', header)
})
cy.contains('td', teacher1).siblings().eq(0).contains('235')
@@ -30,15 +32,15 @@ describe('Teachers page tests', () => {
})
it('Teacher search works', () => {
cy.get('.borderless > :nth-child(3)').click()
cy.get('.prompt').type(teacher1.split(' ')[0])
cy.cs('Search').click()
cy.cs('teacher-search').type(teacher1.split(' ')[0])
cy.get('table tbody tr').should('have.length', 4)
cy.get('table tbody tr').eq(2).contains('td', teacher1)
cy.get('table tbody tr').eq(1).contains('td', teacher1)
})
it('Can check teacher page', () => {
cy.get('.borderless > :nth-child(3)').click()
cy.get('.prompt').type(teacher2)
cy.cs('Search').click()
cy.cs('teacher-search').type(teacher2)
// Prevent opening in new tab
cy.contains('a', teacher2).invoke('removeAttr', 'target').click()
@@ -46,19 +48,17 @@ describe('Teachers page tests', () => {
cy.contains(teacher2)
cy.contains('Syksy 2023').click()
cy.contains('Kevät 2019').click()
cy.get('.ok-sortable-table tbody tr')
.eq(0)
.within(() => {
const rowContent = ['MAT12004', 'Tilastollinen päättely I', '120', '0', '92.31%']
rowContent.forEach((content, index) => {
cy.get('td').eq(index).contains(content)
})
cy.contains('tr', 'MAT12004').within(() => {
const rowContent = ['MAT12004', 'Tilastollinen päättely I', '120', '0', '92.31%']
rowContent.forEach((content, index) => {
cy.get('td').eq(index).contains(content)
})
})
})
it('Check leaderboad works', () => {
cy.get('.borderless > :nth-child(2)').click()
cy.get(':nth-child(1) > .ui > .search').click()
cy.cs('Leaderboard').click()
cy.cs('academic-year').click()
cy.contains('2020-2021').click()
})
})
+1 -7
View File
@@ -39,12 +39,6 @@ describe('University view', () => {
checkProgressTables()
})
it("'Graduated included / Graduated excluded' toggle works", () => {
cy.cs('graduated-toggle').click()
checkProgressBarCharts()
checkProgressTables()
})
it("'All study rights / Special study rights excluded' toggle works", () => {
cy.cs('study-right-toggle').click()
checkProgressBarCharts()
@@ -55,7 +49,7 @@ describe('University view', () => {
it.skip('years in the tables can be clicked to show faculty level breakdown', () => {
cy.cs('study-right-toggle').click()
cy.cs('bachelors-faculty-progress-table-show-button3').click()
cy.contains('29.5%').trigger('mouseover', { force: true })
cy.contains('29.5%').hover()
cy.contains('Matemaattis-luonnontieteellinen tiedekunta')
cy.contains('H50')
cy.contains('0 Credits: 0')
+1 -1
View File
@@ -72,7 +72,7 @@ describe('Users tests', () => {
})
it("only the mocked user's programmes are visible", () => {
cy.intercept('/api/v3/populationstatistics/studyprogrammes').as('studyprogrammes')
cy.intercept('/api/populationstatistics/studyprogrammes').as('studyprogrammes')
cy.visit('/populations')
cy.wait('@studyprogrammes').its('response.statusCode').should('be.oneOf', [200, 304])
+30 -11
View File
@@ -58,17 +58,6 @@ const onlyStudyGuidanceGroupsUser = {
hypersonsisuid: 'hy-hlo-2222222',
}
// TODO add user into anon-DB (check the need for changing hypersonid)
// REMEMBER TO ADD ROLE openUniSearch
// const openUniUser = {
// uid: 'openuniuser',
// displayname: 'Open Uni User',
// 'shib-session-id': 'mock-cypress-session',
// hygroupcn: 'grp-oodikone-basics-users;hy-ypa-opa-dojo',
// mail: 'grp-toska+mockopenuniuser@helsinki.fi',
// hypersonsisuid: 'hy-hlo-9999999',
// }
const userHeaders = [
adminUserHeaders,
basicUserHeaders,
@@ -122,6 +111,19 @@ Cypress.Commands.add('selectFromDropdown', { prevSubject: false }, (label, index
if (isMultiSelect) cy.get('#menu- > .MuiModal-backdrop').click()
})
/**
* Select item from FilterSearchableSelect dropdown.
* If the index is a number, select index-th item.
* Otherwise select the item containing index text.
*/
Cypress.Commands.add('selectFromAutocomplete', { prevSubject: false }, (label, index) => {
cy.cs(`${label}-selector`).click()
cy.get(`#${label}-popper.MuiAutocomplete-popper [role="listbox"]`).within(() => {
if (typeof index === 'number') cy.get('li').eq(index).click()
else cy.get('li').contains(index).click()
})
})
/**
* Set range selector values FilterRange.
* If the reset flag is set, the function tries to reset the original values.
@@ -177,3 +179,20 @@ Cypress.Commands.add('checkTableStats', (correctStats, tableName) => {
cy.get('tr').filter(':visible').should('have.length', correctStats.length)
})
})
/**
* Hover on the selected element.
*/
Cypress.Commands.add('hover', { prevSubject: 'element' }, (subject, options = {}) => {
cy.wrap(subject).trigger('mouseover', {
bubbles: true,
cancelable: true,
...options,
})
cy.wrap(subject).trigger('mousemove', {
bubbles: true,
cancelable: true,
...options,
})
})
-1
View File
@@ -13,7 +13,6 @@
// https://on.cypress.io/configuration
// ***********************************************************
require('@cypress/code-coverage/support')
require('./commands')
Cypress.on('uncaught:exception', error => {
+10
View File
@@ -25,6 +25,9 @@ services:
build:
context: services
dockerfile: backend/Dockerfile
image: oodikone-backend:staging
profiles:
- oodikone
frontend:
<<: *defaults
@@ -32,6 +35,9 @@ services:
build:
context: services
dockerfile: frontend/Dockerfile
image: oodikone-frontend:staging
profiles:
- oodikone
kone-db:
<<: *defaults
@@ -47,6 +53,7 @@ services:
command: >
sh -c 'redis-server --appendonly yes && redis-cli flushall'
image: redis:7.2.6
container_name: redis
ports:
- 6379:6379
@@ -101,6 +108,8 @@ services:
- 1337:80
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
profiles:
- oodikone
# Following services are used to run sis-updater-worker when anon sis-db needs to be updated. Could probably also use production version, but this certainly works.
sis-importer-db:
@@ -114,6 +123,7 @@ services:
<<: *defaults
build:
context: updater/sis-updater-worker
container_name: sis-updater-worker
depends_on:
- redis
- sis-db
-47
View File
@@ -1,47 +0,0 @@
services:
oodikone-backend:
build:
context: ${OODIKONE_REPO:-.}/services
dockerfile: backend/Dockerfile
args:
SENTRY_ENVIRONMENT: ''
SENTRY_RELEASE: ''
SENTRY_DSN: ''
SERVICE_PROVIDER: fd
NODE_ENV: production
image: ${DOCKER_OODIKONE_NAMESPACE:-637423527834.dkr.ecr.eu-north-1.amazonaws.com/oodikone}/oodikone-backend:${DOCKER_IMAGE_TAG:-dev}
oodikone-frontend:
build:
context: ${OODIKONE_REPO:-.}/services
dockerfile: frontend/Dockerfile
args:
SENTRY_ENVIRONMENT: ''
SENTRY_RELEASE: ''
SENTRY_DSN: ''
SERVICE_PROVIDER: fd
LANGUAGE_CENTER_VIEW_ENABLED: 'false' # currently no intention to be enabled
SIS_URL: 'https://sis-qa.funidata.fi'
NODE_ENV: production
image: ${DOCKER_OODIKONE_NAMESPACE:-637423527834.dkr.ecr.eu-north-1.amazonaws.com/oodikone}/oodikone-frontend:${DOCKER_IMAGE_TAG:-dev}
updater-scheduler:
build:
context: ${OODIKONE_REPO:-.}/updater/sis-updater-scheduler
args:
SENTRY_ENVIRONMENT: ''
SENTRY_RELEASE: ''
SENTRY_DSN: ''
NODE_ENV: production
image: ${DOCKER_OODIKONE_NAMESPACE:-637423527834.dkr.ecr.eu-north-1.amazonaws.com/oodikone}/updater-scheduler:${DOCKER_IMAGE_TAG:-dev}
updater-worker:
build:
context: ${OODIKONE_REPO:-.}/updater/sis-updater-worker
args:
SENTRY_ENVIRONMENT: ''
SENTRY_RELEASE: ''
SENTRY_DSN: ''
SERVICE_PROVIDER: fd
NODE_ENV: production
image: ${DOCKER_OODIKONE_NAMESPACE:-637423527834.dkr.ecr.eu-north-1.amazonaws.com/oodikone}/updater-worker:${DOCKER_IMAGE_TAG:-dev}
-190
View File
@@ -1,190 +0,0 @@
x-defaults: &defaults
restart: unless-stopped
x-build_defaults: &build_defaults
dockerfile: dev.Dockerfile
services:
adminer:
<<: *defaults
container_name: adminer
environment:
ADMINER_DESIGN: pepa-linha
image: adminer:4.8.1
ports:
- 5050:8080
volumes:
- ./adminer-plugins:/var/www/html/plugins-enabled
backend:
<<: *defaults
build:
<<: *build_defaults
context: services/backend
depends_on:
- kone-db
- redis
- sis-db
- user-db
env_file:
- .env
environment:
DB_URL_KONE: postgres://postgres@kone-db:5432/kone-db
DB_URL_USER: postgres://postgres@user-db:5432/user-db
FRONT_URL: http://localhost:3000
REDIS: redis
SECRET_TOKEN: dev
SIS_DB_URL: postgres://postgres@sis-db:5432/sis-db
SIS_UPDATER_URL: http://sis-updater-scheduler:8082
IMPORTER_DB_API_URL: 'https://localhost:3005'
JAMI_URL: 'http://jami:3003/'
SERVICE_PROVIDER: fd
SIS_HOST: ${SIS_HOST}
SIS_GRAPHQL_APPLICATION_ACCOUNT: ${SIS_GRAPHQL_APPLICATION_ACCOUNT}
SIS_GRAPHQL_APPLICATION_KEY: ${SIS_GRAPHQL_APPLICATION_KEY}
IMPORTER_DB_API_USER: dev_user
IMPORTER_DB_API_PASSWORD: dev_password
ROOT_ORG_ID: ${ROOT_ORG_ID}
LANGUAGE_CENTER_VIEW_ENABLED: 'false'
ports:
- 8080:8080
volumes:
- backend-nodemod:/opt/app-root/backend/node_modules
- ./environments/testu:/opt/app-root/backend/src/environment
- ./services/backend/:/opt/app-root/backend
- ./services/shared/:/opt/app-root/shared
frontend:
<<: *defaults
build:
<<: *build_defaults
context: services/frontend
container_name: frontend
ports:
- 3000:3000
environment:
REACT_APP_SERVICE_PROVIDER: fd
REACT_APP_LANGUAGE_CENTER_VIEW_ENABLED: 'false'
REACT_APP_SIS_URL: 'https://sis-qa.funidata.fi'
volumes:
- frontend-nodemod:/opt/app-root/frontend/node_modules
- ./services/frontend/:/opt/app-root/frontend
- ./services/shared/:/opt/app-root/shared
kone-db:
<<: *defaults
container_name: kone-db
environment:
PGDATA: /data
POSTGRES_DB: kone-db
POSTGRES_HOST_AUTH_METHOD: trust
image: postgres:15.10
ports:
- 12349:5432
volumes:
- kone-db-data:/data
redis:
<<: *defaults
command: 'redis-server --appendonly yes'
container_name: redis
image: redis:6.2.16
ports:
- '6379:6379'
volumes:
- redis-data:/data
sis-db:
<<: *defaults
container_name: sis-db
environment:
PGDATA: /data
POSTGRES_DB: sis-db
POSTGRES_HOST_AUTH_METHOD: trust
image: postgres:15.10
volumes:
- sis-db-data:/data
shm_size: 2gb
ports:
- 12345:5432
sis-updater-scheduler:
<<: *defaults
build:
<<: *build_defaults
context: updater/sis-updater-scheduler
container_name: sis-updater-scheduler
extra_hosts:
- 'host.docker.internal:host-gateway'
depends_on:
- redis
environment:
REDIS_HOST: redis
REDIS_PORT: 6379
SECRET_TOKEN: dev
SIS_IMPORTER_HOST: ${SIS_IMPORTER_DB_HOST:-host.docker.internal}
SIS_IMPORTER_PORT: ${SIS_IMPORTER_DB_PORT:-15234}
SIS_IMPORTER_DATABASE: importer-db
SIS_IMPORTER_PASSWORD: dev
SIS_IMPORTER_USER: dev
SERVICE_PROVIDER: fd
ports:
- 8082:8082
volumes:
- ./updater/sis-updater-scheduler/:/opt/app-root/src
sis-updater-worker:
<<: *defaults
deploy:
replicas: 2
mode: replicated
build:
<<: *build_defaults
context: updater/sis-updater-worker
depends_on:
- redis
- sis-db
extra_hosts:
- 'host.docker.internal:host-gateway'
environment:
DB_URL: postgres://postgres@sis-db:5432/sis-db
REDIS_HOST: redis
REDIS_PORT: 6379
SIS_IMPORTER_HOST: ${SIS_IMPORTER_DB_HOST:-host.docker.internal}
SIS_IMPORTER_PORT: ${SIS_IMPORTER_DB_PORT:-15234}
SIS_IMPORTER_DATABASE: importer-db
SIS_IMPORTER_PASSWORD: dev
SIS_IMPORTER_USER: dev
SIS_PASSWORD: postgres
ROOT_ORG_ID: ${ROOT_ORG_ID}
SERVICE_PROVIDER: fd
volumes:
- ./updater/sis-updater-worker/:/opt/app-root/src
user-db:
<<: *defaults
container_name: user-db
environment:
PGDATA: /data
POSTGRES_DB: user-db
POSTGRES_HOST_AUTH_METHOD: trust
image: postgres:15.10
volumes:
- user-db-data:/data
ports:
- 12347:5432
volumes:
backend-nodemod:
frontend-nodemod:
kone-db-data:
redis-data:
sis-db-data:
user-db-data:
networks:
default:
name: oodikone
importer:
name: importer_network
external: true
+24 -3
View File
@@ -1,5 +1,26 @@
services:
frontend:
api-tests:
volumes:
- frontend-nodemod:/opt/app-root/frontend/node_modules
- ./services/frontend/instrumented:/opt/app-root/frontend/src
- backend-tests-nodemod:/opt/app-root/backend/node_modules
- ./services/backend/:/opt/app-root/backend
- ./services/shared/:/opt/app-root/shared
environment:
CI: 'true'
NODE_ENV: 'test'
DB_URL_KONE: postgres://postgres@kone-db:5432/kone-db
DB_URL_USER: postgres://postgres@user-db:5432/user-db
FRONT_URL: http://localhost:3000
REDIS: redis
SECRET_TOKEN: test
SIS_DB_URL: postgres://postgres@sis-db:5432/sis-db
SIS_UPDATER_URL: http://sis-updater-scheduler:8082
JAMI_URL: 'http://jami:3003/'
IMPORTER_DB_API_URL: 'https://api-toska.apps.ocp-prod-0.k8s.it.helsinki.fi/importer'
CRYPT_KEY: 'e16730fce263d5270ba8dcf84d068a32556542455e71331880d6c1c5e4db9b4e'
build:
context: services/backend
dockerfile: dev.Dockerfile
command: npm run test
volumes:
backend-tests-nodemod:
+5 -3
View File
@@ -21,6 +21,7 @@ services:
build:
<<: *build_defaults
context: services/backend
container_name: backend
depends_on:
- kone-db
- redis
@@ -44,9 +45,9 @@ services:
profiles:
- oodikone
volumes:
- backend-nodemod:/opt/app-root/backend/node_modules
- ./services/backend/:/opt/app-root/backend
- ./services/shared/:/opt/app-root/shared
- backend-nodemod:/opt/app-root/backend/node_modules
frontend:
<<: *defaults
@@ -59,9 +60,9 @@ services:
profiles:
- oodikone
volumes:
- frontend-nodemod:/opt/app-root/frontend/node_modules
- ./services/frontend/:/opt/app-root/frontend
- ./services/shared/:/opt/app-root/shared
- frontend-nodemod:/opt/app-root/frontend/node_modules
kone-db:
<<: *defaults
@@ -132,6 +133,7 @@ services:
build:
<<: *build_defaults
context: updater/sis-updater-worker
container_name: sis-updater-worker
depends_on:
- redis
- sis-db
@@ -168,7 +170,7 @@ services:
- npm_config_cache=/tmp
- PORT=3003
- NODE_ENV=dev
- DATABASE_URL=postgres://postgres:postgres@jami-db:5432/postgres
- DATABASE_URL=postgres://postgres:postgres@jami-db:5432/jami-db-real
ports:
- 3003:3003
container_name: jami
+2 -5
View File
@@ -8,11 +8,10 @@ Roles are defined [here](../services/backend/src/config/roles.ts). Criteria for
- courseStatistics
- facultyStatistics
- fullSisuAccess
- openUniSearch
- studyGuidanceGroups
- teachers
The roles `facultyStatistics`, `openUniSearch`, and `studyGuidanceGroups` simply enable certain views (_Faculties_, _Open uni student population_, and _Guidance groups_ respectively).
The roles `facultyStatistics` and `studyGuidanceGroups` simply enable certain views (_Faculties_ and _Guidance groups_ respectively).
### courseStatistics
@@ -21,7 +20,7 @@ The roles `facultyStatistics`, `openUniSearch`, and `studyGuidanceGroups` simply
### teachers
- By default, the role only allows seeing the statistics of course providers (study programmes) they have full rights to (see [Programmes](#programmes))
- By default, the role only allows seeing the statistics of course providers (degree programmes) they have full rights to (see [Programmes](#programmes))
- This role always needs to be given and removed manually
- If the user is a member of the `hy-dekaanit` or `hy-varadekaanit-opetus` IAM group (or is an admin), they can see leaderboards, information about individual teachers, and statistics of all course providers. They don't need to be given the `teachers` role manually.
@@ -43,7 +42,6 @@ The roles `facultyStatistics`, `openUniSearch`, and `studyGuidanceGroups` simply
| Custom population | | ✅ | ✅ |
| Completed courses | ✅ | ✅ | ✅ |
| Close to graduation | | ✅ | ✅ |
| Open uni population | | ✅ | |
| Courses | | ✅ | ✅ |
\* = anyone who can log in to Oodikone, check the [IAM groups](#iam-groups) section for more information
@@ -63,7 +61,6 @@ A user must be a member of either the `grp-oodikone-basic-users` or `grp-oodikon
| grp-oodikone-basic-users | courseStatistics |
| grp-oodikone-users | facultyStatistics |
| grp-toska | admin |
| hy-ypa-opa-dojo | openUniSearch |
`grp-oodikone-basic-users`: basically the whole staff (hy-\*-allstaff)
+5 -5
View File
@@ -6,7 +6,7 @@ The made changes do not have any effects on faculty or trends views. **Needs man
Basic rule of thumb is that for the master/licentiate programme I used names `combinedProgramme` and in some rare cases `secondProgramme` as whole or part of the variable names.
## Study programme
## Degree programme
**Needs manual work** in the function `getCombinedProgrammeId` in `services/frontend/src/common/index.js` if new programmes is added. For showing the both programme data correctly, the filtering mechanism is changed to add both programmes and it is not caring for which programme user has acual rights or IAMrights. However, the links for the class stats level are hidden in studytrack tables, if the user has no rights for the programme.
@@ -15,7 +15,7 @@ Basic rule of thumb is that for the master/licentiate programme I used names `co
- Overview
- **Needs manual work**
**Redis nightly study programme update**
**Redis nightly degree programme update**
- Updates automatically combined programme stats. **Needs manual work** here `services/backend/src/services/studyprogrammeHelpers.js` in the constant `combinedStudyprogrammes`. This might needs refactoring if bachelor programme can have many separate master programmes. Then remember to change function `refreshNewOverview` in `services/backend/src/events.js` accordingly to handle lists.
@@ -36,7 +36,7 @@ All things listed below should work automatically, if new combinations are done.
- _Students of the studyprogramme by starting year_
- Graduated field separated for both programmes
- All = currently enrolled + absent + inactive + graduated licentiate
- All = currently enrolled + absent + passive + graduated licentiate
- _Progress of students of the studyprogramme by starting year_
- computed for whole bachelor + master/licentiate studyright period
- _Average graduation times_
@@ -85,12 +85,12 @@ All things listed below should work automatically, if new combinations are done.
- **Graduated from programme** - possibility to filter out students based on graduation either in bachelor's or master's.
- **Studyright status** - active or inactive based on the chosen degree
- **Studyright status** - active or passive based on the chosen degree
- **Studytrack filter** (needs programme code) is not touched at the moment, thus **may need future manual work**.
## Study guidance groups
- Backend: student data is fetched by bachelor study programme code in `/v3/populationstatisticsbystudentnumbers`. Separation from code 'KHxx_xxx+MHxx_xxx' is done in backend.
- Backend: student data is fetched by bachelor degree programme code in `/populationstatisticsbystudentnumbers`. Separation from code 'KHxx_xxx+MHxx_xxx' is done in backend.
- Frontend: things are done in the same way as with class stats.
- **Need manual work**: `./oodikone/services/frontend/src/redux/elementdetails.js` add combined programme codes (both bachelor and master) to `combinedProgrammeCodes` and modify `combinedOptions` list accordingly in `useFilterAndFormattedElementDetails()` function.
+1 -1
View File
@@ -53,7 +53,7 @@ erDiagram
open_uni_population_searches
progress_criteria
study_guidance_group_tags
study_programme_pins
degree_programme_pins
tag ||--o{ tag_student : "belongs to"
tag
tag_student
+5 -5
View File
@@ -55,11 +55,11 @@ The `semester_enrollments` column is an array of JSON objects. Each object conta
The `type` field represents one of the following enrollment types. The `semester` field refers to a semester code, defined as `semestercode` in the model Semester.
| value | meaning | notes |
| ----: | :------- | :------------------------------- |
| 1 | Present | |
| 2 | Absent | |
| 3 | Inactive | neither 1 or 2 has been declared |
| value | meaning | notes |
| ----: | :------ | :------------------------------- |
| 1 | Present | |
| 2 | Absent | |
| 3 | Passive | neither 1 or 2 has been declared |
## Credit type code
+4 -14
View File
@@ -2,26 +2,19 @@
A collection of useful tools, documentation and other resources.
## AI tools
- [ChatGPT](https://chatgpt.com)
- [CurreChat](https://curre.helsinki.fi/chat)
- [GitHub Copilot](https://github.com/features/copilot)
- Free for students!
- [Microsoft Copilot](https://copilot.microsoft.com)
## Code style and maintainability
- [CodeClimate documentation](https://docs.codeclimate.com/docs)
- [Advanced configuration](https://docs.codeclimate.com/docs/advanced-configuration)
- [ESLint documentation](https://eslint.org/docs/v8.x)
- [Prettier documentation](https://prettier.io/docs/en)
- [qlty.sh](https://qlty.sh/gh/UniversityOfHelsinkiCS/projects/oodikone)
- Qlty.sh replaced Code climate which was introduced by Riku
- The usefulness of qlty.sh is sometimes questionable
## Database
- [pgcli](https://www.pgcli.com)
- A useful commandline tool to interact with the database
- Install: `$ pip install pgcli`
- Can be installed from some package managers or optionally via `$ pip install pgcli`
- [Postgres documentation](https://www.postgresql.org/docs/15/index.html)
- [Sequelize documentation](https://sequelize.org/docs/v6/)
@@ -29,8 +22,6 @@ A collection of useful tools, documentation and other resources.
- [GitHub documentation](https://docs.github.com/en)
- [GitHub Actions](https://docs.github.com/en/actions)
- [GitHub Education](https://github.com/education/students)
- Free benefits for students!
- [Mermaid.js documentation](https://mermaid.js.org/intro/syntax-reference.html)
## TypeScript
@@ -46,7 +37,6 @@ A collection of useful tools, documentation and other resources.
- [Material Icons](https://mui.com/material-ui/material-icons/)
- [MUI X Date pickers](https://mui.com/x/react-date-pickers/)
- [Material React Table documentation](https://www.material-react-table.com/docs)
- [Semantic UI React documentation](https://react.semantic-ui.com)
<!--
+64 -53
View File
@@ -25,7 +25,7 @@ export default tseslint.config(
'nyc-config.cjs',
'.lintstagedrc.mjs',
'cypress.config.js',
]
],
},
// Global options (excl. e2e)
@@ -44,8 +44,8 @@ export default tseslint.config(
parser: tseslint.parser,
parserOptions: {
projectService: {
allowDefaultProject: ['*.{js,cjs}']
}
allowDefaultProject: ['*.{js,cjs}'],
},
},
globals: {
...globals.browser,
@@ -53,16 +53,16 @@ export default tseslint.config(
},
},
plugins: {
'import-x': eslintPluginImportX
'import-x': eslintPluginImportX,
},
linterOptions: {
reportUnusedDisableDirectives: 'error',
},
rules: {
'camelcase': 'off', // TODO: Enable. old db/backend logic is still using snake_case every now and then and needs a big refactor
camelcase: 'off', // TODO: Enable. old db/backend logic is still using snake_case every now and then and needs a big refactor
'class-methods-use-this': 'off', // Disabled in favor of @typescript-eslint
'consistent-return': 'off', // Disabled in favor of @typescript-eslint
'id-denylist': ['error', 'c', 'd', 'e', 'err', 't'],
'id-denylist': ['error', 'err'],
'no-alert': 'error',
'no-await-in-loop': 'error',
'no-console': 'error',
@@ -75,24 +75,42 @@ export default tseslint.config(
'prefer-const': 'error',
'prefer-destructuring': 'off', // Disabled in favor of @typescript-eslint
'dot-notation': 'off', // Disabled in favor of @typescript-eslint
'no-restricted-imports': ['error', { 'patterns': [{ 'regex': '^@mui/[^/]+$' }] }],
'quotes': ['error', 'single',
{ avoidEscape: true, allowTemplateLiterals: false }
'no-restricted-imports': [
'error',
{
patterns: [
{
regex: '^@mui/[^/]+$',
message:
'Import only the needed module. This has no effect in prod due to tree-shake, but speeds up development builds.',
},
{
regex: '^src/.*',
message: 'Root imports will not resolve in production build (instead silently crash).',
},
],
},
],
quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: false }],
'import-x/no-commonjs': 'error',
'import-x/no-default-export': 'error',
'import-x/no-extraneous-dependencies': ['error', { devDependencies: false }],
'import-x/no-unused-modules': ['error',
'import-x/no-unused-modules': [
'error',
{
missingExports: true,
unusedExports: true,
src: ['{services,updater}/**/*.{js,jsx,ts,tsx}']
}],
'import-x/order': ['error', {
alphabetize: { order: 'asc', caseInsensitive: true },
groups: [['builtin', 'external'], ['internal'], ['parent'], ['sibling', 'index']],
}],
src: ['{services,updater}/**/*.{js,jsx,ts,tsx}'],
},
],
'import-x/order': [
'error',
{
alphabetize: { order: 'asc', caseInsensitive: true },
groups: [['builtin', 'external'], ['internal'], ['parent'], ['sibling', 'index']],
},
],
// TypeScript
'@typescript-eslint/no-unused-vars': [
@@ -105,9 +123,7 @@ export default tseslint.config(
'@typescript-eslint/consistent-return': 'error', // NOTE: This is weaker than tsconfig.json noImplicitReturns, and should be replaced once ts migration complete
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/dot-notation': 'error',
'@typescript-eslint/prefer-destructuring': ['error',
{ VariableDeclarator: { object: true } }
],
'@typescript-eslint/prefer-destructuring': ['error', { VariableDeclarator: { object: true } }],
'@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'with-single-extends' }],
'@typescript-eslint/no-explicit-any': 'off', // TODO: enable when feeling brave
'@typescript-eslint/no-implied-eval': 'error',
@@ -127,12 +143,12 @@ export default tseslint.config(
'services/frontend/tsconfig.json',
'services/frontend/tsconfig.node.json',
'services/backend/tsconfig.json',
'services/shared/tsconfig.json'
'services/shared/tsconfig.json',
],
}),
createNodeResolver({
extensions: ['.js', '.jsx', 'cjs', 'mjs', '.ts', '.tsx'],
})
}),
],
},
},
@@ -140,22 +156,22 @@ export default tseslint.config(
{
files: ['services/backend/src/routes/*.{ts,js}'],
rules: {
'@typescript-eslint/no-misused-promises': 'off', // Errors come mainly from Express route handlers that are handled correctly by express-async-errors
'@typescript-eslint/no-misused-promises': 'off', // Errors come mainly from Express route handlers that are handled correctly by express-async-errors
'@typescript-eslint/consistent-return': 'off',
}
},
},
// Disable type-aware linting for javascript files
{
files: ['{updater,services}/**/*.{js,cjs}'],
extends: [tseslint.configs.disableTypeChecked]
extends: [tseslint.configs.disableTypeChecked],
},
// Frontend camelCase and react
{
files: ['services/frontend/**/*.{js,jsx,mjs,cjs,ts,tsx}'],
plugins: {
'react': eslintPluginReact,
react: eslintPluginReact,
},
extends: [
eslintPluginReact.configs.flat.recommended,
@@ -166,14 +182,15 @@ export default tseslint.config(
parserOptions: {
ecmaFeatures: {
jsx: true,
}
}
},
},
},
rules: {
'camelcase': 'warn',
camelcase: 'warn',
'react/prop-types': 'off', // Typescript but worse
'react/no-unescaped-entities': 'off',
'react/function-component-definition': ['error',
'react/function-component-definition': [
'error',
{ namedComponents: 'arrow-function', unnamedComponents: 'arrow-function' },
],
'react/no-array-index-key': 'error',
@@ -181,9 +198,7 @@ export default tseslint.config(
'react/no-this-in-sfc': 'error',
'react/prefer-stateless-function': 'error',
'react/jsx-boolean-value': ['error', 'never'],
'react/jsx-filename-extension': ['error',
{ allow: 'as-needed', extensions: ['.jsx', '.tsx'] }
],
'react/jsx-filename-extension': ['error', { allow: 'as-needed', extensions: ['.jsx', '.tsx'] }],
'react/jsx-no-leaked-render': 'warn', // TODO: enable, tho not that useful after full ts migration
'react/jsx-no-useless-fragment': 'error',
'react/jsx-props-no-spreading': 'warn', // TODO: enable, improves readability
@@ -191,18 +206,25 @@ export default tseslint.config(
'react/jsx-tag-spacing': 'warn',
},
settings: {
'react': {
'version': 'detect'
}
}
react: {
version: 'detect',
},
},
},
// Cypress should only use rules provided by the plugin below
{
files: ['cypress/**/*.js'],
extends: [
eslintPluginCypress.configs.recommended
]
extends: [eslintPluginCypress.configs.recommended],
},
// Override for backend api tests
{
files: ['services/backend/tests/**/*.ts'],
rules: {
'import-x/no-extraneous-dependencies': 'off',
'no-console': 'off',
},
},
// Overrides for CommonJS
@@ -210,7 +232,7 @@ export default tseslint.config(
files: [
'services/backend/**/*.js',
'updater/sis-updater-worker/src/db/migrations/*.cjs',
'updater/sis-updater-scheduler/**/*.js'
'updater/sis-updater-scheduler/**/*.js',
],
languageOptions: {
sourceType: 'commonjs',
@@ -221,20 +243,9 @@ export default tseslint.config(
},
},
// Rapodiff
{
files: ['services/backend/src/rapodiff/**/*'],
rules: {
'no-console': 'off',
},
},
// Migration files
{
files: [
'updater/sis-updater-worker/src/db/migrations/*.cjs',
'services/backend/src/database/**/*.js'
],
files: ['updater/sis-updater-worker/src/db/migrations/*.cjs', 'services/backend/src/database/**/*.js'],
rules: {
'import-x/no-unused-modules': 'off',
'@typescript-eslint/no-empty-function': 'off',
@@ -252,5 +263,5 @@ export default tseslint.config(
'no-promise-executor-return': 'off',
},
},
eslintPluginPrettierRecommended, // This must be the last plugin so it can override other configs
eslintPluginPrettierRecommended // This must be the last plugin so it can override other configs
)
-6
View File
@@ -1,6 +0,0 @@
const { parserPlugins } = require('@istanbuljs/schema').defaults.nyc
module.exports = {
cache: false,
parserPlugins: parserPlugins.concat('typescript', 'jsx'),
}
+696 -4531
View File
File diff suppressed because it is too large Load Diff
+12 -22
View File
@@ -2,22 +2,20 @@
"name": "oodikone",
"description": "An application for analyzing university data.",
"engines": {
"node": ">=22.0.0"
"node": ">=24.0.0"
},
"scripts": {
"cypress": "cypress",
"cypress:open": "cypress open",
"cypress:run": "cypress run --browser chrome",
"instrument": "./scripts/instrument.sh",
"cypress:run": "cypress run --browser firefox",
"eslint": "eslint",
"lint:all": "eslint services/ updater/",
"stylelint": "stylelint",
"actionlint": "node-actionlint",
"prettier": "prettier",
"cli": "./cli.sh",
"oodikone": "./run.sh oodikone anon up --detach",
"oodikone:real": "./run.sh oodikone real up --detach",
"oodikone:test": "./run.sh oodikone test up --detach",
"oodikone:ci:setup": "docker compose --file docker-compose.ci.yml --profile tests up --detach",
"oodikone:ci:api": "docker compose --file docker-compose.test.yml up --exit-code-from api-tests",
"updater": "./run.sh updater anon up --detach",
"updater:real": "./run.sh updater real up --detach",
"both": "./run.sh both anon up --detach",
@@ -26,12 +24,11 @@
"testupdater": "docker-compose --file docker-compose.ci.yml up --detach --no-recreate sis-updater-worker && SCHEDULE_IMMEDIATE=weekly EXIT_AFTER_IMMEDIATES=yes docker-compose --file docker-compose.ci.yml run sis-updater-scheduler npm start",
"flushredis": "docker exec -it redis redis-cli flushall",
"prepare": "husky",
"rapodiff": "docker exec -t oodikone_backend_1 /opt/app-root/backend/node_modules/.bin/tsx /opt/app-root/backend/src/rapodiff/rapodiff.js",
"install:local": "npm i && npm i --prefix services/shared && npm i --prefix services/frontend && npm i --prefix services/backend && npm i --prefix updater/sis-updater-scheduler && npm i --prefix updater/sis-updater-worker",
"ci:local": "npm ci && npm ci --prefix services/shared && npm ci --prefix services/frontend && npm ci --prefix services/backend && npm ci --prefix updater/sis-updater-scheduler && npm ci --prefix updater/sis-updater-worker",
"install:docker": "docker exec frontend npm i && docker exec oodikone_backend_1 npm i && docker exec sis-updater-scheduler npm i && docker exec oodikone_sis-updater-worker_1 npm i",
"ci:docker": "docker exec frontend npm ci && docker exec oodikone_backend_1 npm ci && docker exec sis-updater-scheduler npm ci && docker exec oodikone_sis-updater-worker_1 npm ci",
"install:docker": "docker exec frontend npm i && docker exec backend npm i && docker exec sis-updater-scheduler npm i && docker exec sis-updater-worker npm i",
"install:both": "npm run install:local && npm run install:docker",
"ci:local": "npm ci && npm ci --prefix services/shared && npm ci --prefix services/frontend && npm ci --prefix services/backend && npm ci --prefix updater/sis-updater-scheduler && npm ci --prefix updater/sis-updater-worker",
"ci:docker": "docker exec frontend npm ci && docker exec backend npm ci && docker exec sis-updater-scheduler npm ci && docker exec sis-updater-worker npm ci",
"ci:both": "npm run ci:local && npm run ci:docker"
},
"repository": {
@@ -44,29 +41,22 @@
},
"homepage": "https://github.com/UniversityOfHelsinkiCS/oodikone#readme",
"devDependencies": {
"@cypress/code-coverage": "^3.14.4",
"@eslint/js": "^9.28.0",
"@istanbuljs/schema": "^0.1.3",
"cypress": "^15.0.0",
"cypress": "^15.13.0",
"eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5",
"eslint-import-resolver-typescript": "^4.4.3",
"eslint-plugin-cypress": "^5.1.1",
"eslint-plugin-cypress": "^6.2.3",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"globals": "^16.2.0",
"globals": "^17.4.0",
"husky": "^9.1.7",
"lint-staged": "^16.1.0",
"node-actionlint": "^1.2.2",
"nyc": "^17.1.0",
"prettier": "3.5.3",
"stylelint": "^16.20.0",
"stylelint-config-standard": "^39.0.0",
"stylelint": "^17.6.0",
"stylelint-config-standard": "^40.0.0",
"typescript-eslint": "^8.34.0"
},
"dependencies": {
"lodash": "^4.17.21"
}
}
+6 -6
View File
@@ -35,7 +35,7 @@ parse_params() {
option=${args[0]}
# parse arguments
[[ ("$option" != "oodikone" && "$option" != "updater" && "$option" != "both") ]] && \
[[ ("$option" != "oodikone" && "$option" != "updater" && "$option" != "both" && "$option" != "tests") ]] && \
usage && die "Wrong option: $option"
[[ ${#args[@]} -eq 1 ]] && usage && die "Wrong number of arguments"
@@ -47,7 +47,7 @@ parse_params() {
compose_command=${args[*]:1}
else
version=${args[1]}
[[ "$version" != "anon" && "$version" != "real" && "$version" != "test" ]] && usage && \
[[ "$version" != "anon" && "$version" != "real" && "$version" != "api" ]] && usage && \
die "Wrong version $version"
compose_command=${args[*]:2}
fi
@@ -70,8 +70,8 @@ parse_env() {
if [[ "$version" == "real" ]]; then
env="--file docker-compose.yml --file docker-compose.real.yml"
fi
if [[ "$version" == "test" ]]; then
env="--file docker-compose.yml --file docker-compose.test.yml"
if [[ "$version" == "api" ]]; then
env="--file docker-compose.ci.yml"
fi
return 0
}
@@ -85,9 +85,9 @@ parse_params "$@"
parse_profiles
parse_env
if [[ "$compose_command" == "" ]]; then
final_command="docker-compose ${env}"
final_command="docker compose ${env}"
else
final_command="docker-compose ${env} ${profiles} ${compose_command}"
final_command="docker compose ${env} ${profiles} ${compose_command}"
fi
msg "${BLUE}Running: ${final_command}${NOFORMAT}"
-24
View File
@@ -11,7 +11,6 @@ PROJECT_ROOT="$(git rev-parse --show-toplevel)"
DUMP_DIR="$PROJECT_ROOT/.databasedumps"
S3_CONFIG_FILE=~/.s3cfg
S3_BUCKET="s3://psyduck"
DOCKER_COMPOSE=$PROJECT_ROOT/docker-compose.yml
## Following the naming convention in docker-compose, these are names for database
## services. The real data databases inside service will have names suffixed by "-real".
@@ -139,29 +138,6 @@ reset_databases() {
successmsg "Database setup finished"
}
reset_jami_data() {
local database="jami-db"
local dump_destination="$DUMP_DIR/$database.sql.gz"
local s3_path="jami"
infomsg "Removing old data"
infomsg "Removing database and related volume"
docker volume rm oodikone_jami-data || warningmsg "This is okay, continuing"
docker-compose -f "$DOCKER_COMPOSE" down "$database" || warningmsg "This is okay, continuing"
infomsg "Starting postgres in the background"
docker-compose -f "$DOCKER_COMPOSE" up -d "$database"
retry docker-compose -f "$DOCKER_COMPOSE" exec "$database" pg_isready --dbname="postgres"
infomsg "Populating Jami"
docker exec -i "$database" /bin/bash -c "gunzip | psql -U postgres" < "$dump_destination" 2> /dev/null
docker-compose stop "$database"
msg ""
}
reset_all_real_data() {
infomsg "Downloading real data dumps"
for i in ${!DATABASES[*]}; do
-30
View File
@@ -1,30 +0,0 @@
#!/usr/bin/env bash
# This script is used for instrumenting the frontend code
# Counters are added for calculating the code coverage
# Instrumented code is used for generating code coverage reports
# Ensure the script exits if any command fails
set -euo pipefail
# Define the directories
SOURCE_DIR="services/frontend/src"
INSTRU_DIR="services/frontend/instrumented"
# Print status
echo -n "Instrumenting frontend code... "
# Create the instrumented directory if it does not exist
mkdir -p $INSTRU_DIR
# Instrument the code
nyc instrument --all --compact=false $SOURCE_DIR $INSTRU_DIR
# Copy remaining files without overwriting instrumented files
#
# Images, CSS and other static files are not instrumended
# but they are still needed to run the application
rsync -av --quiet --ignore-existing $SOURCE_DIR/ $INSTRU_DIR/
# Print status
echo "Done!"
+7
View File
@@ -0,0 +1,7 @@
*/.dockerignore
*/.eslintrc.json
*/.gitignore
*/*Dockerfile
*/build
*/dist
*/node_modules
-3
View File
@@ -1,3 +0,0 @@
.dockerignore
*Dockerfile
node_modules/
+3
View File
@@ -0,0 +1,3 @@
ignore-scripts=true
min-release-age=7
save-prefix=
+4 -3
View File
@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi10/nodejs-22-minimal AS build
FROM registry.access.redhat.com/ubi9/nodejs-24-minimal AS build
ENV TZ="Europe/Helsinki"
@@ -29,7 +29,7 @@ RUN npm ci --include=dev --prefix shared && npm ci --include=dev --prefix backen
RUN npm run build --prefix shared && npm run build --prefix backend
FROM registry.access.redhat.com/ubi10/nodejs-22-minimal
FROM registry.access.redhat.com/ubi9/nodejs-24-minimal
ENV TZ="Europe/Helsinki"
@@ -52,7 +52,8 @@ WORKDIR /opt/app-root/shared
COPY --from=build /opt/app-root/shared/dist .
WORKDIR /opt/app-root/backend
COPY --from=build /opt/app-root/backend/package*.json .
COPY --from=build /opt/app-root/backend/.npmrc .
COPY --from=build /opt/app-root/backend/package*.json ./
COPY --from=build /opt/app-root/backend/dist ./dist
RUN npm ci --omit=dev
+5 -3
View File
@@ -1,4 +1,4 @@
FROM node:22-alpine
FROM registry.access.redhat.com/ubi9/nodejs-24-minimal
ENV TZ="Europe/Helsinki"
@@ -6,8 +6,10 @@ ENV NODE_ENV=development
ENV NODE_OPTIONS=--max-old-space-size=4096
WORKDIR /opt/app-root/backend
COPY . .
COPY --chown=1001:1001 . .
USER 1001
RUN npm ci
EXPOSE 8080
CMD ["node_modules/.bin/tsx", "watch", "--clear-screen=false", "index.js"]
CMD ["node_modules/.bin/nodemon", "--exec", "node --import tsx index.ts", "--ext", "ts,js,json", "--watch", "src", "--watch", "index.ts", "--legacy-watch"]
+2286 -1162
View File
File diff suppressed because it is too large Load Diff
+16 -14
View File
@@ -2,28 +2,27 @@
"name": "backend",
"private": true,
"scripts": {
"build": "tsc"
"build": "tsc",
"test": "node --import tsx --test --test-reporter spec --test-force-exit"
},
"dependencies": {
"@oodikone/shared": "file:../shared",
"@sentry/node": "^10.5.0",
"@sentry/profiling-node": "^10.5.0",
"@sentry/node": "^10.43.0",
"@sentry/profiling-node": "^10.43.0",
"axios": "^1.9.0",
"bullmq": "^5.53.2",
"compression": "^1.8.0",
"cors": "^2.8.5",
"cron": "^4.3.1",
"dayjs": "^1.11.13",
"express": "^4.21.2",
"express-async-errors": "^3.1.1",
"lodash": "^4.17.21",
"express": "^5.2.1",
"lodash-es": "^4.18.1",
"lru-cache": "^11.1.0",
"morgan": "^1.10.0",
"papaparse": "^5.5.3",
"pg": "^8.16.0",
"pg": "^8.20.0",
"redis": "^4.7.1",
"reflect-metadata": "^0.2.2",
"sequelize": "^6.37.7",
"sequelize": "^6.37.8",
"sequelize-typescript": "^2.1.6",
"umzug": "^3.8.2",
"unfuck-utf8-headers-middleware": "^1.0.1",
@@ -34,13 +33,16 @@
"devDependencies": {
"@types/compression": "^1.8.1",
"@types/cors": "^2.8.19",
"@types/express": "^4.17.23",
"@types/lodash": "^4.17.17",
"@types/express": "^5.0.6",
"@types/lodash-es": "^4.17.12",
"@types/morgan": "^1.9.10",
"@types/node": "^22.15.31",
"@types/validator": "^13.15.1",
"@types/node": "^24.10.1",
"@types/pg": "^8.20.0",
"@types/supertest": "^6.0.3",
"nodemon": "^3.1.11",
"supertest": "^7.2.2",
"tsx": "^4.19.4",
"typescript": "^5.8.3"
"typescript": "^5.9.2"
},
"overrides": {
"winston-gelf-transporter": {
-1
View File
@@ -1,4 +1,3 @@
import 'express-async-errors'
import express from 'express'
import { baseUrl, backendPort } from './config'
+2 -9
View File
@@ -1,14 +1,13 @@
// Node env to use
export const isDev = process.env.NODE_ENV === 'development'
export const isTest = process.env.NODE_ENV === 'test'
export const isStaging = process.env.REACT_APP_STAGING === 'true'
export const isProduction = !isStaging && process.env.NODE_ENV === 'production'
export const runningInCI = process.env.CI === 'true'
// IAM group
export const requiredGroup = isStaging
? ['grp-oodikone-staging-users', 'grp-oodikone-basic-staging-users']
: ['grp-oodikone-users', 'grp-oodikone-basic-users']
export const requiredGroup = ['grp-oodikone-users', 'grp-oodikone-basic-users']
// Pate
export const pateToken = process.env.PATE_API_TOKEN ?? ''
@@ -32,12 +31,6 @@ export const baseUrl = isDev ? '/api' : ''
export const frontUrl = process.env.FRONT_URL
export const backendPort = 8080
// System run in whose environment
export const serviceProvider = process.env.SERVICE_PROVIDER?.toLowerCase() ?? 'toska'
// Optional logout-url configuration
export const configLogoutUrl = serviceProvider === 'toska' ? undefined : process.env.LOGOUT_URL
// Other stuff
export const { DB_URL_KONE, DB_URL_USER, SECRET_TOKEN, SIS_DB_URL, SIS_UPDATER_URL, CRYPT_KEY } = process.env
-1
View File
@@ -5,7 +5,6 @@ export const roles = [
'courseStatistics',
'facultyStatistics',
'fullSisuAccess',
'openUniSearch',
'studyGuidanceGroups',
'teachers',
] as const
+14 -9
View File
@@ -13,7 +13,7 @@ import { updateBasicView, updateStudyTrackView } from './services/studyProgramme
import { findAndSaveTeachers } from './services/teachers/top'
import { deleteOutdatedUsers } from './services/userService'
import logger from './util/logger'
import { jobMaker, addToFlow } from './worker/queue'
import { jobQueue } from './worker/queue'
const schedule = (cronTime: string, onTick: () => void) => {
const onComplete = null
@@ -41,7 +41,7 @@ export const refreshFaculties = async () => {
logger.info('Adding jobs to refresh all faculties')
const faculties = await getFaculties()
for (const faculty of faculties) {
jobMaker.faculty(faculty.code)
jobQueue.refreshFaculty(faculty.code)
}
}
@@ -53,12 +53,13 @@ export const refreshLanguageCenterData = async () => {
}
const refreshProgrammesAndFaculties = async () => {
logger.info('Refreshing all degree programmes and faculties')
const facultyCodes = (await getFaculties()).map(faculty => faculty.code)
for (const faculty of facultyCodes) {
const programmeCodes = (await getDegreeProgrammesOfFaculty(faculty, true))
.map(programme => programme.code)
.filter(code => isRelevantProgramme(code))
await addToFlow(faculty, programmeCodes)
await jobQueue.addToFlow(faculty, programmeCodes)
}
}
@@ -71,7 +72,7 @@ export const refreshProgramme = async (code: string) => {
}
export const refreshProgrammes = async () => {
logger.info('Refreshing study programme and study track overview statistics for all programmes')
logger.info('Refreshing degree programme and study track overview statistics for all programmes')
const facultyCodes = (await getFaculties()).map(faculty => faculty.code)
const programmeCodes: string[] = []
for (const faculty of facultyCodes) {
@@ -82,7 +83,7 @@ export const refreshProgrammes = async () => {
}
for (const code of programmeCodes) {
// If combined programme is given, this updates only the bachelor programme
jobMaker.programme(code)
jobQueue.refreshProgramme(code)
}
}
@@ -96,8 +97,12 @@ const dailyJobs = async () => {
try {
await refreshTeacherLeaderboard()
await refreshProgrammesAndFaculties()
if (languageCenterViewEnabled) jobMaker.languagecenter()
jobMaker.closeToGraduation()
if (languageCenterViewEnabled) {
jobQueue.refreshLanguagecenter()
}
jobQueue.refreshCloseToGraduation()
} catch (error) {
logger.error('Daily jobs failed', error)
}
@@ -118,11 +123,11 @@ export const startCron = () => {
})
schedule('0 19 * * 1', () => {
logger.info('Updating students whose studyplans have not been updated recently')
jobMaker.studyplansUpdate(4)
jobQueue.refreshStudyplans(4)
})
schedule('0 10 * * 2', () => {
logger.info('Updating students whose studyplans have not been updated recently')
jobMaker.studyplansUpdate(5)
jobQueue.refreshStudyplans(5)
})
}
}
@@ -1,8 +1,8 @@
import { Request, Response } from 'express'
import { omit } from 'lodash'
import morgan from 'morgan'
import { FormattedUser } from '../types'
import { FormattedUser } from '@oodikone/shared/types'
import { omitKeys } from '@oodikone/shared/util'
import { getFullStudyProgrammeRights } from '../util'
import logger from '../util/logger'
@@ -52,7 +52,7 @@ const accessLogger = morgan((tokens, req: Request, res: Response): undefined =>
const onlyIamRights = !user.isAdmin && fullStudyProgrammeRights.length === 0
logger.info(message, {
...omit(meta, ['studentsUserCanAccess']), // Don't log student list which might be huge
...omitKeys(meta, ['studentsUserCanAccess']), // Don't log student list which might be huge
isUsageStats: true, // Pass this as a custom field so we can filter by it in Graylog
isIamRights: onlyIamRights || usingIamRights, // Needed for Grafana IAM users panel
})
+23 -73
View File
@@ -1,10 +1,9 @@
import * as Sentry from '@sentry/node'
import { NextFunction, Request, Response } from 'express'
import { intersection } from 'lodash'
import { intersection } from 'lodash-es'
import { configLogoutUrl, isDev, requiredGroup, serviceProvider } from '../config'
import { getMockedUser, getMockedUserFd, getOrganizationAccess, getUserFd, getUserToska } from '../services/userService'
import { FormattedUser, IamAccess } from '../types'
import { requiredGroup } from '../config'
import { getMockedUser, getOrganizationAccess, getUser } from '../services/userService'
import { ApplicationError } from '../util/customErrors'
import logger from '../util/logger'
@@ -14,30 +13,6 @@ const hasRequiredIamGroup = (iamGroups: string[], iamRights: string[]) => {
return intersection(iamGroups, requiredGroup).length > 0 || iamRights.length > 0
}
const getUser = async (
showAsUser: string | undefined,
username: string,
name: string,
email: string,
iamGroups: string[],
specialGroup: Record<string, boolean>,
sisId: string,
iamAccess: IamAccess
) => {
if (showAsUser && specialGroup?.superAdmin) {
return await getMockedUser({ userToMock: showAsUser, mockedBy: username })
}
return await getUserToska({
username,
name,
email,
iamGroups,
specialGroup,
sisId,
iamAccess,
})
}
type Headers = {
displayname?: string
hygroupcn?: string
@@ -49,7 +24,7 @@ type Headers = {
'x-show-as-user'?: string
}
const toskaUserMiddleware = async (req: Request, _res: Response, next: NextFunction) => {
export default async (req: Request, _: Response, next: NextFunction) => {
const {
'shib-session-id': sessionId,
'x-show-as-user': showAsUser,
@@ -61,20 +36,15 @@ const toskaUserMiddleware = async (req: Request, _res: Response, next: NextFunct
uid: username,
} = req.headers as Headers
const missingHeaders: string[] = []
if (!sessionId) missingHeaders.push('shib-session-id')
if (!username) missingHeaders.push('uid')
if (missingHeaders.length > 0) {
const reqUser = username ?? 'Anonymous user'
if (!username || !sessionId)
throw new ApplicationError(
`${reqUser} requested ${req.url} without valid request headers. Missing: ${missingHeaders.join(', ')}`,
`${username ?? 'Anonymous user'} requested ${req.url} without authorization headers.`,
403,
{ logoutUrl }
)
}
const iamGroups = parseIamGroups(hygroupcn!)
const { iamAccess = {}, specialGroup = {} } = await getOrganizationAccess(sisId!, iamGroups)
const iamRights = Object.keys(iamAccess)
@@ -83,49 +53,29 @@ const toskaUserMiddleware = async (req: Request, _res: Response, next: NextFunct
message: 'User does not have required iam group',
meta: { username, name, email, iamGroups, iamRights },
})
throw new ApplicationError(`User '${username}' does not have required iam group`, 403, { logoutUrl })
}
const user = await getUser(showAsUser, username!, name!, email!, iamGroups, specialGroup, sisId!, iamAccess)
if (!user) {
throw new ApplicationError(`Username ${username} not found.`, 403, { logoutUrl })
}
const user =
showAsUser && specialGroup?.superAdmin
? await getMockedUser({ userToMock: showAsUser, mockedBy: username })
: await getUser({
username,
name: name!,
email: email!,
iamGroups,
specialGroup,
sisId: sisId!,
iamAccess,
})
Sentry.setUser({ username: user.mockedBy ?? username! })
// Set username just-in-case there is error for invalid user
Sentry.setUser({ username: user?.mockedBy ?? username })
if (!user) throw new ApplicationError(`Username ${username} not found.`, 403, { logoutUrl })
req.user = user
req.logoutUrl = logoutUrl!
next()
}
const fdUserMiddleware = async (req: Request, _res: Response, next: NextFunction) => {
const { remote_user: remoteUser, 'x-show-as-user': showAsUser } = req.headers
if (!remoteUser) {
throw new ApplicationError('Not enough data in request headers, remote_user was missing', 403, { configLogoutUrl })
}
let user: FormattedUser | null
// getMockedUser in production requires the superAdmin role, which is only available
// via iamGroups, so it's now only implemented for the dev environment
if (showAsUser && isDev) {
user = await getMockedUserFd({ userToMock: showAsUser as string, mockedBy: remoteUser as string })
} else {
user = await getUserFd({ username: remoteUser as string })
}
if (!user) {
throw new ApplicationError(`Could not grant access with the eppn ${remoteUser}.`, 403, { configLogoutUrl })
}
req.user = user
req.logoutUrl = configLogoutUrl
next()
}
const currentUserMiddleware = serviceProvider === 'toska' ? toskaUserMiddleware : fdUserMiddleware
export default currentUserMiddleware
+1 -7
View File
@@ -1,10 +1,4 @@
import headersMiddleware from 'unfuck-utf8-headers-middleware'
import { serviceProvider } from '../config'
const headersToska = ['uid', 'displayName', 'shib-session-id', 'hyGroupCn', 'mail', 'hyPersonSisuId']
const headersOther = ['remote_user']
const headers = serviceProvider === 'toska' ? headersToska : headersOther
const headers = ['uid', 'displayName', 'shib-session-id', 'hyGroupCn', 'mail', 'hyPersonSisuId']
export default headersMiddleware(headers)
+25 -31
View File
@@ -11,16 +11,7 @@ import {
UpdatedAt,
} from 'sequelize-typescript'
import type {
Credit,
Enrollment,
Organization,
SISStudyRight,
SISStudyRightElement,
Student,
Studyplan,
} from '@oodikone/shared/models'
import { ExtentCode, SemesterEnrollment } from '@oodikone/shared/types'
import type { SISStudyRight } from '@oodikone/shared/models'
import { CreditModel } from './credit'
import { EnrollmentModel } from './enrollment'
@@ -34,69 +25,72 @@ import { StudyrightExtentModel } from './studyrightExtent'
underscored: true,
tableName: 'sis_study_rights',
})
export class SISStudyRightModel extends Model<SISStudyRight> implements SISStudyRight {
export class SISStudyRightModel extends Model implements SISStudyRight {
@PrimaryKey
@Column(DataType.STRING)
id!: string
declare id: SISStudyRight['id']
@ForeignKey(() => OrganizationModel)
@Column(DataType.STRING)
facultyCode!: string
declare facultyCode: SISStudyRight['facultyCode']
@BelongsTo(() => OrganizationModel, { foreignKey: 'facultyCode', targetKey: 'code' })
organization!: Organization
declare organization: SISStudyRight['organization']
@HasMany(() => StudyplanModel)
studyPlans!: Studyplan[]
declare studyPlans: SISStudyRight['studyPlans']
@HasMany(() => SISStudyRightElementModel, { foreignKey: 'studyRightId' })
studyRightElements!: SISStudyRightElement[]
declare studyRightElements: SISStudyRight['studyRightElements']
@Column(DataType.DATE)
startDate!: Date
declare startDate: SISStudyRight['startDate']
@Column(DataType.DATE)
endDate!: Date
declare endDate: SISStudyRight['endDate']
@Column(DataType.DATE)
studyStartDate!: Date
declare studyStartDate: SISStudyRight['studyStartDate']
@Column(DataType.BOOLEAN)
cancelled!: boolean
declare cancelled: SISStudyRight['cancelled']
@Column(DataType.STRING)
studentNumber!: string
declare studentNumber: SISStudyRight['studentNumber']
@BelongsTo(() => StudentModel, { foreignKey: 'studentNumber', targetKey: 'studentnumber' })
student!: Student
declare student: SISStudyRight['student']
@ForeignKey(() => StudyrightExtentModel)
@Column(DataType.INTEGER)
extentCode!: ExtentCode
declare extentCode: SISStudyRight['extentCode']
@Column(DataType.STRING)
admissionType!: string
declare admissionType: SISStudyRight['admissionType']
@Column(DataType.JSONB)
semesterEnrollments!: SemesterEnrollment[] | null
declare semesterEnrollments: SISStudyRight['semesterEnrollments']
@Column(DataType.JSONB)
declare transferInfo: SISStudyRight['transferInfo']
@HasMany(() => CreditModel, { foreignKey: 'studyright_id', sourceKey: 'id' })
credits!: Credit[]
declare credits: SISStudyRight['credits']
@HasMany(() => EnrollmentModel, { foreignKey: 'studyright_id', sourceKey: 'id' })
enrollments!: Enrollment[]
declare enrollments: SISStudyRight['enrollments']
@Column(DataType.BOOLEAN)
tvex!: boolean
declare tvex: SISStudyRight['tvex']
@Column(DataType.ARRAY(DataType.STRING))
expirationRuleUrns!: string[]
declare expirationRuleUrns: SISStudyRight['expirationRuleUrns']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: SISStudyRight['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: SISStudyRight['updatedAt']
}
@@ -10,57 +10,54 @@ import {
UpdatedAt,
} from 'sequelize-typescript'
import type { SISStudyRight, SISStudyRightElement } from '@oodikone/shared/models'
import type { Name, StudyTrack } from '@oodikone/shared/types'
import { DegreeProgrammeType, Phase } from '@oodikone/shared/types'
import type { SISStudyRightElement } from '@oodikone/shared/models'
import { SISStudyRightModel } from './SISStudyRight'
@Table({
underscored: true,
tableName: 'sis_study_right_elements',
})
export class SISStudyRightElementModel extends Model<SISStudyRightElement> implements SISStudyRightElement {
export class SISStudyRightElementModel extends Model implements SISStudyRightElement {
@PrimaryKey
@Column(DataType.STRING)
id!: string
declare id: SISStudyRightElement['id']
@Column(DataType.DATE)
startDate!: Date
declare startDate: SISStudyRightElement['startDate']
@Column(DataType.DATE)
endDate!: Date
declare endDate: SISStudyRightElement['endDate']
@Column(DataType.BOOLEAN)
graduated!: boolean
declare graduated: SISStudyRightElement['graduated']
@Column(DataType.INTEGER)
phase!: Phase
declare phase: SISStudyRightElement['phase']
@ForeignKey(() => SISStudyRightModel)
@Column(DataType.STRING)
studyRightId!: string
declare studyRightId: SISStudyRightElement['studyRightId']
@BelongsTo(() => SISStudyRightModel, { foreignKey: 'studyRightId' })
studyRight!: SISStudyRight
declare studyRight: SISStudyRightElement['studyRight']
@Column(DataType.STRING)
code!: string
declare code: SISStudyRightElement['code']
@Column(DataType.JSONB)
name!: Name
declare name: SISStudyRightElement['name']
@Column(DataType.JSONB)
studyTrack!: StudyTrack | null
declare studyTrack: SISStudyRightElement['studyTrack']
@Column(DataType.STRING)
degreeProgrammeType!: DegreeProgrammeType
declare degreeProgrammeType: SISStudyRightElement['degreeProgrammeType']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: SISStudyRightElement['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: SISStudyRightElement['updatedAt']
}
+21 -19
View File
@@ -12,8 +12,7 @@ import {
UpdatedAt,
} from 'sequelize-typescript'
import type { Course, CourseType, Credit, Enrollment, Organization } from '@oodikone/shared/models'
import type { Name } from '@oodikone/shared/types'
import type { Course } from '@oodikone/shared/models'
import { CourseProviderModel } from './courseProvider'
import { CourseTypeModel } from './courseType'
@@ -26,57 +25,60 @@ import { OrganizationModel } from './organization'
modelName: 'course',
tableName: 'course',
})
export class CourseModel extends Model<Course> implements Course {
export class CourseModel extends Model implements Course {
@PrimaryKey
@Column(DataType.STRING)
id!: string
declare id: Course['id']
@ForeignKey(() => CourseModel)
@Column(DataType.STRING)
code!: string
declare code: Course['code']
@ForeignKey(() => CourseTypeModel)
@Column(DataType.STRING)
coursetypecode!: string
declare coursetypecode: Course['coursetypecode']
@BelongsTo(() => CourseTypeModel, { foreignKey: 'coursetypecode', targetKey: 'coursetypecode' })
courseType!: CourseType
declare courseType: Course['courseType']
@HasMany(() => CreditModel, { foreignKey: 'course_id', sourceKey: 'id' })
credits!: Credit[]
declare credits: Course['credits']
@HasMany(() => EnrollmentModel, { foreignKey: 'course_id' })
enrollments!: Enrollment[]
declare enrollments: Course['enrollments']
@BelongsToMany(() => OrganizationModel, () => CourseProviderModel, 'coursecode')
organizations!: Organization[]
declare organizations: Course['organizations']
@Column(DataType.BOOLEAN)
is_study_module!: boolean
declare is_study_module: Course['is_study_module']
@Column(DataType.JSONB)
name!: Name
declare name: Course['name']
@Column(DataType.DATE)
max_attainment_date!: Date
declare max_attainment_date: Course['max_attainment_date']
@Column(DataType.DATE)
min_attainment_date!: Date
declare min_attainment_date: Course['min_attainment_date']
@CreatedAt
@Column({ field: 'created_at', type: DataType.DATE })
createdAt!: Date
declare createdAt: Course['createdAt']
@UpdatedAt
@Column({ field: 'updated_at', type: DataType.DATE })
updatedAt!: Date
declare updatedAt: Course['updatedAt']
@Column(DataType.JSONB)
substitutions!: string[]
declare substitutions: Course['substitutions']
@Column(DataType.JSONB)
declare substitution_groups: Course['substitution_groups']
@Column(DataType.STRING)
course_unit_type!: string
declare course_unit_type: Course['course_unit_type']
@Column({ field: 'main_course_code', type: DataType.STRING })
mainCourseCode!: string
declare mainCourseCode: Course['mainCourseCode']
}
@@ -10,23 +10,23 @@ import { OrganizationModel } from './organization'
modelName: 'course_provider',
tableName: 'course_providers',
})
export class CourseProviderModel extends Model<CourseProvider> implements CourseProvider {
export class CourseProviderModel extends Model implements CourseProvider {
@ForeignKey(() => CourseModel)
@Column(DataType.STRING)
coursecode!: string
declare coursecode: CourseProvider['coursecode']
@Column(DataType.JSONB)
shares!: Array<{ share: number; startDate?: string; endDate?: string }> | null
declare shares: CourseProvider['shares']
@ForeignKey(() => OrganizationModel)
@Column(DataType.STRING)
organizationcode!: string
declare organizationcode: CourseProvider['organizationcode']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: CourseProvider['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: CourseProvider['updatedAt']
}
+7 -8
View File
@@ -1,8 +1,7 @@
import { Column, CreatedAt, DataType, HasMany, Model, PrimaryKey, Table, UpdatedAt } from 'sequelize-typescript'
import type { Course, CourseType } from '@oodikone/shared/models'
import type { CourseType } from '@oodikone/shared/models'
import { Name } from '@oodikone/shared/types'
import { CourseModel } from './course'
@Table({
@@ -10,22 +9,22 @@ import { CourseModel } from './course'
modelName: 'course_type',
tableName: 'course_types',
})
export class CourseTypeModel extends Model<CourseType> implements CourseType {
export class CourseTypeModel extends Model implements CourseType {
@PrimaryKey
@Column(DataType.STRING)
coursetypecode!: string
declare coursetypecode: CourseType['coursetypecode']
@HasMany(() => CourseModel)
courses!: Course[]
declare courses: CourseType['courses']
@Column(DataType.JSONB)
name!: Required<Name>
declare name: CourseType['name']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: CourseType['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: CourseType['updatedAt']
}
+26 -26
View File
@@ -11,7 +11,7 @@ import {
UpdatedAt,
} from 'sequelize-typescript'
import type { Credit, Course, CreditType, Semester, SISStudyRight, Student, Teacher } from '@oodikone/shared/models'
import type { Credit } from '@oodikone/shared/models'
import { CreditTypeCode } from '@oodikone/shared/types'
import { CourseModel } from './course'
@@ -27,85 +27,85 @@ import { TeacherModel } from './teacher'
modelName: 'credit',
tableName: 'credit',
})
export class CreditModel extends Model<Credit> implements Credit {
export class CreditModel extends Model implements Credit {
@PrimaryKey
@Column(DataType.STRING)
id!: string
declare id: Credit['id']
@Column(DataType.STRING)
grade!: string
declare grade: Credit['grade']
@ForeignKey(() => StudentModel)
@Column(DataType.STRING)
student_studentnumber!: string
declare student_studentnumber: Credit['student_studentnumber']
@BelongsTo(() => StudentModel, { foreignKey: 'student_studentnumber', targetKey: 'studentnumber' })
student!: Student
declare student: Credit['student']
@BelongsTo(() => SemesterModel, { foreignKey: 'semester_composite' })
semester!: Semester
declare semester: Credit['semester']
@Column(DataType.DOUBLE)
credits!: number
declare credits: Credit['credits']
@ForeignKey(() => CreditTypeModel)
@Column(DataType.INTEGER)
credittypecode!: CreditTypeCode
declare credittypecode: Credit['credittypecode']
@BelongsTo(() => CreditTypeModel, { foreignKey: 'credittypecode', targetKey: 'credittypecode' })
creditType!: CreditType
declare creditType: Credit['creditType']
@Column(DataType.DATE)
createdate!: Date
declare createdate: Credit['createdate']
@Column(DataType.DATE)
attainment_date!: Date
declare attainment_date: Credit['attainment_date']
@BelongsToMany(() => TeacherModel, () => CreditTeacherModel, 'credit_id')
teachers!: Teacher[]
declare teachers: Credit['teachers']
@Column(DataType.STRING)
course_code!: string
declare course_code: Credit['course_code']
@BelongsTo(() => CourseModel, { foreignKey: 'course_id' })
course!: Course
declare course: Credit['course']
@ForeignKey(() => CourseModel)
@Column(DataType.STRING)
course_id!: string
declare course_id: Credit['course_id']
@Column(DataType.STRING)
semester_composite!: string
declare semester_composite: Credit['semester_composite']
@Column(DataType.INTEGER)
semestercode!: number
declare semestercode: Credit['semestercode']
@Column(DataType.BOOLEAN)
isStudyModule!: boolean
declare isStudyModule: Credit['isStudyModule']
@Column(DataType.STRING)
org!: string
declare org: Credit['org']
@Column(DataType.STRING)
language!: string
declare language: Credit['language']
@Column(DataType.BOOLEAN)
is_open!: boolean
declare is_open: Credit['is_open']
@ForeignKey(() => SISStudyRightModel)
@Column(DataType.STRING)
studyright_id!: string
declare studyright_id: Credit['studyright_id']
@BelongsTo(() => SISStudyRightModel)
studyright!: SISStudyRight
declare studyright: Credit['studyright']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: Credit['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: Credit['updatedAt']
static passed: ({ credittypecode }: { credittypecode: CreditTypeCode }) => boolean
+5 -5
View File
@@ -10,20 +10,20 @@ import { TeacherModel } from './teacher'
modelName: 'credit_teacher',
tableName: 'credit_teachers',
})
export class CreditTeacherModel extends Model<CreditTeacher> implements CreditTeacher {
export class CreditTeacherModel extends Model implements CreditTeacher {
@ForeignKey(() => CreditModel)
@Column(DataType.STRING)
credit_id!: string
declare credit_id: CreditTeacher['credit_id']
@ForeignKey(() => TeacherModel)
@Column(DataType.STRING)
teacher_id!: string
declare teacher_id: CreditTeacher['teacher_id']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: CreditTeacher['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: CreditTeacher['updatedAt']
}
+4 -5
View File
@@ -1,7 +1,6 @@
import { Column, CreatedAt, DataType, Model, PrimaryKey, Table, UpdatedAt } from 'sequelize-typescript'
import type { CreditType } from '@oodikone/shared/models'
import { CreditTypeCode, Name } from '@oodikone/shared/types'
@Table({
underscored: true,
@@ -11,16 +10,16 @@ import { CreditTypeCode, Name } from '@oodikone/shared/types'
export class CreditTypeModel extends Model<CreditType> implements CreditType {
@PrimaryKey
@Column(DataType.INTEGER)
credittypecode!: CreditTypeCode
declare credittypecode: CreditType['credittypecode']
@Column(DataType.JSONB)
name!: Required<Name>
declare name: CreditType['name']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: CreditType['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: CreditType['updatedAt']
}
@@ -1,34 +1,33 @@
import { Column, CreatedAt, DataType, Model, Table, UpdatedAt } from 'sequelize-typescript'
import type { CurriculumPeriod } from '@oodikone/shared/models'
import type { Name } from '@oodikone/shared/types'
@Table({
underscored: true,
modelName: 'curriculum_period',
tableName: 'curriculum_periods',
})
export class CurriculumPeriodModel extends Model<CurriculumPeriod> implements CurriculumPeriod {
export class CurriculumPeriodModel extends Model implements CurriculumPeriod {
@Column({ type: DataType.STRING, primaryKey: true })
id!: string
declare id: CurriculumPeriod['id']
@Column({ type: DataType.JSONB, allowNull: false })
name!: Name
declare name: CurriculumPeriod['name']
@Column({ type: DataType.STRING, allowNull: false })
universityOrgId!: string
declare universityOrgId: CurriculumPeriod['universityOrgId']
@Column({ type: DataType.DATE, allowNull: false })
startDate!: Date
declare startDate: CurriculumPeriod['startDate']
@Column({ type: DataType.DATE, allowNull: false })
endDate!: Date
declare endDate: CurriculumPeriod['endDate']
@CreatedAt
@Column({ type: DataType.DATE, allowNull: false })
createdAt!: Date
declare createdAt: CurriculumPeriod['createdAt']
@UpdatedAt
@Column({ type: DataType.DATE, allowNull: false })
updatedAt!: Date
declare updatedAt: CurriculumPeriod['updatedAt']
}
+18 -19
View File
@@ -10,8 +10,7 @@ import {
UpdatedAt,
} from 'sequelize-typescript'
import type { Enrollment, Semester, SISStudyRight, Student } from '@oodikone/shared/models'
import { EnrollmentState } from '@oodikone/shared/types'
import type { Enrollment } from '@oodikone/shared/models'
import { CourseModel } from './course'
import { SemesterModel } from './semester'
@@ -23,59 +22,59 @@ import { StudentModel } from './student'
modelName: 'enrollment',
tableName: 'enrollment',
})
export class EnrollmentModel extends Model<Enrollment> implements Enrollment {
export class EnrollmentModel extends Model implements Enrollment {
@PrimaryKey
@Column(DataType.STRING)
id!: string
declare id: Enrollment['id']
@ForeignKey(() => StudentModel)
@Column(DataType.STRING)
studentnumber!: string
declare studentnumber: Enrollment['studentnumber']
@BelongsTo(() => StudentModel, { foreignKey: 'studentnumber', targetKey: 'studentnumber' })
student!: Student
declare student: Enrollment['student']
@Column(DataType.STRING)
course_code!: string
declare course_code: Enrollment['course_code']
@Column(DataType.STRING)
state!: EnrollmentState
declare state: Enrollment['state']
@Column(DataType.DATE)
enrollment_date_time!: Date
declare enrollment_date_time: Enrollment['enrollment_date_time']
@ForeignKey(() => CourseModel)
@Column(DataType.STRING)
course_id!: string
declare course_id: Enrollment['course_id']
@ForeignKey(() => SemesterModel)
@Column(DataType.STRING)
semester_composite!: string
declare semester_composite: Enrollment['semester_composite']
@BelongsTo(() => SemesterModel, { foreignKey: 'semester_composite', targetKey: 'composite' })
semester!: Semester
declare semester: Enrollment['semester']
@Column(DataType.INTEGER)
semestercode!: number
declare semestercode: Enrollment['semestercode']
@Column(DataType.BOOLEAN)
is_open!: boolean
declare is_open: Enrollment['is_open']
@ForeignKey(() => SISStudyRightModel)
@Column({ type: DataType.STRING, allowNull: true })
studyright_id!: string
declare studyright_id: Enrollment['studyright_id']
@BelongsTo(() => SISStudyRightModel, { foreignKey: 'studyright_id', targetKey: 'id', constraints: false })
studyright!: SISStudyRight
declare studyright: Enrollment['studyright']
@BelongsTo(() => CourseModel, { foreignKey: 'course_id', targetKey: 'id' })
course!: CourseModel
declare course: Enrollment['course']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: Enrollment['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: Enrollment['updatedAt']
}
@@ -1,28 +1,24 @@
import { CreationOptional } from 'sequelize'
import { AutoIncrement, Column, DataType, Model, PrimaryKey, Table } from 'sequelize-typescript'
import type { CustomPopulationSearch, CustomPopulationSearchCreation } from '@oodikone/shared/models/kone'
import type { CustomPopulationSearch } from '@oodikone/shared/models/kone'
@Table({
underscored: false,
modelName: 'custom_population_search',
tableName: 'custom_population_searches',
})
export class CustomPopulationSearchModel
extends Model<CustomPopulationSearch, CustomPopulationSearchCreation>
implements CustomPopulationSearch
{
export class CustomPopulationSearchModel extends Model implements CustomPopulationSearch {
@PrimaryKey
@AutoIncrement
@Column(DataType.BIGINT)
id!: CreationOptional<string>
declare id: CustomPopulationSearch['id']
@Column(DataType.BIGINT)
userId!: string
declare userId: CustomPopulationSearch['userId']
@Column(DataType.STRING)
name!: string
declare name: CustomPopulationSearch['name']
@Column(DataType.ARRAY(DataType.STRING))
students!: string[]
declare students: CustomPopulationSearch['students']
}
@@ -1,33 +1,32 @@
import { CreationOptional } from 'sequelize'
import { Column, DataType, Model, PrimaryKey, Table, AutoIncrement, Default } from 'sequelize-typescript'
import type { ExcludedCourse, ExcludedCourseCreation } from '@oodikone/shared/models/kone'
import type { ExcludedCourse } from '@oodikone/shared/models/kone'
@Table({
underscored: true,
modelName: 'excluded_course',
tableName: 'excluded_courses',
})
export class ExcludedCourseModel extends Model<ExcludedCourse, ExcludedCourseCreation> implements ExcludedCourse {
export class ExcludedCourseModel extends Model implements ExcludedCourse {
@PrimaryKey
@AutoIncrement
@Column(DataType.INTEGER)
id!: CreationOptional<number>
declare id: ExcludedCourse['id']
@Column(DataType.STRING)
programme_code!: string
declare programme_code: ExcludedCourse['programme_code']
@Column(DataType.STRING)
course_code!: string
declare course_code: ExcludedCourse['course_code']
@Column(DataType.STRING)
curriculum_version!: string
declare curriculum_version: ExcludedCourse['curriculum_version']
@Default(DataType.NOW)
@Column(DataType.DATE)
createdAt!: CreationOptional<Date>
declare createdAt: ExcludedCourse['createdAt']
@Default(DataType.NOW)
@Column(DataType.DATE)
updatedAt!: CreationOptional<Date>
declare updatedAt: ExcludedCourse['updatedAt']
}
@@ -1,7 +1,6 @@
import { CreationOptional } from 'sequelize'
import { AutoIncrement, Column, DataType, Model, PrimaryKey, Table } from 'sequelize-typescript'
import type { OpenUniPopulationSearch, OpenUniPopulationSearchCreation } from '@oodikone/shared/models/kone'
import type { OpenUniPopulationSearch } from '@oodikone/shared/models/kone'
@Table({
underscored: true,
@@ -9,21 +8,18 @@ import type { OpenUniPopulationSearch, OpenUniPopulationSearchCreation } from '@
modelName: 'open_uni_population_search',
tableName: 'open_uni_population_searches',
})
export class OpenUniPopulationSearchModel
extends Model<OpenUniPopulationSearch, OpenUniPopulationSearchCreation>
implements OpenUniPopulationSearch
{
export class OpenUniPopulationSearchModel extends Model implements OpenUniPopulationSearch {
@PrimaryKey
@AutoIncrement
@Column(DataType.BIGINT)
id!: CreationOptional<string>
declare id: OpenUniPopulationSearch['id']
@Column(DataType.BIGINT)
userId!: string
declare userId: OpenUniPopulationSearch['userId']
@Column(DataType.STRING)
name!: string
declare name: OpenUniPopulationSearch['name']
@Column(DataType.ARRAY(DataType.STRING))
courseCodes!: string[]
declare courseCodes: OpenUniPopulationSearch['courseCodes']
}
@@ -1,7 +1,6 @@
import { CreationOptional } from 'sequelize'
import { Column, DataType, Model, PrimaryKey, Table } from 'sequelize-typescript'
import type { ProgressCriteria, ProgressCriteriaCreation } from '@oodikone/shared/models/kone'
import type { ProgressCriteria } from '@oodikone/shared/models/kone'
@Table({
underscored: true,
@@ -9,50 +8,47 @@ import type { ProgressCriteria, ProgressCriteriaCreation } from '@oodikone/share
modelName: 'progress_criteria',
tableName: 'progress_criteria',
})
export class ProgressCriteriaModel
extends Model<ProgressCriteria, ProgressCriteriaCreation>
implements ProgressCriteria
{
export class ProgressCriteriaModel extends Model implements ProgressCriteria {
@PrimaryKey
@Column(DataType.STRING)
code!: string
declare code: ProgressCriteria['code']
@Column(DataType.STRING)
curriculumVersion!: CreationOptional<string>
declare curriculumVersion: ProgressCriteria['curriculumVersion']
@Column(DataType.ARRAY(DataType.STRING))
coursesYearOne!: string[]
declare coursesYearOne: ProgressCriteria['coursesYearOne']
@Column(DataType.ARRAY(DataType.STRING))
coursesYearTwo!: string[]
declare coursesYearTwo: ProgressCriteria['coursesYearTwo']
@Column(DataType.ARRAY(DataType.STRING))
coursesYearThree!: string[]
declare coursesYearThree: ProgressCriteria['coursesYearThree']
@Column(DataType.ARRAY(DataType.STRING))
coursesYearFour!: string[]
declare coursesYearFour: ProgressCriteria['coursesYearFour']
@Column(DataType.ARRAY(DataType.STRING))
coursesYearFive!: string[]
declare coursesYearFive: ProgressCriteria['coursesYearFive']
@Column(DataType.ARRAY(DataType.STRING))
coursesYearSix!: string[]
declare coursesYearSix: ProgressCriteria['coursesYearSix']
@Column(DataType.INTEGER)
creditsYearOne!: number
declare creditsYearOne: ProgressCriteria['creditsYearOne']
@Column(DataType.INTEGER)
creditsYearTwo!: number
declare creditsYearTwo: ProgressCriteria['creditsYearTwo']
@Column(DataType.INTEGER)
creditsYearThree!: number
declare creditsYearThree: ProgressCriteria['creditsYearThree']
@Column(DataType.INTEGER)
creditsYearFour!: number
declare creditsYearFour: ProgressCriteria['creditsYearFour']
@Column(DataType.INTEGER)
creditsYearFive!: number
declare creditsYearFive: ProgressCriteria['creditsYearFive']
@Column(DataType.INTEGER)
creditsYearSix!: number
declare creditsYearSix: ProgressCriteria['creditsYearSix']
}
@@ -1,7 +1,6 @@
import { CreationOptional } from 'sequelize'
import { AutoIncrement, Column, DataType, Model, PrimaryKey, Table, Unique } from 'sequelize-typescript'
import type { StudyGuidanceGroupTag, StudyGuidanceGroupTagCreation } from '@oodikone/shared/models/kone'
import type { StudyGuidanceGroupTag } from '@oodikone/shared/models/kone'
@Table({
underscored: true,
@@ -9,22 +8,19 @@ import type { StudyGuidanceGroupTag, StudyGuidanceGroupTagCreation } from '@oodi
modelName: 'study_guidance_group_tag',
tableName: 'study_guidance_group_tags',
})
export class StudyGuidanceGroupTagModel
extends Model<StudyGuidanceGroupTag, StudyGuidanceGroupTagCreation>
implements StudyGuidanceGroupTag
{
export class StudyGuidanceGroupTagModel extends Model implements StudyGuidanceGroupTag {
@PrimaryKey
@AutoIncrement
@Column(DataType.BIGINT)
id?: CreationOptional<string>
declare id: StudyGuidanceGroupTag['id']
@Unique
@Column(DataType.STRING)
studyGuidanceGroupId!: string
declare studyGuidanceGroupId: StudyGuidanceGroupTag['studyGuidanceGroupId']
@Column(DataType.STRING)
studyProgramme!: string | null
declare studyProgramme: StudyGuidanceGroupTag['studyProgramme']
@Column(DataType.STRING)
year!: string | null
declare year: StudyGuidanceGroupTag['year']
}
@@ -8,11 +8,11 @@ import type { StudyProgrammePin } from '@oodikone/shared/models/kone'
modelName: 'study_programme_pin',
tableName: 'study_programme_pins',
})
export class StudyProgrammePinModel extends Model<StudyProgrammePin> implements StudyProgrammePin {
export class StudyProgrammePinModel extends Model implements StudyProgrammePin {
@PrimaryKey
@Column(DataType.INTEGER)
userId!: number
declare userId: StudyProgrammePin['userId']
@Column(DataType.ARRAY(DataType.STRING))
studyProgrammes!: string[]
declare studyProgrammes: StudyProgrammePin['studyProgrammes']
}
+8 -9
View File
@@ -1,7 +1,6 @@
import { CreationOptional } from 'sequelize'
import { AutoIncrement, BelongsTo, Column, DataType, ForeignKey, Model, PrimaryKey, Table } from 'sequelize-typescript'
import type { Tag, TagCreation, TagStudent } from '@oodikone/shared/models/kone'
import type { Tag } from '@oodikone/shared/models/kone'
import { TagStudentModel } from './tagStudent'
@@ -9,25 +8,25 @@ import { TagStudentModel } from './tagStudent'
modelName: 'tag',
tableName: 'tag',
})
export class TagModel extends Model<Tag, TagCreation> implements Tag {
export class TagModel extends Model implements Tag {
@PrimaryKey
@AutoIncrement
@ForeignKey(() => TagStudentModel)
@Column(DataType.BIGINT)
tag_id!: CreationOptional<string>
declare tag_id: Tag['tag_id']
@BelongsTo(() => TagStudentModel)
tagStudent!: CreationOptional<TagStudent>
declare tagStudent: Tag['tagStudent']
@Column(DataType.STRING)
tagname!: string
declare tagname: Tag['tagname']
@Column(DataType.STRING)
studytrack!: string
declare studytrack: Tag['studytrack']
@Column(DataType.STRING)
year!: string | null
declare year: Tag['year']
@Column(DataType.BIGINT)
personal_user_id!: string | null
declare personal_user_id: Tag['personal_user_id']
}
@@ -1,7 +1,6 @@
import { CreationOptional } from 'sequelize'
import { AutoIncrement, BelongsTo, Column, DataType, ForeignKey, Model, PrimaryKey, Table } from 'sequelize-typescript'
import type { Tag, TagStudent, TagStudentCreation } from '@oodikone/shared/models/kone'
import type { TagStudent } from '@oodikone/shared/models/kone'
import { StudentModel } from '../student'
import { TagModel } from './tag'
@@ -10,18 +9,18 @@ import { TagModel } from './tag'
modelName: 'tag_student',
tableName: 'tag_student',
})
export class TagStudentModel extends Model<TagStudent, TagStudentCreation> implements TagStudent {
export class TagStudentModel extends Model implements TagStudent {
@PrimaryKey
@AutoIncrement
@ForeignKey(() => TagModel)
@Column(DataType.BIGINT)
tag_id!: string
declare tag_id: TagStudent['tag_id']
@PrimaryKey
@ForeignKey(() => StudentModel)
@Column(DataType.STRING)
studentnumber!: string
declare studentnumber: TagStudent['studentnumber']
@BelongsTo(() => TagModel)
tag!: CreationOptional<Tag>
declare tag: TagStudent['tag']
}
+12 -13
View File
@@ -10,8 +10,7 @@ import {
UpdatedAt,
} from 'sequelize-typescript'
import type { Organization, ProgrammeModule, SISStudyRight } from '@oodikone/shared/models'
import type { Name } from '@oodikone/shared/types'
import type { Organization } from '@oodikone/shared/models'
import { CourseModel } from './course'
import { CourseProviderModel } from './courseProvider'
@@ -23,37 +22,37 @@ import { SISStudyRightModel } from './SISStudyRight'
modelName: 'organization',
tableName: 'organization',
})
export class OrganizationModel extends Model<Organization> implements Organization {
export class OrganizationModel extends Model implements Organization {
@PrimaryKey
@Column(DataType.STRING)
id!: string
declare id: Organization['id']
@Column(DataType.STRING)
code!: string
declare code: Organization['code']
@Column(DataType.JSONB)
name!: Name
declare name: Organization['name']
@Column(DataType.STRING)
parent_id!: string
declare parent_id: Organization['parent_id']
@HasMany(() => OrganizationModel, { foreignKey: 'parent_id', as: 'children' })
children!: Organization[]
declare children: Organization['children']
@HasMany(() => ProgrammeModuleModel, { foreignKey: 'organization_id' })
programmeModules!: ProgrammeModule[]
declare programmeModules: Organization['programmeModules']
@HasMany(() => SISStudyRightModel, { foreignKey: 'facultyCode', sourceKey: 'code' })
SISStudyRights!: SISStudyRight[]
declare SISStudyRights: Organization['SISStudyRights']
@BelongsToMany(() => CourseModel, () => CourseProviderModel, 'organizationcode')
courses!: CourseModel[]
declare courses: Organization['courses']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: Organization['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: Organization['updatedAt']
}
+21 -22
View File
@@ -12,7 +12,6 @@ import {
} from 'sequelize-typescript'
import type { Organization, ProgrammeModule } from '@oodikone/shared/models'
import { DegreeProgrammeType, Name } from '@oodikone/shared/types'
import { OrganizationModel } from './organization'
import { ProgrammeModuleChildModel } from './programmeModuleChild'
@@ -22,62 +21,62 @@ import { ProgrammeModuleChildModel } from './programmeModuleChild'
modelName: 'programme_module',
tableName: 'programme_modules',
})
export class ProgrammeModuleModel extends Model<ProgrammeModule> implements ProgrammeModule {
export class ProgrammeModuleModel extends Model implements ProgrammeModule {
@PrimaryKey
@Column(DataType.STRING)
id!: string
declare id: ProgrammeModule['id']
@BelongsToMany(() => ProgrammeModuleModel, () => ProgrammeModuleChildModel, 'child_id')
parents!: ProgrammeModule[]
declare parents: ProgrammeModule[]
@BelongsToMany(() => ProgrammeModuleModel, () => ProgrammeModuleChildModel, 'parent_id')
children!: ProgrammeModule[]
declare children: ProgrammeModule[]
@BelongsTo(() => OrganizationModel, { foreignKey: 'organization_id' })
declare organization: Organization
@Column(DataType.STRING)
group_id!: string
declare group_id: ProgrammeModule['group_id']
@Column(DataType.STRING)
code!: string
declare code: ProgrammeModule['code']
@Column(DataType.JSONB)
name!: Name
declare name: ProgrammeModule['name']
@Column(DataType.STRING)
type!: 'course' | 'module'
declare type: ProgrammeModule['type']
@Column(DataType.INTEGER)
order!: number
declare order: ProgrammeModule['order']
@Column(DataType.STRING)
studyLevel!: string
declare studyLevel: ProgrammeModule['studyLevel']
@ForeignKey(() => OrganizationModel)
@Column(DataType.STRING)
organization_id!: string
@BelongsTo(() => OrganizationModel, { foreignKey: 'organization_id' })
organization!: Organization
declare organization_id: ProgrammeModule['organization_id']
@Column(DataType.DATE)
valid_from!: Date
declare valid_from: ProgrammeModule['valid_from']
@Column(DataType.DATE)
valid_to!: Date
declare valid_to: ProgrammeModule['valid_to']
@Column(DataType.ARRAY(DataType.STRING))
curriculum_period_ids!: string[]
declare curriculum_period_ids: ProgrammeModule['curriculum_period_ids']
@Column(DataType.STRING)
degreeProgrammeType!: DegreeProgrammeType | null
declare degreeProgrammeType: ProgrammeModule['degreeProgrammeType']
@Column(DataType.INTEGER)
minimumCredits!: number | null
declare minimumCredits: ProgrammeModule['minimumCredits']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: ProgrammeModule['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: ProgrammeModule['updatedAt']
}
@@ -9,23 +9,23 @@ import { ProgrammeModuleModel } from './programmeModule'
modelName: 'programme_module_child',
tableName: 'programme_module_children',
})
export class ProgrammeModuleChildModel extends Model<ProgrammeModuleChild> implements ProgrammeModuleChild {
export class ProgrammeModuleChildModel extends Model implements ProgrammeModuleChild {
@PrimaryKey
@Column(DataType.STRING)
composite!: string
declare composite: ProgrammeModuleChild['composite']
@ForeignKey(() => ProgrammeModuleModel)
@Column(DataType.STRING)
parentId!: string
declare parentId: ProgrammeModuleChild['parentId']
@Column(DataType.STRING)
childId!: string
declare childId: ProgrammeModuleChild['childId']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: ProgrammeModuleChild['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: ProgrammeModuleChild['updatedAt']
}
+13 -14
View File
@@ -1,50 +1,49 @@
import { Column, CreatedAt, DataType, Model, PrimaryKey, Table, UpdatedAt } from 'sequelize-typescript'
import type { Semester } from '@oodikone/shared/models'
import type { Name } from '@oodikone/shared/types'
@Table({
underscored: true,
modelName: 'semester',
tableName: 'semesters',
})
export class SemesterModel extends Model<Semester> implements Semester {
export class SemesterModel extends Model implements Semester {
@PrimaryKey
@Column(DataType.STRING)
composite!: string
declare composite: Semester['composite']
@Column(DataType.INTEGER)
semestercode!: number
declare semestercode: Semester['semestercode']
@Column(DataType.JSONB)
name!: Name
declare name: Semester['name']
@Column(DataType.INTEGER)
startYear!: number
declare startYear: Semester['startYear']
@Column(DataType.DATE)
startdate!: Date
declare startdate: Semester['startdate']
@Column(DataType.DATE)
enddate!: Date
declare enddate: Semester['enddate']
@Column(DataType.INTEGER)
yearcode!: number
declare yearcode: Semester['yearcode']
@Column(DataType.STRING)
yearname!: string
declare yearname: Semester['yearname']
@Column(DataType.STRING)
org!: string
declare org: Semester['org']
@Column(DataType.INTEGER)
termIndex!: number
declare termIndex: Semester['termIndex']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: Semester['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: Semester['updatedAt']
}
+24 -26
View File
@@ -1,8 +1,6 @@
import { Column, CreatedAt, DataType, HasMany, Model, PrimaryKey, Table, UpdatedAt } from 'sequelize-typescript'
import type { Student, Credit, Enrollment, SISStudyRight, Studyplan } from '@oodikone/shared/models'
import type { Name } from '@oodikone/shared/types'
import { GenderCode } from '@oodikone/shared/types'
import type { Student } from '@oodikone/shared/models'
import { CreditModel } from './credit'
import { EnrollmentModel } from './enrollment'
@@ -14,73 +12,73 @@ import { StudyplanModel } from './studyplan'
modelName: 'student',
tableName: 'student',
})
export class StudentModel extends Model<Student> implements Student {
export class StudentModel extends Model implements Student {
@PrimaryKey
@Column(DataType.STRING)
studentnumber!: string
declare studentnumber: Student['studentnumber']
@Column(DataType.STRING)
lastname!: string
declare lastname: Student['lastname']
@Column(DataType.STRING)
firstnames!: string
declare firstnames: Student['firstnames']
@Column(DataType.STRING)
abbreviatedname!: string
declare abbreviatedname: Student['abbreviatedname']
@HasMany(() => EnrollmentModel, { foreignKey: 'studentnumber', sourceKey: 'studentnumber' })
enrollments!: Enrollment[]
declare enrollments: Student['enrollments']
@HasMany(() => SISStudyRightModel, { foreignKey: 'studentNumber', sourceKey: 'studentnumber' })
studyRights!: SISStudyRight[]
declare studyRights: Student['studyRights']
@HasMany(() => StudyplanModel, { foreignKey: 'studentnumber', sourceKey: 'studentnumber' })
studyplans!: Studyplan[]
declare studyplans: Student['studyplans']
@HasMany(() => CreditModel, { foreignKey: 'student_studentnumber', sourceKey: 'studentnumber' })
credits!: Credit[]
declare credits: Student['credits']
@Column(DataType.DATE)
birthdate!: Date
declare birthdate: Student['birthdate']
@Column(DataType.INTEGER)
creditcount!: number
declare creditcount: Student['creditcount']
@Column(DataType.DATE)
dateofuniversityenrollment!: Date
declare dateofuniversityenrollment: Student['dateofuniversityenrollment']
@Column(DataType.STRING)
email!: string
declare email: Student['email']
@Column(DataType.STRING)
secondary_email!: string
declare secondary_email: Student['secondary_email']
@Column(DataType.STRING)
national_student_number!: string
declare national_student_number: Student['national_student_number']
@Column(DataType.STRING)
phone_number!: string
declare phone_number: Student['phone_number']
@Column(DataType.JSONB)
citizenships!: Name[]
declare citizenships: Student['citizenships']
@Column(DataType.STRING)
gender_code!: GenderCode
declare gender_code: Student['gender_code']
@Column(DataType.STRING)
sis_person_id!: string
declare sis_person_id: Student['sis_person_id']
@Column(DataType.BOOLEAN)
hasPersonalIdentityCode!: boolean
declare hasPersonalIdentityCode: Student['hasPersonalIdentityCode']
@Column(DataType.STRING)
preferredLanguage!: string
declare preferredLanguage: Student['preferredLanguage']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: Student['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: Student['updatedAt']
}
+15 -15
View File
@@ -10,7 +10,7 @@ import {
UpdatedAt,
} from 'sequelize-typescript'
import type { SISStudyRight, Student, Studyplan } from '@oodikone/shared/models'
import type { Studyplan } from '@oodikone/shared/models'
import { SISStudyRightModel } from './SISStudyRight'
import { StudentModel } from './student'
@@ -20,48 +20,48 @@ import { StudentModel } from './student'
modelName: 'studyplan',
tableName: 'studyplan',
})
export class StudyplanModel extends Model<Studyplan> implements Studyplan {
export class StudyplanModel extends Model implements Studyplan {
@PrimaryKey
@Column(DataType.STRING)
id!: string
declare id: Studyplan['id']
@ForeignKey(() => StudentModel)
@Column(DataType.STRING)
studentnumber!: string
declare studentnumber: Studyplan['studentnumber']
@BelongsTo(() => StudentModel, { foreignKey: 'studentnumber', targetKey: 'studentnumber' })
student!: Student
declare student: Studyplan['student']
@ForeignKey(() => SISStudyRightModel)
@Column(DataType.STRING)
sis_study_right_id!: string
declare sis_study_right_id: Studyplan['sis_study_right_id']
@BelongsTo(() => SISStudyRightModel, { foreignKey: 'sis_study_right_id', targetKey: 'id', as: 'studyRight' })
studyRight!: SISStudyRight
declare studyRight: Studyplan['studyRight']
@Column(DataType.STRING)
programme_code!: string
declare programme_code: Studyplan['programme_code']
@Column(DataType.ARRAY(DataType.STRING))
included_courses!: string[]
declare included_courses: Studyplan['included_courses']
@Column({ type: DataType.ARRAY(DataType.TEXT), field: 'included_modules' })
includedModules!: string[]
declare includedModules: Studyplan['includedModules']
@Column(DataType.STRING)
sisu_id!: string
declare sisu_id: Studyplan['sisu_id']
@Column(DataType.DOUBLE)
completed_credits!: number
declare completed_credits: Studyplan['completed_credits']
@Column(DataType.STRING)
curriculum_period_id!: string
declare curriculum_period_id: Studyplan['curriculum_period_id']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: Studyplan['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: Studyplan['updatedAt']
}
@@ -1,26 +1,25 @@
import { Column, CreatedAt, DataType, Model, PrimaryKey, Table, UpdatedAt } from 'sequelize-typescript'
import type { StudyrightExtent } from '@oodikone/shared/models'
import { ExtentCode } from '@oodikone/shared/types'
@Table({
underscored: true,
modelName: 'studyright_extent',
tableName: 'studyright_extents',
})
export class StudyrightExtentModel extends Model<StudyrightExtent> implements StudyrightExtent {
export class StudyrightExtentModel extends Model implements StudyrightExtent {
@PrimaryKey
@Column(DataType.INTEGER)
extentcode!: ExtentCode
declare extentcode: StudyrightExtent['extentcode']
@Column(DataType.STRING)
name!: string
declare name: StudyrightExtent['name']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: StudyrightExtent['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: StudyrightExtent['updatedAt']
}
+7 -7
View File
@@ -1,6 +1,6 @@
import { BelongsToMany, Column, CreatedAt, DataType, Model, PrimaryKey, Table, UpdatedAt } from 'sequelize-typescript'
import type { Teacher, Credit } from '@oodikone/shared/models'
import type { Teacher } from '@oodikone/shared/models'
import { CreditModel } from './credit'
import { CreditTeacherModel } from './creditTeacher'
@@ -10,22 +10,22 @@ import { CreditTeacherModel } from './creditTeacher'
modelName: 'teacher',
tableName: 'teacher',
})
export class TeacherModel extends Model<Teacher> implements Teacher {
export class TeacherModel extends Model implements Teacher {
@PrimaryKey
@Column(DataType.STRING)
id!: string
declare id: Teacher['id']
@Column(DataType.STRING)
name!: string
declare name: Teacher['name']
@BelongsToMany(() => CreditModel, () => CreditTeacherModel)
credits!: Credit[]
declare credits: Teacher['credits']
@CreatedAt
@Column(DataType.DATE)
createdAt!: Date
declare createdAt: Teacher['createdAt']
@UpdatedAt
@Column(DataType.DATE)
updatedAt!: Date
declare updatedAt: Teacher['updatedAt']
}
+9 -13
View File
@@ -1,11 +1,7 @@
import { CreationOptional } from 'sequelize'
import { AutoIncrement, Column, DataType, Default, Model, PrimaryKey, Table, Unique } from 'sequelize-typescript'
import type { Language } from '@oodikone/shared/language'
import type { User, UserCreation } from '@oodikone/shared/models/user'
import type { Role } from '@oodikone/shared/types'
@Table({
underscored: true,
timestamps: false,
@@ -16,33 +12,33 @@ export class UserModel extends Model<User, UserCreation> implements User {
@PrimaryKey
@AutoIncrement
@Column(DataType.BIGINT)
id!: CreationOptional<string>
declare id: User['id']
@Column(DataType.STRING)
fullName!: string
declare fullName: User['fullName']
@Unique
@Column(DataType.STRING)
username!: string
declare username: User['username']
@Column(DataType.STRING)
email!: string
declare email: User['email']
@Default('fi')
@Column(DataType.STRING)
language!: CreationOptional<Language>
declare language: User['language']
@Column(DataType.STRING)
sisuPersonId!: string
declare sisuPersonId: User['sisuPersonId']
@Column(DataType.DATE)
lastLogin!: Date
declare lastLogin: User['lastLogin']
@Default([])
@Column(DataType.ARRAY(DataType.STRING))
roles!: CreationOptional<Role[]>
declare roles: User['roles']
@Default([])
@Column(DataType.ARRAY(DataType.STRING))
programmeRights!: CreationOptional<string[]>
declare programmeRights: User['programmeRights']
}
-2
View File
@@ -1,2 +0,0 @@
*.csv
*.csv#
-22
View File
@@ -1,22 +0,0 @@
# Rapodiff
Use this to check differences between oodikone and rapo.
## How to load the CSV file from Rapo
1. Go to Rapo and open ”Koulutusohjelmien vuosiseuranta”
2. Navigate to ”Opintopisteet ja opetus / Credits and teaching”
3. From the faculty dropdown menu, select all the faculties
4. From the bottom of the page, click ”Vie -> Tiedot -> CSV”
5. Rename the file to `data.csv` and copy it to this directory
## Usage
1. Have real oodikone running. Then run rapodiff **in the root directory**: `npm run rapodiff -- args`
2. place args with a mode. omit args to print possible modes.
## Known differences in APIs
- rapo does not show students that have graduated (it however takes some days for rapo to notice a graduation)
- rapo does not handle the starting year of transfers properly (shows in old staring population)
- rapo show some students incorrectly passive, see https://github.com/UniversityOfHelsinkiCS/oodikone/issues/3521
@@ -1,188 +0,0 @@
const { orderBy } = require('lodash')
// dbConnections must be imported to avoid Sequelize errors even if it's not used
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { dbConnections } = require('../database/connection')
const { computeCreditsProduced } = require('../services/providerCredits')
const { parseCsv, printProgressBar } = require('./helpers')
const diffs = []
let noDiffCounter = 0
// Change weird rapo programmecodes to oodikone format, example: 300-M003 => MH30_003
const transformProgrammeCode = oldCode => `${oldCode[4]}H${oldCode.slice(0, 2)}_${oldCode.slice(5, 8)}`
const formatData = data =>
data.reduce((obj, cur) => {
const parseNum = str => (str === '' ? 0 : parseFloat(str.replace(',', '.')))
const [
year,
facultyCode,
programmeInfo,
basic,
exchange,
otherUni,
openUni,
special,
total,
transferredFromAbroad,
otherTransferred,
] = cur
const programmeCode = transformProgrammeCode(programmeInfo.split(' ')[0])
if (!obj[programmeCode]) obj[programmeCode] = {}
obj[programmeCode][year] = {
year: parseNum(year),
facultyCode,
programmeInfo,
basic: parseNum(basic),
'incoming-exchange': parseNum(exchange),
agreement: parseNum(otherUni),
'open-uni': parseNum(openUni),
separate: parseNum(special),
total: parseNum(total),
transferred: parseNum(transferredFromAbroad) + parseNum(otherTransferred),
}
return obj
}, {})
const diff = (rapoData, okData, code) => {
for (const year of Object.keys(okData)) {
if (year !== '2022' && year !== '2023') continue
const rapo = rapoData[year]
const ok = okData[year]
if (!rapo) {
if (!ok) console.log(`Year ${year} not in rapo for code ${code}, skipping`)
continue
}
for (const field of ['basic', 'incoming-exchange', 'open-uni', 'agreement', 'separate', 'transferred']) {
const okValue = Math.round(ok[field] || 0)
const rapoValue = Math.round(rapo[field] || 0)
const diff = Math.abs(okValue - rapoValue)
if (diff === 0) {
noDiffCounter++
continue
}
const percentage = rapoValue !== 0 ? Number(((diff / rapoValue) * 100).toFixed(2)) : 100
diffs.push({ field, year, code, okValue, rapoValue, diff, 'diff %': percentage })
}
}
}
const processProgrammes = async data => {
let programmeCounter = 0
const rapoProgrammeData = formatData(data.slice(1))
const allProgrammeCodes = [...new Set(Object.keys(rapoProgrammeData))]
for (const programmeCode of allProgrammeCodes) {
const okProgrammeData = await computeCreditsProduced(programmeCode, false, true)
diff(rapoProgrammeData[programmeCode], okProgrammeData.stats, programmeCode)
printProgressBar(allProgrammeCodes.length, ++programmeCounter)
}
const fieldToSortBy = 'diff' // 'diff %'
const orderedDiffs = orderBy(diffs, fieldToSortBy, 'asc')
console.table(orderedDiffs)
console.log(`${diffs.length} diffs found with current settings. Numbers with no diff: ${noDiffCounter}`)
const totalDiff = orderedDiffs.reduce((sum, cur) => cur.diff + sum, 0)
console.log('Total difference: ', totalDiff)
}
const programmeCreditsDiff = async fileName => {
console.log('Running studyprogramme credits diff')
await parseCsv(fileName, processProgrammes)
console.log('Diff completed.')
}
/*
separate
Suoritus on erillisellä opiskeluoikeudella opettajankoulutuksen opintoja suorittavan opiskelijan suorittama
Suoritus on erillisellä opiskeluoikeudella opiskelevan suorittama
agreement
Suoritus on korkeakoulujen välisillä yhteistyösopimuksilla opiskelevan suorittama
open-uni
Suoritus on avoimessa korkeakouluopetuksessa suoritettu
incoming-exchange
Suoritus on tulevan kansainvälisen vaihto-opiskelijan suorittama
basic
perustutkinto-opiskelija = 1
ja
Tuntematon
tai (ehkä):
Suoritus on kotimainen harjoittelujakso
?? Opintosuoritus on täydennyskoulutuksessa suoritettu ??
other
perustutkinto-opiskelija = 0 && tuntematon
*/
const getCategory = (typeName, isBasic, hyvaksiluettu) => {
if (hyvaksiluettu) return 'transferred' // ?? unsure
if (isBasic || ['Suoritus on kotimainen harjoittelujakso', 'Tuntematon'].includes(typeName)) return 'basic'
if (typeName === 'Suoritus on avoimessa korkeakouluopetuksessa suoritettu') return 'open-uni'
if (typeName === 'Suoritus on tulevan kansainvälisen vaihto-opiskelijan suorittama') return 'incoming-exchange'
if (typeName.startsWith('Suoritus on erillisellä opiskeluoikeudella')) return 'separate'
if (typeName === 'Suoritus on korkeakoulujen välisillä yhteistyösopimuksilla opiskelevan suorittama')
return 'agreement'
return 'other'
}
const processIds = async (rawData, code, field) => {
const data = rawData.slice(0, 21628) // 11476 = KTM viimeinen
const rapoData = data
.map(row => ({
id: row[0].slice(11),
type: getCategory(row[1], row[3] === '1', row[6] === '1'),
typeCode: row[8],
isModule: row[9] !== '0',
credits: parseFloat(row[10]),
included: row[4] === '1',
}))
.filter(course => !course.isModule && (field === 'transferred' || course.included))
const rapoIds = rapoData.filter(course => course.credits > 0 && course.type === field).map(row => row.id)
const rapoCredits = rapoData.reduce(
(stats, cur) => {
stats[cur.type] += cur.credits
return stats
},
{
transferred: 0,
total: '_',
basic: 0,
'open-uni': 0,
separate: 0,
'incoming-exchange': 0,
agreement: 0,
other: 0,
}
)
const stats = await computeCreditsProduced(code, false, true, field)
if (!stats.stats.ids) {
console.log({ rapoCredits })
console.log('Edit getCreditsProduced code so that it saves a list of the relevant ids you want to compare.')
return
}
const okIdMap = stats.stats.ids
const rapoIdMap = rapoIds.reduce((obj, cur) => {
obj[cur] = true
return obj
}, {})
const onlyInRapo = Object.keys(rapoIdMap).filter(id => !okIdMap[id])
const onlyInOk = Object.keys(okIdMap).filter(id => !rapoIdMap[id])
console.log('\n onlyInRapo: ', onlyInRapo.length)
console.dir(onlyInRapo.slice(0, 30))
console.log('\n onlyInOk: ', onlyInOk.length)
console.dir(onlyInOk.slice(0, 30))
delete stats.ids
console.log(stats.stats['2022'])
console.log('rapoCredits: ', rapoCredits)
}
const testNewCalc = async (fileName, code, field) => {
await parseCsv(fileName, async data => processIds(data, code, field))
}
module.exports = { programmeCreditsDiff, testNewCalc }
-36
View File
@@ -1,36 +0,0 @@
const fs = require('fs/promises')
const Papa = require('papaparse')
const parseCsv = async (fileName, fun) => {
try {
const file = await fs.readFile(`${__dirname}/${fileName}`, 'utf8')
Papa.parsePromise = file => {
return new Promise((complete, error) => {
Papa.parse(file, { complete, error })
})
}
const data = await Papa.parsePromise(file)
await fun(data?.data)
} catch (error) {
console.log(error)
}
}
const printProgressBar = (total, current) => {
const barLength = 30
const progress = Math.floor((current / total) * barLength)
const empty = barLength - progress
const progressBar = `[${'#'.repeat(progress)}${'-'.repeat(empty)}]`
const percentage = Math.floor((current / total) * 100)
process.stdout.clearLine()
process.stdout.cursorTo(0)
process.stdout.write(`${progressBar} ${percentage} %`)
if (current >= total) {
console.log()
}
}
module.exports = { parseCsv, printProgressBar }
-31
View File
@@ -1,31 +0,0 @@
const { programmeCreditsDiff, testNewCalc } = require('./creditsDiff')
// ** To add new mode, just type key and function here **
const modes = {
// Check credits with current local oodikone data against provided data.csv in rapodiff folder.
credits: () => programmeCreditsDiff('data.csv'),
// Provide programme code as argument. Parses credits from credits.csv in rapodiff folder. This must be obtained from Rapo people. This tells which credit ids are found in oodikone but not rapo, and vice versa.
// notice: check mode requires writing ids in to the stats object in credits calculating function.
// Add something like this there:
// if (category === 'open-uni' && credits > 0 && new Date(attainmentDate).getFullYear() === 2022) stats.ids[id] = true
check: (fileName, code, field) => testNewCalc(fileName, code, field),
}
const main = async () => {
console.log('**RAPODIFF**\n')
const mode = process.argv[2]
const modeFunction = modes[mode]
const arg = process.argv[3]
const arg2 = process.argv[4]
const arg3 = process.argv[5]
if (!modeFunction) {
console.log(
'Unknown or missing mode. Supply the mode as an argument (e.g. `npm run rapodiff -- credits`). Supported modes:\n'
)
console.log(Object.keys(modes).join('\n'))
return
}
await modeFunction(arg, arg2, arg3)
}
main().then(() => process.exit(0))
+7 -15
View File
@@ -3,7 +3,7 @@ import compression from 'compression'
import cors from 'cors'
import { Express, json as jsonExpress } from 'express'
import { frontUrl, languageCenterViewEnabled, serviceProvider } from './config'
import { frontUrl, languageCenterViewEnabled } from './config'
import accessLogger from './middleware/accessLogger'
import * as auth from './middleware/auth'
import currentUserMiddleware from './middleware/currentUser'
@@ -15,7 +15,6 @@ import completedCoursesSearch from './routes/completedCoursesSearch'
import courseExclusions from './routes/courseExclusions'
import courses from './routes/courses'
import curriculumPeriods from './routes/curriculumPeriods'
import customOpenUniSearch from './routes/customOpenUniSearch'
import customPopulationSearch from './routes/customPopulationSearch'
import faculties from './routes/faculties'
import feedback from './routes/feedback'
@@ -34,8 +33,7 @@ import tags from './routes/tags'
import teachers from './routes/teachers'
import university from './routes/university'
import updater from './routes/updater'
import usersToska from './routes/users'
import usersFd from './routes/usersFd'
import users from './routes/users'
const routes = (app: Express, url: string) => {
app.use(cors({ credentials: true, origin: frontUrl }))
@@ -43,7 +41,7 @@ const routes = (app: Express, url: string) => {
app.use(compression())
app.use(headersMiddleware)
app.use(currentUserMiddleware) // eslint-disable-line @typescript-eslint/no-misused-promises
app.use(currentUserMiddleware)
app.use(accessLogger)
app.use(`${url}/login`, login)
app.use(url, courses)
@@ -53,10 +51,9 @@ const routes = (app: Express, url: string) => {
app.use(`${url}/semesters/codes`, semesters)
app.use(url, tags)
app.use(url, programmeModules)
app.use(`${url}/v3/course-exclusions`, courseExclusions)
app.use(`${url}/v2/studyprogrammes`, studyProgramme)
app.use(`${url}/course-exclusions`, courseExclusions)
app.use(`${url}/studyprogrammes`, studyProgramme)
app.use(`${url}/programme-criteria`, studyProgrammeCriteria)
app.use(`${url}/openunisearch`, auth.roles(['openUniSearch']), customOpenUniSearch)
app.use(`${url}/changelog`, changelog)
app.use(`${url}/completedcoursessearch`, completedCoursesSearch)
if (languageCenterViewEnabled) {
@@ -66,19 +63,14 @@ const routes = (app: Express, url: string) => {
app.use(`${url}/university`, university)
app.use(`${url}/updater`, auth.roles(['admin']), updater)
app.use(`${url}/teachers`, teachers)
if (serviceProvider === 'toska') {
app.use(`${url}/users`, usersToska)
} else {
app.use(`${url}/users`, usersToska)
app.use(`${url}/users`, usersFd)
}
app.use(`${url}/users`, users)
app.use(`${url}/feedback`, feedback)
app.use(`${url}/custom-population-search`, customPopulationSearch)
app.use(`${url}/studyguidancegroups`, auth.roles(['studyGuidanceGroups']), studyGuidanceGroups)
app.use(`${url}/close-to-graduation`, auth.roles(['fullSisuAccess', 'studyGuidanceGroups']), closeToGraduation)
app.use(`${url}/study-programme-pins`, studyProgrammePins)
app.use(`${url}/curriculum-periods`, curriculumPeriods)
app.get('*', (_, res) => {
app.get('', (_, res) => {
res.status(404).json({ error: 'unknown endpoint' })
})
Sentry.setupExpressErrorHandler(app)
+9 -6
View File
@@ -1,20 +1,19 @@
import axios from 'axios'
import { Request, Response, Router } from 'express'
import { Router } from 'express'
import { Release } from '@oodikone/shared/types'
import { isDev } from '../config'
import { isDev, isTest } from '../config'
const router = Router()
const changelog: { data?: Release[] } = {}
export type ChangelogResBody = Release[]
router.get<never, ChangelogResBody>('/', async (_req: Request, res: Response) => {
router.get<never, Release[]>('/', async (_, res) => {
if (changelog.data) {
return res.status(200).send(changelog.data)
}
if (isDev) {
if (isDev || isTest) {
const fakeRelease: Release[] = [
{
description: '**Feature 1**\n- Added a fancy new feature \n\n**Feature 2**\n- Fixed a bug\n- Fixed another bug',
@@ -35,8 +34,11 @@ router.get<never, ChangelogResBody>('/', async (_req: Request, res: Response) =>
version: '0.0.1',
},
]
changelog.data = fakeRelease
return res.status(200).json(fakeRelease)
}
const response = await axios.get('https://api.github.com/repos/UniversityOfHelsinkiCS/oodikone/releases')
const releasesFromAPI: Release[] = response.data.map((release: Record<string, any>) => ({
description: release.body,
@@ -44,6 +46,7 @@ router.get<never, ChangelogResBody>('/', async (_req: Request, res: Response) =>
title: release.name,
version: release.tag_name,
}))
changelog.data = releasesFromAPI
res.status(200).json(releasesFromAPI)
})
@@ -1,22 +1,24 @@
import { Router } from 'express'
import { findStudentsCloseToGraduation, getCloseToGraduationData } from '../services/populations/closeToGraduation'
import type { CloseToGraduationResBody } from '@oodikone/shared/routes/closeToGraduation'
import { getCloseToGraduationData } from '../services/populations/closeToGraduation'
import { getAllStudentsUserHasInGroups } from '../services/studyGuidanceGroups'
import { hasFullAccessToStudentData } from '../util'
const router = Router()
type CloseToGraduationResBody = Awaited<ReturnType<typeof findStudentsCloseToGraduation>>
router.get<never, CloseToGraduationResBody>('/', async (req, res) => {
const { user } = req
if (hasFullAccessToStudentData(user.roles)) {
const result = await getCloseToGraduationData()
if (!hasFullAccessToStudentData(user.roles)) {
const studentNumbers = await getAllStudentsUserHasInGroups(user.sisPersonId)
const result = await getCloseToGraduationData(studentNumbers)
return res.json(result)
}
const studentsInUsersGuidanceGroups = await getAllStudentsUserHasInGroups(user.sisPersonId)
const result = await getCloseToGraduationData(studentsInUsersGuidanceGroups)
res.json(result)
const result = await getCloseToGraduationData()
return res.json(result)
})
export default router
@@ -1,14 +1,14 @@
import { Router } from 'express'
import { difference, intersection } from 'lodash'
import { difference, intersection } from 'lodash-es'
import { CanError } from '@oodikone/shared/routes'
import { tryCatch } from '@oodikone/shared/util'
import { Courses, FormattedStudent, getCompletedCourses } from '../services/completedCoursesSearch'
import {
getOpenUniSearches,
createNewSearch,
deleteSearch,
updateSearch,
getUserSearches,
createNewUserSearch,
deleteUserSearch,
updateUserSearch,
FoundSearch,
} from '../services/openUni/openUniManageSearches'
import { hasFullAccessToStudentData, safeJSONParse } from '../util'
@@ -42,18 +42,11 @@ router.get<never, SearchResBody, SearchReqBody, SearchQuery>('/', async (req, re
return res.status(400).json({ error: 'Courses must be of type array' })
}
const answerTimeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 6000))
// Teachers can also get rights to students via Importer if
// the students have enrolled to their courses in last 8 months
// (acual logic a bit different, see Importer)
const { data: teacherRightsToStudents, error } = importerClient
? await tryCatch<{ data: string[] }>(
Promise.race([
importerClient.post('/teacher-rights/', { teacherId, studentNumbers }),
answerTimeout as Promise<never>, // This will always reject with an Error
])
)
? await tryCatch<{ data: string[] }>(importerClient.post('/teacher-rights/', { teacherId, studentNumbers }))
: { data: null, error: null }
if (error) {
@@ -79,7 +72,7 @@ export type GetSearchResBody = FoundSearch[]
router.get<never, GetSearchResBody>('/searches', async (req, res) => {
const userId = req.user.id
const foundSearches = await getOpenUniSearches(userId)
const foundSearches = await getUserSearches(userId)
return res.json(foundSearches)
})
@@ -105,7 +98,7 @@ router.post<never, CreateSearchResBody, CreateSearchReqBody>('/searches', async
if (courseCodes && !Array.isArray(courseCodes)) {
return res.status(400).json({ error: 'Courselist must be type of array' })
}
const createdSearch = await createNewSearch(userId, name, courseCodes)
const createdSearch = await createNewUserSearch(userId, name, courseCodes)
if (!createdSearch) {
return res.status(400).json({ error: 'Failed to create courselist' })
}
@@ -137,7 +130,7 @@ router.put<UpdateSearchParams, UpdateSearchResBody, UpdateSearchReqBody>('/searc
return res.status(422).end()
}
const updatedSearch = await updateSearch(userId, id, courseCodes)
const updatedSearch = await updateUserSearch(userId, id, courseCodes)
if (!updatedSearch) {
return res.status(404).json({ error: 'Courselist could not be found' })
}
@@ -162,7 +155,7 @@ router.delete<DeleteSearchParams, DeleteSearchResBody>('/searches/:id', async (r
return res.status(422).end()
}
const deletedSearch = await deleteSearch(userId, id)
const deletedSearch = await deleteUserSearch(userId, id)
if (!deletedSearch) {
return res.status(404).json({ error: 'Courselist could not be found' })
}
+19 -12
View File
@@ -1,19 +1,20 @@
import { Router } from 'express'
import type { ExcludedCoursesResBody, ExcludedCoursesReqBody } from '@oodikone/shared/routes/courseExclusions'
import { addExcludedCourses, removeExcludedCourses } from '../services/excludedCourses'
import { hasFullAccessToStudentData } from '../util'
const router = Router()
export type ExcludedCoursesParams = { code: string }
export type ExcludedCoursesResBody = void
export type ExcludedCoursesReqBody = {
courseCodes: string[]
curriculumVersion: string
}
router.post<never, ExcludedCoursesResBody, ExcludedCoursesReqBody>('/', async (req, res) => {
const { programmeCode, courseCodes, curriculumVersion } = req.body
const { roles, programmeRights } = req.user
const hasFullAccess = hasFullAccessToStudentData(roles)
const hasAccessToProgramme = programmeRights.map(({ code }) => code).includes(programmeCode)
if (!hasFullAccess && !hasAccessToProgramme) return res.status(403).end()
router.post<ExcludedCoursesParams, ExcludedCoursesResBody, ExcludedCoursesReqBody>('/:code', async (req, res) => {
const { code: programmeCode } = req.params
const { courseCodes, curriculumVersion } = req.body
const result = await addExcludedCourses(courseCodes, curriculumVersion, programmeCode)
if (!result) {
res.status(400).end()
@@ -22,9 +23,15 @@ router.post<ExcludedCoursesParams, ExcludedCoursesResBody, ExcludedCoursesReqBod
res.status(201).end()
})
router.delete<ExcludedCoursesParams, ExcludedCoursesResBody, ExcludedCoursesReqBody>('/:code', async (req, res) => {
const { code: programmeCode } = req.params
const { courseCodes, curriculumVersion } = req.body
router.delete<never, ExcludedCoursesResBody, ExcludedCoursesReqBody>('/', async (req, res) => {
const { programmeCode, courseCodes, curriculumVersion } = req.body
const { roles, programmeRights } = req.user
const hasFullAccess = hasFullAccessToStudentData(roles)
const hasAccessToProgramme = programmeRights.map(({ code }) => code).includes(programmeCode)
if (!hasFullAccess && !hasAccessToProgramme) return res.status(403).end()
const result = await removeExcludedCourses(courseCodes, curriculumVersion, programmeCode)
if (!result) {
res.status(400).end()

Some files were not shown because too many files have changed in this diff Show More