mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-13 03:13:40 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bf083cc89 | ||
|
|
c88d5984ef | ||
|
|
e328258071 | ||
|
|
37a16b54e1 | ||
|
|
4729fa457c | ||
|
|
4b37fd4536 | ||
|
|
ec5cfcc6c5 | ||
|
|
b41846a51e | ||
|
|
5b866377a9 | ||
|
|
febba2752c | ||
|
|
71f733817a | ||
|
|
82d2885d99 | ||
|
|
dad7ddc1f2 | ||
|
|
ee9c2c8eb8 | ||
|
|
6c9b11a105 | ||
|
|
afa52f7e54 | ||
|
|
f2865b9145 | ||
|
|
0514f4fdfa | ||
|
|
170a2e40e1 | ||
|
|
5849e89ac6 | ||
|
|
bc6cccfe03 | ||
|
|
53d1890a4a | ||
|
|
4a93c4dce4 | ||
|
|
be79d8bb3f | ||
|
|
97df3bee41 | ||
|
|
e1c4e792f1 | ||
|
|
230d07cbde | ||
|
|
92f848bf21 | ||
|
|
ae8aeede1b | ||
|
|
2db12dcaac | ||
|
|
9672b542b8 | ||
|
|
e6a8946b65 | ||
|
|
3b0ccb5424 | ||
|
|
0c54ef578f | ||
|
|
4ee0f74361 | ||
|
|
362850c2de | ||
|
|
42c41345a0 | ||
|
|
301208cf99 | ||
|
|
b173e27f70 | ||
|
|
1bdbec963b | ||
|
|
80cf4acf01 | ||
|
|
fdd5cbd2e5 | ||
|
|
38e398598f | ||
|
|
0b01d223ae | ||
|
|
c4fd257b42 | ||
|
|
e7ee8749c8 | ||
|
|
fb21d171ff | ||
|
|
a6905313c3 | ||
|
|
7e8e5bc2e5 | ||
|
|
c84259e67a | ||
|
|
63ccd5adc1 | ||
|
|
2d7bc42a9e | ||
|
|
0c6077d287 | ||
|
|
cf24936bc9 | ||
|
|
d0134192c6 | ||
|
|
f7609dca3d | ||
|
|
4add4409eb | ||
|
|
2b5bf09514 | ||
|
|
8ea1308c77 | ||
|
|
faba796303 | ||
|
|
04715e4503 | ||
|
|
39b77e43d7 | ||
|
|
b6a99dda83 | ||
|
|
ff25524d32 | ||
|
|
d37228e68e | ||
|
|
779c1a9627 | ||
|
|
f6d8ca87f1 | ||
|
|
1903f74764 | ||
|
|
181d39a9f9 | ||
|
|
f16b211a88 | ||
|
|
71f3abcf3e | ||
|
|
32197643ef | ||
|
|
d84a02d659 | ||
|
|
5d2b52cfaf | ||
|
|
3d09abb518 | ||
|
|
a69c0b6d00 | ||
|
|
c7185bf3d7 | ||
|
|
920895c583 | ||
|
|
f277ca758d | ||
|
|
afffd17485 | ||
|
|
3b83a59b1a | ||
|
|
f92dab550d | ||
|
|
7b8a160cc9 | ||
|
|
44f40373cc | ||
|
|
2ab3dbf861 | ||
|
|
be10cefba7 | ||
|
|
3d0b6cf864 | ||
|
|
af9ceda816 | ||
|
|
ec58964032 | ||
|
|
b3f7cccae4 | ||
|
|
73ff9f585b | ||
|
|
1c81efd4b6 | ||
|
|
bdda5dcf01 | ||
|
|
cd2cd18eea | ||
|
|
a7810cf774 | ||
|
|
b17b119a59 | ||
|
|
eb60d1bc5d | ||
|
|
3c09f2186d | ||
|
|
9ef0d8b59b | ||
|
|
9aa4150b9a | ||
|
|
0c5cac1c8c | ||
|
|
a52997fbd4 | ||
|
|
b6d68f6b3d | ||
|
|
5e750a4f40 | ||
|
|
0b81a0dbf5 | ||
|
|
12996de95a | ||
|
|
7995635034 | ||
|
|
b4e176a1a6 | ||
|
|
ce982e38d0 | ||
|
|
6c1f49838b | ||
|
|
3739a17b78 | ||
|
|
a1ecc12042 | ||
|
|
e78f2d64a4 | ||
|
|
6991713112 | ||
|
|
60fc1d135d | ||
|
|
89cc24fb81 | ||
|
|
1243e09dfe | ||
|
|
0dff5d89c0 | ||
|
|
b3cc8e3054 | ||
|
|
b89f8a33f1 | ||
|
|
5279a1f6b7 | ||
|
|
6f853d0248 | ||
|
|
bf8aec914e | ||
|
|
e190a66f6e | ||
|
|
dec58cba25 | ||
|
|
a7743cfe06 | ||
|
|
7e2f6b212f | ||
|
|
eb2665b034 | ||
|
|
e5eb58aee6 | ||
|
|
cb820fd2e9 | ||
|
|
d3bc063e39 | ||
|
|
fe25985eec | ||
|
|
361d5da6a4 | ||
|
|
7fc06a5534 | ||
|
|
98bcf78fc7 | ||
|
|
fc42613625 | ||
|
|
62419f0fe4 | ||
|
|
ad6bd96af6 | ||
|
|
cad1fe590c | ||
|
|
f911ae9d1c | ||
|
|
8c05c1c2f3 | ||
|
|
b2a92d9003 | ||
|
|
44d15b5ed8 | ||
|
|
6468aaff87 | ||
|
|
cbf8663997 | ||
|
|
388a605937 | ||
|
|
80cffc5fbf | ||
|
|
0e66802c32 | ||
|
|
49da696a0b | ||
|
|
517638a44e | ||
|
|
abcd00cbad | ||
|
|
3a697b94b1 | ||
|
|
807e5fb2f6 | ||
|
|
4e74a157d7 | ||
|
|
4ffde19997 | ||
|
|
40f3713130 | ||
|
|
81d4921bf0 | ||
|
|
5a508636a0 | ||
|
|
77b7fd960d | ||
|
|
c2a1f25d8d | ||
|
|
1ec3e78ebe | ||
|
|
27c071dca4 | ||
|
|
7432f59a81 | ||
|
|
4da174c5de | ||
|
|
78f894660b | ||
|
|
3ed1b9aaaf | ||
|
|
df6175fb75 | ||
|
|
dbf176ff2c | ||
|
|
f9398f8707 | ||
|
|
fa05ca2bf8 | ||
|
|
25dcd4f009 | ||
|
|
1ac1f01c41 | ||
|
|
85a64bdd2e | ||
|
|
911f1c6156 | ||
|
|
70f937dc74 | ||
|
|
191dd7c148 | ||
|
|
caf62699f1 | ||
|
|
344dafdc88 | ||
|
|
4b79059809 | ||
|
|
24d8a157b3 | ||
|
|
05551f1f14 | ||
|
|
d075a1c24b | ||
|
|
82a9fac4cb | ||
|
|
1cb4a94772 | ||
|
|
da84c544c4 | ||
|
|
0c1959dfb8 | ||
|
|
5240a4bbe9 | ||
|
|
5277f71dd9 | ||
|
|
a1610d15a9 | ||
|
|
6a9d92e1f3 | ||
|
|
6808bfddc7 | ||
|
|
062b81d537 | ||
|
|
48f5c58a3f | ||
|
|
943b78def4 | ||
|
|
2a5f47da36 | ||
|
|
0bdd010b2b | ||
|
|
576051dae3 | ||
|
|
fc70a24b41 | ||
|
|
84504ab0d2 | ||
|
|
f2a8672c22 | ||
|
|
65c6319e87 | ||
|
|
78e3957541 | ||
|
|
37fcd15f06 | ||
|
|
3244499fc6 | ||
|
|
07c25cb9af | ||
|
|
c9592ba954 | ||
|
|
34a479c1f6 | ||
|
|
f34cc2cd44 | ||
|
|
90beeda177 | ||
|
|
2dc74af290 | ||
|
|
f488019790 | ||
|
|
7fe7557f7c | ||
|
|
feca2bbe0a | ||
|
|
179f9737be | ||
|
|
c2653c96a7 | ||
|
|
d83a36206d | ||
|
|
6cdcbb5f5f | ||
|
|
8337e9ac94 | ||
|
|
18fd991175 | ||
|
|
cb3e953be3 | ||
|
|
d45d2c5922 | ||
|
|
3f0e1d947a | ||
|
|
55a475a322 | ||
|
|
e4c703b877 | ||
|
|
f8496edcc4 | ||
|
|
59cb4638ce | ||
|
|
8a7426ae02 | ||
|
|
3d5ced89c6 | ||
|
|
6334fe5696 | ||
|
|
cf280b0a9d | ||
|
|
7f3f4f5dfe | ||
|
|
f8e13c9fea | ||
|
|
e23b843375 | ||
|
|
922f65cf81 | ||
|
|
cee1d0f6cb | ||
|
|
0a3124e90f | ||
|
|
6065329c58 | ||
|
|
d521b8a99f | ||
|
|
bb67309a7c | ||
|
|
fe190a83ea | ||
|
|
ed0bcd7fee | ||
|
|
18df30c09a | ||
|
|
17ef03d05d | ||
|
|
2bf804d7c7 | ||
|
|
04da6d92d3 | ||
|
|
2e3d22baba | ||
|
|
758d35c298 | ||
|
|
f6323229f9 | ||
|
|
28a04770bc | ||
|
|
0a417bec49 | ||
|
|
2423bd45da | ||
|
|
0155624c86 | ||
|
|
1d310b2d9a | ||
|
|
231cf8e836 | ||
|
|
f55c30b21e | ||
|
|
e9f0490ed1 | ||
|
|
f9ab98159d | ||
|
|
ec9746097b | ||
|
|
067e47a99e | ||
|
|
2e760c7473 | ||
|
|
b3449fcf2a | ||
|
|
009f3c13ae | ||
|
|
06c31b3922 | ||
|
|
e9657fec00 | ||
|
|
64f2c8f9f6 | ||
|
|
db374b6d84 | ||
|
|
9056a91857 | ||
|
|
a8d94c3707 | ||
|
|
02243fdf4b | ||
|
|
810bf65030 | ||
|
|
207711b980 | ||
|
|
dce0748b43 | ||
|
|
bd299a0b84 | ||
|
|
454bb610af | ||
|
|
134752f94c | ||
|
|
92c01d1eaa | ||
|
|
4333d6a44f | ||
|
|
34440de0d5 | ||
|
|
b3e352d51d | ||
|
|
3ca2235ca1 | ||
|
|
453586a634 | ||
|
|
4313ba2c94 | ||
|
|
295683ced1 | ||
|
|
b05b11aa06 | ||
|
|
ab7e5d01f5 | ||
|
|
e206e2b956 | ||
|
|
af6cc538da | ||
|
|
8e83da9138 | ||
|
|
7811bdd509 | ||
|
|
63dc848857 | ||
|
|
2d6a9afc80 | ||
|
|
ffb12bc89d | ||
|
|
c08db04472 | ||
|
|
63dec8a96d | ||
|
|
92b01943a0 | ||
|
|
00196be0e7 | ||
|
|
106fa5ed0f | ||
|
|
42074567e8 | ||
|
|
eed4a316da | ||
|
|
feb80d4203 | ||
|
|
89c2e95dd0 | ||
|
|
142896bc0e | ||
|
|
a5f4ab9ad2 | ||
|
|
a99fbbb6c4 | ||
|
|
24f5018861 | ||
|
|
90f6c52469 | ||
|
|
66f603e2ca | ||
|
|
937bb70b08 | ||
|
|
ae509ae8ea | ||
|
|
8beddf655a | ||
|
|
3486112d1c | ||
|
|
7cadd05c5a | ||
|
|
3b19485d44 | ||
|
|
37c000b8dc | ||
|
|
8835d38fd3 | ||
|
|
4bc5ed6558 | ||
|
|
b96d409a00 | ||
|
|
da2e24f4e6 | ||
|
|
80ca4185ed | ||
|
|
c1982596d5 | ||
|
|
f6d3728bd1 | ||
|
|
b0590afc29 | ||
|
|
703c1be7c3 | ||
|
|
416c160667 | ||
|
|
d9e1943c51 | ||
|
|
52a8cd2e75 | ||
|
|
4d712a308c | ||
|
|
b81d179f27 | ||
|
|
083aa87d0a | ||
|
|
420db2dd88 | ||
|
|
5a03c2b426 | ||
|
|
a227d65aa4 | ||
|
|
5236638322 | ||
|
|
16fd293290 | ||
|
|
2d98a776c4 | ||
|
|
be784074ee | ||
|
|
7aedb82dcf | ||
|
|
e93031427a | ||
|
|
5f98df491d | ||
|
|
8970d62413 | ||
|
|
ba5224a3b7 | ||
|
|
934f5afa2a | ||
|
|
8f331e6078 | ||
|
|
e8b51cc5e3 | ||
|
|
1bcf1baa3e | ||
|
|
9c04ca7bf1 | ||
|
|
517e67aeab | ||
|
|
13d8db6ed2 | ||
|
|
2cf2d240f6 | ||
|
|
71c297d345 | ||
|
|
4b718b3cf6 | ||
|
|
6375c816ac | ||
|
|
df081ae3a4 | ||
|
|
dba194c52e | ||
|
|
493f56c28a | ||
|
|
668c37c00c | ||
|
|
7897ef596a | ||
|
|
09b8f75a59 | ||
|
|
ef4f1518cb | ||
|
|
71a3686846 | ||
|
|
c761949d68 | ||
|
|
b9bee01681 | ||
|
|
586985c45f | ||
|
|
da4e495a33 | ||
|
|
88652020ca | ||
|
|
5bd9e7ff9f | ||
|
|
c87ea60b37 | ||
|
|
60f5c23de4 | ||
|
|
57083d03fa | ||
|
|
18f5666daf | ||
|
|
43e746de6f | ||
|
|
3cc0f89dbd | ||
|
|
a9013204d6 | ||
|
|
eb1a124292 | ||
|
|
4db0ef4897 | ||
|
|
21cbfb18d4 | ||
|
|
cfe27ed599 | ||
|
|
7dafd5d9c2 | ||
|
|
76c037dd72 | ||
|
|
053a20f285 | ||
|
|
ae33741452 | ||
|
|
5c55b58278 | ||
|
|
a692a2dc66 | ||
|
|
4e97210422 | ||
|
|
4816db4791 | ||
|
|
66af7cfe07 | ||
|
|
56f3eb98a4 | ||
|
|
3ebff5d4f9 | ||
|
|
f842364c1f | ||
|
|
3d5bc801ca | ||
|
|
d04476d874 | ||
|
|
34444db544 | ||
|
|
1c6e87ac5f | ||
|
|
a7707358d6 | ||
|
|
949b85776a | ||
|
|
41551182a4 | ||
|
|
447142c7fc | ||
|
|
085e102bd6 | ||
|
|
4de0da3b7a | ||
|
|
c0fb7208d8 | ||
|
|
47cb76d78b | ||
|
|
1d03e59691 | ||
|
|
07d1db6d36 | ||
|
|
a914b46c1c | ||
|
|
52cca4dd01 | ||
|
|
2360d05f25 | ||
|
|
f5829c0c18 | ||
|
|
587ec153eb | ||
|
|
eebabf0ba1 | ||
|
|
fc93028e57 | ||
|
|
42bbce2f57 | ||
|
|
86430e12e1 | ||
|
|
c4daf60e8c | ||
|
|
66341a1bc4 | ||
|
|
2ae0d23271 | ||
|
|
029491fb84 | ||
|
|
9a91b5fba7 | ||
|
|
b16ad3d28b | ||
|
|
2c7d9bce74 | ||
|
|
2f7343bdc0 | ||
|
|
7e474b7599 | ||
|
|
03d9ccb8aa | ||
|
|
7569cd91be | ||
|
|
83095bb9cc | ||
|
|
53a9d538da | ||
|
|
0f6c14b07c | ||
|
|
f56d2deb84 | ||
|
|
b094834448 | ||
|
|
5662c6f9f6 | ||
|
|
a793b7a35d | ||
|
|
a590b93130 | ||
|
|
04225056ff | ||
|
|
7967d10333 | ||
|
|
2d82c69ee3 | ||
|
|
475b78e36e | ||
|
|
57247069a2 | ||
|
|
67b9dea0fa | ||
|
|
2b462ca3b1 | ||
|
|
a257bbdca3 | ||
|
|
6c76adfa30 | ||
|
|
766e705a34 | ||
|
|
f08e27eeb2 | ||
|
|
cf1b30cc4c | ||
|
|
656f199b1b | ||
|
|
f7e4575ced | ||
|
|
ad3ad11e7f | ||
|
|
7b745b0853 | ||
|
|
8bab98eb32 | ||
|
|
4f7a272f9b | ||
|
|
11b68b7e25 | ||
|
|
8c08204fe7 | ||
|
|
39e9dc8985 | ||
|
|
0a63bfd7b1 | ||
|
|
1dbe571bc8 | ||
|
|
4afa5707fb | ||
|
|
fb9e45f63d | ||
|
|
ece3a12e71 | ||
|
|
f5b4477f2e | ||
|
|
7df3b06e7b | ||
|
|
f7d51b0dd9 | ||
|
|
f7ddd23200 | ||
|
|
899709bc9e | ||
|
|
271e0c70f0 | ||
|
|
0b5eb3cbdf | ||
|
|
121ab7e9de | ||
|
|
56a38e342e | ||
|
|
de912d2557 | ||
|
|
b194689379 | ||
|
|
e4f4e47f87 | ||
|
|
fc6af9432a | ||
|
|
545a3f9f8c | ||
|
|
90d6b63892 | ||
|
|
12f35ae9e0 | ||
|
|
61628c7601 | ||
|
|
a9a61139db | ||
|
|
674fe0cc40 | ||
|
|
ad4893f05b | ||
|
|
a498d72e2f | ||
|
|
9932b77d35 | ||
|
|
2fe2561d69 | ||
|
|
254ccefb9c | ||
|
|
ccd21629b8 | ||
|
|
8488453cbb | ||
|
|
830647d988 | ||
|
|
1cd5bb66d9 | ||
|
|
4158b75375 | ||
|
|
84acff6e58 | ||
|
|
d7aed1f9e2 | ||
|
|
371443f737 | ||
|
|
3782347124 | ||
|
|
fa67d645b4 | ||
|
|
d31a24d6a8 | ||
|
|
5d7550435e | ||
|
|
d07c10f184 | ||
|
|
49054b5eeb | ||
|
|
dfdb233700 | ||
|
|
4252230c81 | ||
|
|
7984cefeb0 | ||
|
|
c9373a6e02 | ||
|
|
1d510951e5 | ||
|
|
917475b6ae | ||
|
|
e1f863ff77 | ||
|
|
d3a3c0ff5c | ||
|
|
0a67aaae29 | ||
|
|
3d5c4ccaec | ||
|
|
f29cd0f1e4 | ||
|
|
487a0c3c81 | ||
|
|
2ae850d44b | ||
|
|
bbc9cd2c78 | ||
|
|
f3560e68da | ||
|
|
d3343debec | ||
|
|
e49644ca50 | ||
|
|
08b3e9074a | ||
|
|
36b8aaa681 | ||
|
|
2d34d13bce | ||
|
|
005ea1eb68 | ||
|
|
c5dd481f4c | ||
|
|
429b2ae7f8 | ||
|
|
9266ad5dd0 | ||
|
|
105b171bb1 | ||
|
|
0c5d5a3efa | ||
|
|
21d4bfc0e8 | ||
|
|
705cc9c10a | ||
|
|
304dd24bdb | ||
|
|
963bb272b6 | ||
|
|
966cc0aaa8 | ||
|
|
157cef1643 | ||
|
|
1c1ae58e20 | ||
|
|
426965473f | ||
|
|
f850ca8d12 | ||
|
|
f0d55d5d7f | ||
|
|
5ee1b6dce0 | ||
|
|
4facb54a97 | ||
|
|
1e7f980661 | ||
|
|
2515411c70 | ||
|
|
e8ce4c703c | ||
|
|
9661807e60 | ||
|
|
b80ef2316f | ||
|
|
3d008c7fa7 | ||
|
|
1e7217197c | ||
|
|
4f98bd61b8 | ||
|
|
3e50a40f6a | ||
|
|
a2c37d38eb | ||
|
|
d35e303657 | ||
|
|
620bfcb81d | ||
|
|
56feb9518b | ||
|
|
2d866b5c53 | ||
|
|
7bfd075bf7 | ||
|
|
32a76d4ce5 | ||
|
|
c631a0d615 | ||
|
|
3d09f5ce9e | ||
|
|
e16792d0d6 | ||
|
|
9aac9d789b | ||
|
|
f4f2649d74 | ||
|
|
892ab49eba | ||
|
|
ff2f4badd0 | ||
|
|
44478234d7 | ||
|
|
9f8ea752b7 | ||
|
|
c97ebf51e2 | ||
|
|
591444c099 | ||
|
|
204703d873 | ||
|
|
c4a9f49c47 | ||
|
|
54728c373c | ||
|
|
dccef76d5a | ||
|
|
cd0ef266e8 | ||
|
|
5f9a8312a2 | ||
|
|
80e908d9a8 | ||
|
|
e6a216f872 | ||
|
|
edacce89e2 | ||
|
|
528def2260 | ||
|
|
ad9ececc57 | ||
|
|
6f7172c7d3 | ||
|
|
65a15f5de2 | ||
|
|
364ec8b9fd | ||
|
|
0ef8efeca3 | ||
|
|
b49b1feff4 | ||
|
|
c44c146692 | ||
|
|
c526629b11 | ||
|
|
f085593f55 | ||
|
|
c24766f896 | ||
|
|
45acf19cc9 | ||
|
|
9847ba9466 | ||
|
|
12bd60d70f | ||
|
|
b03c5a11d6 | ||
|
|
120808c00d | ||
|
|
19b80a5cee | ||
|
|
143458f867 | ||
|
|
289e0cd856 | ||
|
|
f069d65ea0 | ||
|
|
16b876fa5a | ||
|
|
9736a5c12d | ||
|
|
01a4dcf5f6 | ||
|
|
2918cfc44b | ||
|
|
0e8c5ef7a7 | ||
|
|
066a212261 | ||
|
|
9c9f0dfadd | ||
|
|
b349237343 | ||
|
|
02b3315991 | ||
|
|
2d3372d33f | ||
|
|
8b8f80b965 | ||
|
|
40bf945553 | ||
|
|
5c0932aef5 | ||
|
|
493df5c733 | ||
|
|
761d0ae11d | ||
|
|
597acb83d9 | ||
|
|
9fddb500fc | ||
|
|
6dce3e500d | ||
|
|
8819cc8382 | ||
|
|
69e580f3ce | ||
|
|
cacc275235 | ||
|
|
9dfe65d1c8 | ||
|
|
92c89f4279 | ||
|
|
97b5d5f32f | ||
|
|
9c3db7243e | ||
|
|
b53b4548e7 | ||
|
|
0d86be4546 | ||
|
|
1795c688ea | ||
|
|
174858959f | ||
|
|
9bd17a3f26 | ||
|
|
29df71ad6b | ||
|
|
296459d16e | ||
|
|
d412b2589a | ||
|
|
9a00ae5ef9 | ||
|
|
d2dfb21f65 | ||
|
|
a18ecc11fe | ||
|
|
d463d25c4f | ||
|
|
2fddd0769a | ||
|
|
cd0516b7fe | ||
|
|
878004ae77 | ||
|
|
44aabde06c | ||
|
|
d277f78d3b | ||
|
|
63affe01b0 | ||
|
|
31e561230b | ||
|
|
86ec37dce3 | ||
|
|
24160474b0 | ||
|
|
da0039cb01 | ||
|
|
10ee08f9fb | ||
|
|
bac8f6bd15 | ||
|
|
b0dc3b7540 | ||
|
|
bf0e7bb050 | ||
|
|
d4a0e44ebd | ||
|
|
1b9afef816 | ||
|
|
804fe9fcdb | ||
|
|
95fbe7d875 | ||
|
|
3e9fa504fa | ||
|
|
f7c9c2e9ce | ||
|
|
fc396aae57 | ||
|
|
a03fe5ee7c | ||
|
|
ea772525d0 | ||
|
|
f615214ca8 | ||
|
|
78071784d2 | ||
|
|
8496b1c660 | ||
|
|
71feb64add | ||
|
|
aed6e2265b | ||
|
|
c49925bc6c | ||
|
|
45d4dbb33e | ||
|
|
24093efca8 | ||
|
|
59c54230d6 | ||
|
|
5dd67c79b0 | ||
|
|
3b94afe6ea | ||
|
|
6695f7620b | ||
|
|
dc0629741d | ||
|
|
9405bcefac | ||
|
|
f80a92db18 | ||
|
|
3648d5d5cf | ||
|
|
fe9888dca1 | ||
|
|
8bd3274fa8 | ||
|
|
b0e696d187 | ||
|
|
40b9df5dba | ||
|
|
185e356373 | ||
|
|
8073049c78 | ||
|
|
ca49f241e4 | ||
|
|
366958683e | ||
|
|
1c7b04633c | ||
|
|
bf7c521173 | ||
|
|
1fda343551 | ||
|
|
6f87bc8989 | ||
|
|
882812c8b9 | ||
|
|
b3ea850b6d | ||
|
|
f4e32e2186 | ||
|
|
ae4133e97c | ||
|
|
fbfcebf74a | ||
|
|
5def6898ce | ||
|
|
95abbebe70 | ||
|
|
ecd1b2d42a | ||
|
|
a36eb4e010 | ||
|
|
0c2ea7c42d | ||
|
|
d4e786094f | ||
|
|
055bd7e00c | ||
|
|
ed621834c2 | ||
|
|
2cbbf3330d | ||
|
|
16e6a07143 | ||
|
|
7b0b200775 | ||
|
|
4d9c4d66da | ||
|
|
9106817d8f | ||
|
|
3547932508 | ||
|
|
c1015119ec | ||
|
|
8cdf83772e | ||
|
|
d033b40ac4 | ||
|
|
3d904185c0 | ||
|
|
401e3e7c27 | ||
|
|
b78a29c26f | ||
|
|
2a7307320a | ||
|
|
808923023f | ||
|
|
ff6999f0b3 | ||
|
|
8dd55fa03e | ||
|
|
7e698f62a0 | ||
|
|
778cf988e5 | ||
|
|
111b11179d | ||
|
|
0bb12b667b | ||
|
|
660f797aca | ||
|
|
abc9035e84 | ||
|
|
a3797c5605 | ||
|
|
9e35cee053 | ||
|
|
9075e0dd95 | ||
|
|
7aaff17d0d | ||
|
|
3321327df2 | ||
|
|
73b5ca1477 | ||
|
|
a9026b1085 | ||
|
|
66ddd4c6b3 | ||
|
|
938ccec288 | ||
|
|
10c8a61c04 | ||
|
|
5a9c62c8f4 | ||
|
|
63c7b7fff2 | ||
|
|
29d28a484c | ||
|
|
d165c313d1 | ||
|
|
cf328a7714 | ||
|
|
557094a53f | ||
|
|
4bbc0a40a3 | ||
|
|
acd308fbae | ||
|
|
36236766e1 | ||
|
|
c98d575b63 | ||
|
|
112a52b8ec | ||
|
|
4c82c79f5b | ||
|
|
dadf5d05cd | ||
|
|
bfddc55bf4 | ||
|
|
6a41185e49 | ||
|
|
41eb4068b0 | ||
|
|
267d61bd76 | ||
|
|
37a0584b4f | ||
|
|
aafdcbd8f5 | ||
|
|
6644c7c0be | ||
|
|
92554d2b2a | ||
|
|
a36ffefdee | ||
|
|
5144ded031 | ||
|
|
b04c4db365 | ||
|
|
4759fbc42b | ||
|
|
b9e95f7405 | ||
|
|
8932d7938a | ||
|
|
4c686f6a58 | ||
|
|
1f4990f160 | ||
|
|
862931292b | ||
|
|
383bd22810 | ||
|
|
9839ff3c8a | ||
|
|
c66fd1d504 | ||
|
|
fd8eee5b9a | ||
|
|
032b1f3fb9 | ||
|
|
ffde4616dc | ||
|
|
b01b157cad | ||
|
|
676d9dd01b | ||
|
|
51d1f6baa5 | ||
|
|
e320c2b27c | ||
|
|
5b426e3a5a | ||
|
|
d7af692cde | ||
|
|
ce85a67a66 | ||
|
|
b5f8e44a50 | ||
|
|
82f73ca67b | ||
|
|
2449c81f4b | ||
|
|
f7b0cc2940 | ||
|
|
5e09869c74 | ||
|
|
91d659e810 | ||
|
|
e592fa3c5e | ||
|
|
ea56bd2fed | ||
|
|
4588788d75 | ||
|
|
0bcfb406c6 | ||
|
|
1ee0541299 | ||
|
|
22ce1cd6f0 | ||
|
|
875d72b5ad | ||
|
|
599e316884 | ||
|
|
f60aef096d | ||
|
|
f429d94656 | ||
|
|
067e83708d | ||
|
|
7f7a572751 | ||
|
|
e9ff756610 | ||
|
|
3e11475c0d | ||
|
|
24579c20c7 | ||
|
|
0966a04933 | ||
|
|
a7235940a0 | ||
|
|
40b27cb40a | ||
|
|
6346966604 | ||
|
|
d32f8e40ea | ||
|
|
f9875b84ca | ||
|
|
19239ba6e4 | ||
|
|
c5a6d2bd0b | ||
|
|
904d127a60 | ||
|
|
144a095baa | ||
|
|
9aa1bdf383 | ||
|
|
b03243104c | ||
|
|
afa6639987 | ||
|
|
2f5716c1be | ||
|
|
17aca47378 | ||
|
|
5f794339e0 | ||
|
|
7b34d09452 | ||
|
|
379f918d6e | ||
|
|
f079565232 | ||
|
|
4ac083e280 | ||
|
|
482d44b276 | ||
|
|
8dc553fd08 | ||
|
|
16bff7b28a | ||
|
|
fcc9881dd7 | ||
|
|
8e20da6f81 | ||
|
|
b3c51cd8c5 | ||
|
|
58b2b4092b | ||
|
|
58df06b5c8 | ||
|
|
eb24ec1159 | ||
|
|
dc86a75fc1 | ||
|
|
951bb6b002 |
+4
-109
@@ -1,111 +1,6 @@
|
||||
// ! If we don't have this file, running ESLint will fail because of the import/no-unused-modules rule
|
||||
// ! See: https://github.com/import-js/eslint-plugin-import/issues/3079
|
||||
// ! When this issue is resolved, this file can be removed
|
||||
{
|
||||
"env": {
|
||||
"commonjs": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "commonjs"
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"airbnb",
|
||||
"plugin:cypress/recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.ts"],
|
||||
"env": {
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"airbnb-typescript",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021,
|
||||
"sourceType": "module",
|
||||
"project": true
|
||||
},
|
||||
"rules": {
|
||||
"import/prefer-default-export": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/return-await": "off",
|
||||
"@typescript-eslint/no-shadow": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"camelcase": "off",
|
||||
"consistent-return": "off",
|
||||
"cypress/no-assigning-return-values": "off",
|
||||
"cypress/unsafe-to-chain-command": "off",
|
||||
"default-param-last": "off",
|
||||
"import/extensions": ["error", "never"],
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"alphabetize": { "order": "asc", "caseInsensitive": true },
|
||||
"groups": [["builtin", "external"], ["internal"], ["parent"], ["sibling", "index"]],
|
||||
"pathGroups": [
|
||||
{
|
||||
"pattern": "../../../**",
|
||||
"group": "internal",
|
||||
"position": "before"
|
||||
},
|
||||
{
|
||||
"pattern": "../../**",
|
||||
"group": "internal",
|
||||
"position": "after"
|
||||
},
|
||||
{
|
||||
"pattern": "../**",
|
||||
"group": "parent",
|
||||
"position": "before"
|
||||
},
|
||||
{
|
||||
"pattern": "./**",
|
||||
"group": "sibling",
|
||||
"position": "before"
|
||||
}
|
||||
],
|
||||
"pathGroupsExcludedImportTypes": ["builtin", "external"]
|
||||
}
|
||||
],
|
||||
"no-async-promise-executor": "off",
|
||||
"no-await-in-loop": "off",
|
||||
"no-console": "error",
|
||||
"no-continue": "off",
|
||||
"no-param-reassign": "off",
|
||||
"no-plusplus": "off",
|
||||
"no-promise-executor-return": "off",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
"selector": "ForInStatement",
|
||||
"message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array."
|
||||
},
|
||||
"LabeledStatement",
|
||||
"WithStatement"
|
||||
],
|
||||
"no-return-await": "off",
|
||||
"no-shadow": "off",
|
||||
"prettier/prettier": "error",
|
||||
"quotes": ["error", "single", { "avoidEscape": true }]
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [".js", ".jsx", ".ts", ".tsx"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"ignorePatterns": ["build/", "dist/", "instrumented/", "node_modules/", "**/*migrations*/**"]
|
||||
"ignorePatterns": ["**/node_modules/", "**/dist/", "**/build/"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: 'Lint code'
|
||||
description: 'Runs ESLint and Stylelint for frontend and backend'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: 'Install dependencies'
|
||||
shell: bash
|
||||
run: npm ci && cd services/frontend && npm ci && cd ../backend && npm ci
|
||||
- name: 'Create symlink for shared'
|
||||
shell: bash
|
||||
run: ln -s $PWD/services/shared $PWD/services/backend/src/shared && ln -s $PWD/services/shared $PWD/services/frontend/src/shared
|
||||
- name: 'Run Stylelint for CSS files'
|
||||
shell: bash
|
||||
run: npm run stylelint "services/frontend/src/**/*.css"
|
||||
- name: 'Run ESLint for JS and TS files'
|
||||
shell: bash
|
||||
run: npm run eslint -- services
|
||||
@@ -11,6 +11,9 @@ inputs:
|
||||
spec:
|
||||
required: true
|
||||
description: 'Path to Cypress file'
|
||||
test_name:
|
||||
required: true
|
||||
description: 'Name of the test'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
@@ -35,10 +38,10 @@ runs:
|
||||
CYPRESS_baseUrl: http://localhost:1337
|
||||
|
||||
- name: 'Upload video if tests fail'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: Cypress videos
|
||||
name: 'Cypress videos - ${{ inputs.test_name }}'
|
||||
path: ./cypress/videos/*
|
||||
|
||||
- name: 'Print logs on failure'
|
||||
|
||||
@@ -26,14 +26,14 @@ jobs:
|
||||
with:
|
||||
image: oodikone-frontend
|
||||
tags: production ${{ github.sha }}
|
||||
context: services/frontend
|
||||
context: services
|
||||
containerfiles: |
|
||||
services/frontend/Dockerfile
|
||||
build-args: |
|
||||
BUILT_AT=${{ steps.get_current_time.outputs.NOW }}
|
||||
SENTRY_RELEASE=${{ steps.extract_version.outputs.VERSION }}
|
||||
SENTRY_ENVIRONMENT=production
|
||||
SENTRY_DSN=${{ secrets.OODIKONE_SENTRY_DSN }}
|
||||
SENTRY_DSN=${{ secrets.OODIKONE_FRONTEND_SENTRY_DSN }}
|
||||
|
||||
- name: Push to quay.io
|
||||
uses: redhat-actions/push-to-registry@v2
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
with:
|
||||
image: oodikone-backend
|
||||
tags: production ${{ github.sha }}
|
||||
context: services/backend
|
||||
context: services
|
||||
containerfiles: |
|
||||
services/backend/Dockerfile
|
||||
build-args: |
|
||||
@@ -77,8 +77,8 @@ jobs:
|
||||
username: toska+github
|
||||
password: ${{ secrets.QUAY_IO_TOKEN }}
|
||||
|
||||
sentry_release:
|
||||
name: 'Create Sentry release'
|
||||
sentry_release_frontend:
|
||||
name: 'Create Sentry release for frontend'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- release_frontend
|
||||
@@ -94,12 +94,40 @@ jobs:
|
||||
- name: Pull frontend image and create a container
|
||||
run: docker create --name frontend quay.io/toska/oodikone-frontend:production
|
||||
|
||||
- name: Pull backend image and create a container
|
||||
run: docker create --name backend quay.io/toska/oodikone-backend:production
|
||||
|
||||
- name: Copy assets from the frontend container
|
||||
run: docker cp frontend:/opt/app-root/src/build ./build
|
||||
|
||||
- name: Create Sentry release
|
||||
uses: getsentry/action-release@v1
|
||||
env:
|
||||
SENTRY_URL: https://toska.cs.helsinki.fi/
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
SENTRY_ORG: toska
|
||||
SENTRY_PROJECT: oodikone-frontend
|
||||
with:
|
||||
environment: production
|
||||
set_commits: 'skip'
|
||||
sourcemaps: './build/assets'
|
||||
url_prefix: '~/assets'
|
||||
version: ${{ steps.extract_version.outputs.VERSION }}
|
||||
|
||||
sentry_release_backend:
|
||||
name: 'Create Sentry release for backend'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- release_frontend
|
||||
- release_backend
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Extract release version
|
||||
id: extract_version
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Pull backend image and create a container
|
||||
run: docker create --name backend quay.io/toska/oodikone-backend:production
|
||||
|
||||
- name: Copy assets from the backend container
|
||||
run: docker cp backend:/opt/app-root/src/dist ./dist
|
||||
|
||||
@@ -109,27 +137,28 @@ jobs:
|
||||
SENTRY_URL: https://toska.cs.helsinki.fi/
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
SENTRY_ORG: toska
|
||||
SENTRY_PROJECT: oodikone
|
||||
SENTRY_PROJECT: oodikone-backend
|
||||
with:
|
||||
environment: production
|
||||
set_commits: 'skip'
|
||||
sourcemaps: './build/assets ./dist'
|
||||
url_prefix: '~/assets'
|
||||
sourcemaps: './dist'
|
||||
url_prefix: '~/opt/app-root/src/dist/'
|
||||
version: ${{ steps.extract_version.outputs.VERSION }}
|
||||
|
||||
report_success:
|
||||
name: 'Report deployment success to oodikone notifications'
|
||||
name: 'Report deployment success to the "oodikone_notifications" channel'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- release_frontend
|
||||
- release_backend
|
||||
- sentry_release
|
||||
- sentry_release_frontend
|
||||
- sentry_release_backend
|
||||
|
||||
if: success()
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: 'Send notification to slack'
|
||||
- name: 'Send notification to Slack'
|
||||
uses: UniversityOfHelsinkiCS/slackbot-action@v1.5
|
||||
with:
|
||||
webhook-url: ${{ secrets.WEBHOOK_URL }}
|
||||
@@ -138,18 +167,19 @@ jobs:
|
||||
deployment-target: production
|
||||
|
||||
report_failure:
|
||||
name: 'Report deployment failure to oodikone notifications'
|
||||
name: 'Report deployment failure to the "oodikone_notifications" channel'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- release_frontend
|
||||
- release_backend
|
||||
- sentry_release
|
||||
- sentry_release_frontend
|
||||
- sentry_release_backend
|
||||
|
||||
if: failure()
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: 'Send notification to slack'
|
||||
- name: 'Send notification to Slack'
|
||||
uses: UniversityOfHelsinkiCS/slackbot-action@v1.5
|
||||
with:
|
||||
webhook-url: ${{ secrets.WEBHOOK_URL }}
|
||||
|
||||
@@ -18,15 +18,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
- name: 'Install dependencies'
|
||||
run: npm ci && cd services/frontend && npm ci && cd ../backend && npm ci
|
||||
- name: 'Run Stylelint for CSS files'
|
||||
run: npm run stylelint "services/frontend/src/**/*.css"
|
||||
- name: 'Run ESLint for JS files'
|
||||
run: npm run eslint -- --ext .jsx,.js services --report-unused-disable-directives
|
||||
- name: Lint backend and frontend
|
||||
uses: ./.github/actions/lint
|
||||
|
||||
test_end_to_end:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -62,6 +55,10 @@ jobs:
|
||||
spec: cypress/e2e/Language_center.js
|
||||
- name: 'Test study guidance groups'
|
||||
spec: cypress/e2e/Study_guidance_groups.js
|
||||
- name: 'Test evaluation overview'
|
||||
spec: cypress/e2e/Evaluation_overview.js
|
||||
- name: 'Test university'
|
||||
spec: cypress/e2e/University.js
|
||||
name: ${{ matrix.name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -71,3 +68,4 @@ jobs:
|
||||
username: ${{ secrets.TOSKAREGISTRY_USERNAME }}
|
||||
password: ${{ secrets.TOSKAREGISTRY_PASSWORD }}
|
||||
spec: ${{ matrix.spec }}
|
||||
test_name: ${{ matrix.name }}
|
||||
|
||||
@@ -5,16 +5,16 @@ on:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/test.yaml'
|
||||
- '.github/workflows/staging.yaml'
|
||||
- '.github/actions/**'
|
||||
- '.github/workflows/staging.yaml'
|
||||
- '.github/workflows/test.yaml'
|
||||
- 'cypress/**'
|
||||
- 'cypress.config.json'
|
||||
- 'services/**'
|
||||
- '!services/backend/src/rapodiff/**'
|
||||
- 'updater/sis-updater-worker/src/db/migrations/**'
|
||||
- 'docker-compose.ci.yml'
|
||||
- 'package*.json'
|
||||
- 'updater/sis-updater-worker/src/db/migrations/**'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
with:
|
||||
image: oodikone-frontend
|
||||
tags: staging ${{ github.sha }}
|
||||
context: services/frontend
|
||||
context: services
|
||||
containerfiles: |
|
||||
services/frontend/Dockerfile
|
||||
build-args: |
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
with:
|
||||
image: oodikone-backend
|
||||
tags: staging ${{ github.sha }}
|
||||
context: services/backend
|
||||
context: services
|
||||
containerfiles: |
|
||||
services/backend/Dockerfile
|
||||
build-args: |
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
password: ${{ secrets.QUAY_IO_TOKEN }}
|
||||
|
||||
report_success:
|
||||
name: 'Report deployment success to oodikone notifications'
|
||||
name: 'Report deployment success to the "oodikone_notifications" channel'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- release_frontend
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: 'Send notification to slack'
|
||||
- name: 'Send notification to Slack'
|
||||
uses: UniversityOfHelsinkiCS/slackbot-action@v1.5
|
||||
with:
|
||||
webhook-url: ${{ secrets.WEBHOOK_URL }}
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
deployment-target: staging
|
||||
|
||||
report_failure:
|
||||
name: 'Report deployment failure to oodikone notifications'
|
||||
name: 'Report deployment failure to the "oodikone_notifications" channel'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- release_frontend
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: 'Send notification to slack'
|
||||
- name: 'Send notification to Slack'
|
||||
uses: UniversityOfHelsinkiCS/slackbot-action@v1.5
|
||||
with:
|
||||
webhook-url: ${{ secrets.WEBHOOK_URL }}
|
||||
|
||||
+78
-36
@@ -17,15 +17,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
- name: 'Install dependencies'
|
||||
run: npm ci && cd services/frontend && npm ci && cd ../backend && npm ci
|
||||
- name: 'Run Stylelint for CSS files'
|
||||
run: npm run stylelint "services/frontend/src/**/*.css"
|
||||
- name: 'Run ESLint for JS and TS files'
|
||||
run: npm run eslint -- --ext .js,.jsx,.ts,.tsx services --report-unused-disable-directives
|
||||
- name: Lint backend and frontend
|
||||
uses: ./.github/actions/lint
|
||||
|
||||
# === Build database images ===
|
||||
build_backend:
|
||||
@@ -35,32 +28,77 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: 'Build image and export it'
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
build-args: |
|
||||
SENTRY_RELEASE=${{ github.sha }}
|
||||
SENTRY_ENVIRONMENT=staging
|
||||
context: services/backend
|
||||
context: services
|
||||
file: services/backend/Dockerfile
|
||||
push: false
|
||||
tags: oodikone-backend:staging
|
||||
outputs: type=docker,dest=/tmp/backend.tar
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
- name: 'Upload image for other jobs'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: backend-image
|
||||
path: /tmp/backend.tar
|
||||
|
||||
build_and_push_anon_sis_db:
|
||||
name: 'Build anon sis-db and push to toska hub if new migrations have been added'
|
||||
build_frontend:
|
||||
name: 'Build frontend'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
- 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
|
||||
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
|
||||
|
||||
check_migration_files:
|
||||
name: Check if new migrations have been added
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
kone_db_migrations: ${{ steps.check_migrations.outputs.kone_db_migrations }}
|
||||
user_db_migrations: ${{ steps.check_migrations.outputs.user_db_migrations }}
|
||||
sis_db_migrations: ${{ steps.check_migrations.outputs.sis_db_migrations }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check if new migrations have been added
|
||||
id: check_migrations
|
||||
uses: dorny/paths-filter@v3
|
||||
with:
|
||||
filters: |
|
||||
migrations:
|
||||
kone_db_migrations:
|
||||
- 'services/backend/src/database/migrations_kone/**'
|
||||
- '.github/workflows/test.yaml'
|
||||
user_db_migrations:
|
||||
- 'services/backend/src/database/migrations_user/**'
|
||||
- '.github/workflows/test.yaml'
|
||||
sis_db_migrations:
|
||||
- 'updater/sis-updater-worker/src/db/migrations/**'
|
||||
- '.github/workflows/test.yaml'
|
||||
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.check_migration_files.outputs.sis_db_migrations == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -68,7 +106,6 @@ jobs:
|
||||
username: ${{ secrets.username }}
|
||||
password: ${{ secrets.password }}
|
||||
- name: 'Let sis-updater-worker run migrations, then push new version of image to toska hub'
|
||||
if: steps.check_migrations.outputs.migrations == 'true'
|
||||
run: |
|
||||
docker compose --file docker-compose.ci.yml up --detach sis-updater-worker
|
||||
sleep 30 # wait for sis-updater-worker to start and run migrations
|
||||
@@ -78,22 +115,18 @@ jobs:
|
||||
name: 'Build anon kone-db and push to toska hub if new migrations have been added'
|
||||
needs:
|
||||
- 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@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: check_migrations
|
||||
- name: 'Download backend image'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
filters: |
|
||||
migrations:
|
||||
- 'services/backend/src/database/migrations_kone/**'
|
||||
- '.github/workflows/test.yaml'
|
||||
- name: 'Download images'
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: /tmp/
|
||||
name: backend-image
|
||||
- name: 'Load built backend image'
|
||||
run: docker load --input /tmp/artifact/backend.tar
|
||||
run: docker load --input backend.tar
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -101,17 +134,19 @@ jobs:
|
||||
username: ${{ secrets.username }}
|
||||
password: ${{ secrets.password }}
|
||||
- name: 'Let backend run migrations, then push new version of image to toska hub'
|
||||
if: steps.check_migrations.outputs.migrations == 'true'
|
||||
run: |
|
||||
docker compose --file docker-compose.ci.yml up --detach backend
|
||||
sleep 30 # wait for sis-updater-worker to start and run migrations
|
||||
sleep 30 # wait for backend to start and run migrations
|
||||
./anonyymioodi/commit_and_push_to_toska_hub.sh kone-db
|
||||
|
||||
build_and_push_anon_user_db:
|
||||
name: 'Build anon user-db and push to toska hub if new migrations have been added'
|
||||
needs:
|
||||
- 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@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
@@ -121,12 +156,12 @@ jobs:
|
||||
migrations:
|
||||
- 'services/backend/src/database/migrations_user/**'
|
||||
- '.github/workflows/test.yaml'
|
||||
- name: 'Download images'
|
||||
uses: actions/download-artifact@v3
|
||||
- name: 'Download backend image'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/
|
||||
name: backend-image
|
||||
- name: 'Load built backend image'
|
||||
run: docker load --input /tmp/artifact/backend.tar
|
||||
run: docker load --input backend.tar
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -134,10 +169,9 @@ jobs:
|
||||
username: ${{ secrets.username }}
|
||||
password: ${{ secrets.password }}
|
||||
- name: 'Let backend run migrations, then push new version of image to toska hub'
|
||||
if: steps.check_migrations.outputs.migrations == 'true'
|
||||
run: |
|
||||
docker compose --file docker-compose.ci.yml up --detach backend
|
||||
sleep 30 # wait for sis-updater-worker to start and run migrations
|
||||
sleep 30 # wait for backend to start and run migrations
|
||||
./anonyymioodi/commit_and_push_to_toska_hub.sh user-db
|
||||
|
||||
# === End-to-end tests ===
|
||||
@@ -147,6 +181,8 @@ jobs:
|
||||
- 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()
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -177,6 +213,10 @@ jobs:
|
||||
spec: cypress/e2e/Language_center.js
|
||||
- name: 'Test study guidance groups'
|
||||
spec: cypress/e2e/Study_guidance_groups.js
|
||||
- name: 'Test evaluation overview'
|
||||
spec: cypress/e2e/Evaluation_overview.js
|
||||
- name: 'Test university'
|
||||
spec: cypress/e2e/University.js
|
||||
name: ${{ matrix.name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -186,6 +226,7 @@ jobs:
|
||||
username: ${{ secrets.username }}
|
||||
password: ${{ secrets.password }}
|
||||
spec: ${{ matrix.spec }}
|
||||
test_name: ${{ matrix.name }}
|
||||
|
||||
# === Other tests ===
|
||||
test_service_exit_statuses:
|
||||
@@ -194,6 +235,7 @@ jobs:
|
||||
- build_and_push_anon_sis_db
|
||||
- build_and_push_anon_kone_db
|
||||
- build_and_push_anon_user_db
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
- name: 'Install dependencies'
|
||||
run: npm ci && cd updater/sis-updater-scheduler && npm ci && cd ../sis-updater-worker && npm ci
|
||||
- name: 'Run linter'
|
||||
run: npm run eslint updater -- --report-unused-disable-directives
|
||||
run: npm run eslint -- updater
|
||||
|
||||
build_worker:
|
||||
name: 'Build Updater worker'
|
||||
@@ -36,15 +36,18 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: 'Build the image'
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: updater/sis-updater-worker/
|
||||
push: false
|
||||
outputs: type=docker,dest=/tmp/worker.tar
|
||||
tags: toska/sis-updater-worker:latest
|
||||
- name: 'Store built image'
|
||||
uses: actions/upload-artifact@v3
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
- name: 'Upload image for other jobs'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: updater-worker-image
|
||||
path: /tmp/worker.tar
|
||||
|
||||
build_scheduler:
|
||||
@@ -54,15 +57,18 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: 'Build the image'
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: updater/sis-updater-scheduler/
|
||||
push: false
|
||||
outputs: type=docker,dest=/tmp/scheduler.tar
|
||||
tags: toska/sis-updater-scheduler:latest
|
||||
- name: 'Store built image'
|
||||
uses: actions/upload-artifact@v3
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
- name: 'Upload image for other jobs'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: updater-scheduler-image
|
||||
path: /tmp/scheduler.tar
|
||||
|
||||
test_updater:
|
||||
@@ -81,13 +87,14 @@ jobs:
|
||||
username: ${{ secrets.TOSKAREGISTRY_USERNAME }}
|
||||
password: ${{ secrets.TOSKAREGISTRY_PASSWORD }}
|
||||
- name: 'Download built images'
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/
|
||||
pattern: 'updater-{worker,scheduler}-image'
|
||||
merge-multiple: true
|
||||
- name: 'Load worker image'
|
||||
run: docker load --input /tmp/artifact/worker.tar
|
||||
run: docker load --input worker.tar
|
||||
- name: 'Load scheduler image'
|
||||
run: docker load --input /tmp/artifact/scheduler.tar
|
||||
run: docker load --input scheduler.tar
|
||||
- name: 'Start worker'
|
||||
run: docker compose --file docker-compose.ci.yml up --detach --no-recreate sis-updater-worker
|
||||
- name: 'Run hourly jobs'
|
||||
@@ -128,6 +135,9 @@ jobs:
|
||||
context: updater/sis-updater-worker
|
||||
containerfiles: |
|
||||
updater/sis-updater-worker/Dockerfile
|
||||
build-args: |
|
||||
SENTRY_RELEASE=${{ github.sha }}
|
||||
SENTRY_ENVIRONMENT=production
|
||||
|
||||
- name: Push to quay.io
|
||||
uses: redhat-actions/push-to-registry@v2
|
||||
@@ -155,6 +165,9 @@ jobs:
|
||||
context: updater/sis-updater-scheduler
|
||||
containerfiles: |
|
||||
updater/sis-updater-scheduler/Dockerfile
|
||||
build-args: |
|
||||
SENTRY_RELEASE=${{ github.sha }}
|
||||
SENTRY_ENVIRONMENT=production
|
||||
|
||||
- name: Push to quay.io
|
||||
uses: redhat-actions/push-to-registry@v2
|
||||
|
||||
+12
-3
@@ -56,8 +56,17 @@ cypress/videos/
|
||||
coverage/
|
||||
instrumented/
|
||||
|
||||
# pycharm
|
||||
# Pycharm
|
||||
.idea
|
||||
|
||||
# fd specific
|
||||
environments/
|
||||
# VS Code
|
||||
.vscode/settings.json
|
||||
.vscode/tasks.json
|
||||
.vscode/launch.json
|
||||
.vscode/terminals.json
|
||||
|
||||
# Funidata specific
|
||||
environments/
|
||||
|
||||
# Target of shared symlink
|
||||
services/**/src/shared
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
npx lint-staged
|
||||
|
||||
if git grep -qE --cached "(it|describe)\.only" $(git diff --cached --name-only) ./cypress; then
|
||||
if git grep -qE --cached "(it|describe)\.only" $(git diff --cached --name-only --diff-filter=ACMR | xargs) ./cypress; then
|
||||
echo "\e[0;31mDo not commit .only-tests."
|
||||
exit 1
|
||||
else
|
||||
|
||||
@@ -191,7 +191,7 @@ 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 API and Oodikone data.
|
||||
[Rapodiff](./services/backend/src/rapodiff/) can be used to check differences between Rapo and Oodikone data.
|
||||
|
||||
## ❓FAQ
|
||||
|
||||
@@ -199,7 +199,7 @@ Continuous integration (CI) works with Github actions and is defined in workflow
|
||||
|
||||
You should always install the dependencies **inside** the container to have the application **inside** the container access them. Module might be missing for example when someone else installs a new library and you only pull the changes in package.json. Use `docker exec <service> npm ci` to install modules inside the container or `npm run install:docker` to install modules inside all packages (backend, frontend, sis-updater-scheduler and sis-updater-worker).
|
||||
|
||||
### Studyguidance groups don't work on my machine
|
||||
### Study guidance groups don't work on my machine
|
||||
|
||||
Make sure you have your VPN on.
|
||||
|
||||
@@ -209,6 +209,41 @@ First: Try `npm run cli` option 1: _Set up oodikone from scratch_ and then optio
|
||||
|
||||
If that does not help, try option 5: _Docker system prune_. Notice that this clears up **all Docker data**, including data related to other projects. If this is not ok, you'll need to identify the Oodikone-specific Docker volumes yourself and caches and clear those invididually.
|
||||
|
||||
### How to run tests locally faster
|
||||
|
||||
Vite simplifies development but can be **very slow** when running tests. If you're curious about the reasons for this, you can read more in [this GitHub issue](https://github.com/cypress-io/cypress/issues/22968).
|
||||
|
||||
To speed up testing, you can use Vite's preview mode. This will make the tests run faster, but keep in mind that you must rebuild the frontend code every time you make changes to it. To use the preview mode, follow these steps:
|
||||
|
||||
1. Run `npm run build` in the `services/frontend` directory.
|
||||
2. Run `npm run preview` in the `services/frontend` directory and keep it running.
|
||||
3. Update the `baseUrl` in [cypress.config.js](./cypress.config.js) to `http://localhost:4173`.
|
||||
4. Start Oodikone with `npm run oodikone`.
|
||||
|
||||
After these steps, you can run your tests:
|
||||
|
||||
- Use `npm run cypress open` to open the Cypress UI and run the tests interactively.
|
||||
|
||||
Tests can also be run in headless mode, similar to how they are run in the CI/CD pipeline. To do this, use:
|
||||
|
||||
```bash
|
||||
npm run cypress:run -- --spec <file path>
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
npm run cypress:run -- --spec "cypress/e2e/Language_center.js"
|
||||
```
|
||||
|
||||
Running tests in headless mode may also help if your browser keeps crashing when running tests interactively. If you want to run all tests, use:
|
||||
|
||||
```bash
|
||||
npm run cypress:run
|
||||
```
|
||||
|
||||
Remember to revert the `baseUrl` in [cypress.config.js](./cypress.config.js) to its original value when you're done.
|
||||
|
||||
## ✌🏼 Maintainers and contribution
|
||||
|
||||
[Toska](https://toska.dev) - University of Helsinki
|
||||
|
||||
+67
-13
@@ -1,18 +1,72 @@
|
||||
# Anonyymioodi - test database setup
|
||||
|
||||
This folder contains a guide and scripts to modify or create anonymous database images for oodikone, mainly used for testing.
|
||||
This folder contains a guide and scripts to modify or create anonymous database images for Oodikone, mainly used for testing.
|
||||
|
||||
This quick guide should be enough for most purposes: Updating schema, data or postgres version. If not, a more elaborate guide is stored in [old readme](old_readme.md) (though it may have some outdated info).
|
||||
The quick guide should be enough for most purposes: updating schema, data, or the PostgreSQL version. If you need to generate completely new `sis-importer-db` and `sis-db` images (which shouldn't happen too often), follow the instructions in the section [Generate new sis-importer-db and sis-db completely from scratch](#generate-new-sis-importer-db-and-sis-db-completely-from-scratch).
|
||||
|
||||
### Quick guide to update a test database
|
||||
## Quick guide to updating a test database
|
||||
|
||||
- Make sure you have the latest test-db. Just pulling images doesn't seem to replace your already existing volume, so you may need to remove it by hand. This may work: `docker volume rm oodikone_sis-db-data`. The new image including data should be pulled next time you launch oodikone normally.
|
||||
- Do your dev stuff normally with the test database, for example with `npm run both`. Edit data however you like, run updater via frontend for example, then run tests to see if they pass.
|
||||
- When you're happy with the database, create a dump:
|
||||
- `docker exec -i <dbname> pg_dump -Fc -U postgres <dbname> > anonyymioodi/<dbname>.sqz`
|
||||
- Now you can shut it down `npm run docker:down`
|
||||
- Navigate to this folder `cd anonyymioodi`
|
||||
- If you need to **update postgres version**, do it now by changing it in this directory's `docker-compose.yml`.
|
||||
- Create new container from dump `./create_container_from_dump.sh <dbname>`
|
||||
- Commit and push it to toska registry `./commit_and_push_to_toska_hub.sh <dbname>`
|
||||
- `docker compose down`
|
||||
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`).
|
||||
|
||||
4. When you're satisfied with the database, create a dump:
|
||||
|
||||
- Use the command: `docker exec -i <dbname> pg_dump -Fc -U postgres <dbname> > anonyymioodi/<dbname>.sqz`.
|
||||
|
||||
5. Shut down the environment with `npm run docker:down`.
|
||||
|
||||
6. Navigate to this folder: `cd anonyymioodi`.
|
||||
|
||||
7. If you need to update the PostgreSQL version, do it now by changing the version in this directory's `docker-compose.yml`. The version should match the one used on the server to avoid compatibility issues.
|
||||
|
||||
8. Create a new container from the dump: `./create_container_from_dump.sh <dbname>`.
|
||||
|
||||
9. Commit and push it to the Toska registry: `./commit_and_push_to_toska_hub.sh <dbname>`.
|
||||
|
||||
10. Bring down the environment with `docker compose down`.
|
||||
|
||||
## Generate new sis-importer-db and sis-db completely from scratch
|
||||
|
||||
> [!CAUTION]
|
||||
> The more the data in `sis-importer-db` and `sis-db` changes, the more tests might break. Because of this, it's highly recommended to make as few changes as possible (for example, avoid changing the degree programs whose students are used in the test data).
|
||||
>
|
||||
> If you only need to make small changes, follow the quick guide above.
|
||||
|
||||
### Sis-importer-db
|
||||
|
||||
1. To generate a sample dump from test Sisu data, follow the instructions at [importer-db-staging-sampletaker](https://github.com/UniversityOfHelsinkiCS/sis-importer/tree/master/importer-db-staging-sampletaker) inside the `sis-importer` repo.
|
||||
|
||||
2. Copy `sis-importer-db.sqz` into this folder.
|
||||
|
||||
3. Run the following commands (in this folder) to create a new image from the dump and push it to the Toska registry:
|
||||
|
||||
```bash
|
||||
./create_container_from_dump.sh sis-importer-db
|
||||
./commit_and_push_to_toska_hub.sh sis-importer-db
|
||||
docker compose down
|
||||
```
|
||||
|
||||
### Sis-db
|
||||
|
||||
1. To ensure the newest `sis-importer-db` image is used:
|
||||
|
||||
- Delete all `sis-importer-db` images you have locally (list images with `docker images`, delete with `docker rmi <image ID>`).
|
||||
- Also, delete the old `sis-importer-db` volume (`docker volume rm sis-importer-db-data`).
|
||||
|
||||
2. Start Oodikone and Updater with `npm run both` (this will pull the newest `sis-importer-db` image you just pushed in the previous step).
|
||||
|
||||
3. Truncate all tables in `sis-db` **_except for `migrations` and `semesters`_** (the easiest way is to use Adminer).
|
||||
|
||||
4. Modify the scheduler limit in [config.js](../updater/sis-updater-scheduler/src/config.js) by setting `module.exports.DEV_SCHEDULE_COUNT = null`.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -9,9 +9,7 @@ services:
|
||||
PGDATA: /data
|
||||
POSTGRES_DB: kone-db
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
image: postgres:15.7
|
||||
profiles:
|
||||
- oodikone
|
||||
image: postgres:15.8
|
||||
|
||||
sis-db:
|
||||
<<: *defaults
|
||||
@@ -20,7 +18,7 @@ services:
|
||||
PGDATA: /data
|
||||
POSTGRES_DB: sis-db
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
image: postgres:15.7
|
||||
image: postgres:15.8
|
||||
shm_size: 2gb
|
||||
|
||||
sis-importer-db:
|
||||
@@ -30,9 +28,7 @@ services:
|
||||
PGDATA: /data
|
||||
POSTGRES_DB: sis-importer-db
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
image: postgres:15.7
|
||||
profiles:
|
||||
- updater
|
||||
image: postgres:15.8
|
||||
|
||||
user-db:
|
||||
<<: *defaults
|
||||
@@ -41,6 +37,4 @@ services:
|
||||
PGDATA: /data
|
||||
POSTGRES_DB: user-db
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
image: postgres:15.7
|
||||
profiles:
|
||||
- oodikone
|
||||
image: postgres:15.8
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
-- add these to sis-importer db by running:
|
||||
-- docker exec -i sis-importer-db psql -U postgres sis-importer-db < enrich_sampledata.sql
|
||||
|
||||
-- if you need to run updater for these students only, see studentnumberlist in the end
|
||||
-- this file
|
||||
|
||||
-- Add admission types (valintaperusteet) to studyrights for some of the computer
|
||||
-- science 2018-2019 population students. See person_ids / student_number below
|
||||
-- (in case you need to update dumps).
|
||||
|
||||
-- TODO: this should be moved into the sampledata creator script in
|
||||
-- importer repo, but since this file contains studentnumbers and person ids, it's
|
||||
-- better to keep this here
|
||||
|
||||
-- KP (koepisteet)
|
||||
-- hy-hlo-1412620 | 013683126
|
||||
-- hy-hlo-1485246 | 014340523
|
||||
-- hy-hlo-32230770 | 010589388
|
||||
-- hy-hlo-35626233 | 010775888
|
||||
-- hy-hlo-40150144 | 011065995
|
||||
UPDATE studyrights
|
||||
SET admission_type_urn = 'urn:code:admission-type:kp'
|
||||
WHERE person_id IN (
|
||||
'hy-hlo-1412620',
|
||||
'hy-hlo-1485246',
|
||||
'hy-hlo-32230770',
|
||||
'hy-hlo-35626233',
|
||||
'hy-hlo-40150144'
|
||||
);
|
||||
|
||||
-- TV (todistusvalinta)
|
||||
-- hy-hlo-45783433 | 011321150
|
||||
-- hy-hlo-48537946 | 010764653
|
||||
-- hy-hlo-49801905 | 011194400
|
||||
-- hy-hlo-53321302 | 011338871
|
||||
-- hy-hlo-51223300 | 013951984
|
||||
-- hy-hlo-53310137 | 012520688
|
||||
-- hy-hlo-53621583 | 011196796
|
||||
-- hy-hlo-53863064 | 011736884
|
||||
-- hy-hlo-57711592 | 010262566
|
||||
-- hy-hlo-57732872 | 013670647
|
||||
UPDATE studyrights
|
||||
SET admission_type_urn = 'urn:code:admission-type:tv'
|
||||
WHERE person_id IN (
|
||||
'hy-hlo-45783433',
|
||||
'hy-hlo-48537946',
|
||||
'hy-hlo-49801905',
|
||||
'hy-hlo-53321302',
|
||||
'hy-hlo-51223300',
|
||||
'hy-hlo-53310137',
|
||||
'hy-hlo-53621583',
|
||||
'hy-hlo-53863064',
|
||||
'hy-hlo-57711592',
|
||||
'hy-hlo-57732872'
|
||||
);
|
||||
|
||||
-- YP:
|
||||
-- hy-hlo-67768022 | 011952325
|
||||
-- hy-hlo-73740025 | 011736606
|
||||
-- hy-hlo-74780917 | 012775433
|
||||
-- hy-hlo-79615371 | 014113941
|
||||
-- hy-hlo-78118455 | 014300615
|
||||
UPDATE studyrights
|
||||
SET admission_type_urn = 'urn:code:admission-type:yp'
|
||||
WHERE person_id IN (
|
||||
'hy-hlo-67768022',
|
||||
'hy-hlo-73740025',
|
||||
'hy-hlo-74780917',
|
||||
'hy-hlo-79615371',
|
||||
'hy-hlo-78118455'
|
||||
);
|
||||
-- AV:
|
||||
-- hy-hlo-78777265 | 010779392
|
||||
-- hy-hlo-81909550 | 011894153
|
||||
-- hy-hlo-84571890 | 013897158
|
||||
-- hy-hlo-80330834 | 015085926
|
||||
-- hy-hlo-84775722 | 011791272
|
||||
UPDATE studyrights
|
||||
SET admission_type_urn = 'urn:code:admission-type:av'
|
||||
WHERE person_id IN (
|
||||
'hy-hlo-78777265',
|
||||
'hy-hlo-81909550',
|
||||
'hy-hlo-84571890',
|
||||
'hy-hlo-80330834',
|
||||
'hy-hlo-84775722'
|
||||
);
|
||||
-- M:
|
||||
-- hy-hlo-87439827 | 011928456
|
||||
-- hy-hlo-96612371 | 014418361
|
||||
-- hy-hlo-84840261 | 010975895
|
||||
-- hy-hlo-90330681 | 010522688
|
||||
-- hy-hlo-96594603 | 013951573
|
||||
UPDATE studyrights
|
||||
SET admission_type_urn = 'urn:code:admission-type:m'
|
||||
WHERE person_id IN (
|
||||
'hy-hlo-87439827',
|
||||
'hy-hlo-96612371',
|
||||
'hy-hlo-84840261',
|
||||
'hy-hlo-90330681',
|
||||
'hy-hlo-96594603'
|
||||
);
|
||||
|
||||
-- all studentnumbers for running updater:
|
||||
013683126
|
||||
014340523
|
||||
010589388
|
||||
010775888
|
||||
011065995
|
||||
011321150
|
||||
010764653
|
||||
011194400
|
||||
011338871
|
||||
013951984
|
||||
012520688
|
||||
011196796
|
||||
011736884
|
||||
010262566
|
||||
013670647
|
||||
011952325
|
||||
011736606
|
||||
012775433
|
||||
014113941
|
||||
014300615
|
||||
010779392
|
||||
011894153
|
||||
013897158
|
||||
015085926
|
||||
011791272
|
||||
011928456
|
||||
014418361
|
||||
010975895
|
||||
010522688
|
||||
013951573
|
||||
@@ -1,71 +0,0 @@
|
||||
# Old readme of anonyymioodi
|
||||
|
||||
This is mostly obsolete, but may help with something sometimes.
|
||||
|
||||
## Anonyymioodi - anonymous database setup for oodikone
|
||||
|
||||
(name anonyymioodi for :culture: reasons)
|
||||
|
||||
This folder contains a guide and scripts to create anonymous database images for oodikone, mainly used for testing.
|
||||
|
||||
## How to create / modify database images
|
||||
|
||||
Creating / modifying images varies a bit depending on the db. Generally, to modify images, you can either use `psql` directly in docker by running `docker exec -it <dbname> psql -U postgres <dbname>` or start adminer with oodikone's docker-compose.
|
||||
|
||||
By default, previous db images from Toska Hub are used as a base. If needed, comment out Toska hub image and uncomment corresponding clean postgres image in docker-compose before running other steps.
|
||||
|
||||
See more details on how to create / modify databases below.
|
||||
|
||||
### Sis-importer-db
|
||||
|
||||
The following steps are for building a clean sis-importer-db image from scratch. If you want to just modify the existing image a bit, follow the steps defined for user-db / kone-db.
|
||||
|
||||
- See [sis-importer/importer-db-staging-sampletaker](https://github.com/UniversityOfHelsinkiCS/sis-importer/tree/master/importer-db-staging-sampletaker) and create `sis-importer-db.sqz` dump
|
||||
- Move `sis-importer-db.sqz` to this folder
|
||||
- Run `./create_container_from_dump.sh sis-importer-db`
|
||||
- Enrich db with command in file `enrich_data.sql` either by piping command to container or by running command inside container.
|
||||
|
||||
### Sis-db:
|
||||
|
||||
The following steps are for building a clean sis-db image from scratch using oodikone's updater. If you want to just modify the existing image a bit, follow the steps defined for user-db / kone-db.
|
||||
|
||||
- Temporarily replace sis-db -service in oodikone's docker-compose.yml with sis-db -service from this repo's docker-compose.yml
|
||||
- Ensure the newest sis-importer-db image is pulled from toska hub in oodikone repo
|
||||
- Modify sis scheduler limit in config.js by setting: `module.exports.DEV_SCHEDULE_COUNT = null`
|
||||
- Start updater, see oodikone readme for more info.
|
||||
- Populate sis-db from sis-importer-db:
|
||||
|
||||
```
|
||||
curl --request GET --url 'http://localhost:8082/v1/meta?token=dev' && \
|
||||
curl --request GET --url 'http://localhost:8082/v1/programmes?token=dev' && \
|
||||
curl --request GET --url 'http://localhost:8082/v1/students?token=dev'
|
||||
```
|
||||
|
||||
(or run these from frontend. You might also want to follow sis-updater-workers logs to ensure things are running smoothly.)
|
||||
|
||||
- Clean up any extra stuff you don't want to keep in sis-db.
|
||||
- Bonus: check that cypress tests run okay
|
||||
|
||||
### User-db / kone-db
|
||||
|
||||
- Temporarily replace the db service in oodikone's docker-compose.yml with the same db service from this folder's docker-compose.yml
|
||||
- Ensure the newest user-db/kone-db image is pulled from the registry with `docker-compose pull`
|
||||
- Modify db to wanted state. Tips:
|
||||
- Run oodikone if you want to upgrade something from frontend (e.g. tags in kone-db, courses in sis-db etc.).
|
||||
- Easy way to create new users to user-db is to modify dev user details in the frontend API config. A new user is created during login if the given uid is not present in user-db.
|
||||
|
||||
## Publishing the image
|
||||
|
||||
After completing the steps above and making sure your local version has the correct data, run `./commit_and_push_to_toska_hub.sh <dbname>` to publish the db image to Toska Hub.
|
||||
|
||||
## How to update Postgres versions
|
||||
|
||||
When Postgres is updated on the server, anon db images need to be updated to match server versions. Do following:
|
||||
|
||||
- Ensure you're using toska hub image in docker-compose and that you've pulled the newest image from toska hub: `docker-compose pull <dbname>`.
|
||||
- Start database with docker-compose
|
||||
- Create dump from database by running `docker exec -i <dbname> pg_dump -Fc -U postgres <dbname> > <dbname>.sqz`
|
||||
- Close container with docker-compose down.
|
||||
- Update the Postgres version in docker-compose.yml in this folder.
|
||||
- Uncomment row with Postgres image and comment row with Toskahub image.
|
||||
- Run `./create_container_from_dump.sh <dbname>`, followed by `./commit_and_push_to_toska_hub.sh <dbname>`
|
||||
@@ -48,12 +48,12 @@ set_custom_select_prompt() {
|
||||
PS3="Please enter your choice: "
|
||||
|
||||
options=(
|
||||
"Set up oodikone from scratch."
|
||||
"Reset all real data."
|
||||
"Reset single database."
|
||||
"Set up Oodikone from scratch"
|
||||
"Reset all real data"
|
||||
"Reset single database"
|
||||
"Restore data from dumps"
|
||||
"Docker system prune"
|
||||
"Quit."
|
||||
"Quit"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -66,11 +66,11 @@ set_custom_select_prompt
|
||||
while true; do
|
||||
select opt in "${options[@]}"; do
|
||||
case $opt in
|
||||
"Set up oodikone from scratch.")
|
||||
"Set up Oodikone from scratch")
|
||||
set_up_oodikone_from_scratch;;
|
||||
"Reset all real data.")
|
||||
"Reset all real data")
|
||||
reset_all_real_data;;
|
||||
"Reset single database.")
|
||||
"Reset single database")
|
||||
reset_single_database
|
||||
set_custom_select_prompt # Set prompt back to initial values
|
||||
;;
|
||||
@@ -78,7 +78,7 @@ while true; do
|
||||
restore_data_from_dumps;;
|
||||
"Docker system prune")
|
||||
docker_prune;;
|
||||
"Quit.")
|
||||
"Quit")
|
||||
break 2;;
|
||||
*) msg "${RED}Invalid option:${NOFORMAT} $REPLY
|
||||
";;
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable global-require */
|
||||
/* eslint-disable import/extensions */
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
/* eslint-disable import/no-commonjs */
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
|
||||
/* eslint-disable import/no-unused-modules */
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = defineConfig({
|
||||
@@ -9,8 +9,8 @@ module.exports = defineConfig({
|
||||
defaultCommandTimeout: 30000,
|
||||
requestTimeout: 30000,
|
||||
videoCompression: false,
|
||||
viewportWidth: 1800,
|
||||
viewportHeight: 1200,
|
||||
viewportWidth: 1920,
|
||||
viewportHeight: 1080,
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const openCompletedCoursesModal = () => {
|
||||
cy.get('button').contains('Search completed courses of students').click()
|
||||
@@ -22,8 +22,8 @@ const selectSavedCourselist = name => {
|
||||
const deleteAllSearches = () => {
|
||||
cy.contains('Saved courselists')
|
||||
.get('.dropdown')
|
||||
.then(d => {
|
||||
const searchItems = d.find('div[role=option] > span[class=text]')
|
||||
.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).type('{enter}')
|
||||
@@ -85,10 +85,12 @@ describe('When search modal is opened', () => {
|
||||
})
|
||||
|
||||
it('Pushes the query to url', () => {
|
||||
cy.url().should(
|
||||
'include',
|
||||
'/completedcoursessearch?courseList=TKT10001&courseList=TKT10002&courseList=TKT10003&courseList=TKT10004&studentList=010450938&studentList=010589388&studentList=010614509'
|
||||
)
|
||||
cy.fixture('completedCoursesData').then(({ studentSet1, coursesSet1 }) => {
|
||||
cy.url().should(
|
||||
'include',
|
||||
`/completedcoursessearch?courseList=${coursesSet1.join('&courseList=')}&studentList=${studentSet1.join('&studentList=')}`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('Finds correct students and courses', () => {
|
||||
@@ -96,6 +98,8 @@ describe('When search modal is opened', () => {
|
||||
cy.get('[data-cy="completed-courses-table-div"]').should('contain.text', studentSet1[0])
|
||||
cy.get('[data-cy="completed-courses-table-div"]').should('contain.text', studentSet1[1])
|
||||
cy.get('[data-cy="completed-courses-table-div"]').should('contain.text', studentSet1[2])
|
||||
cy.get('[data-cy="completed-courses-table-div"]').should('contain.text', studentSet1[3])
|
||||
cy.get('[data-cy="completed-courses-table-div"]').should('contain.text', studentSet1[4])
|
||||
cy.get('[data-cy="completed-courses-table-div"]').should('contain.text', coursesSet1[0])
|
||||
cy.get('[data-cy="completed-courses-table-div"]').should('contain.text', coursesSet1[1])
|
||||
cy.get('[data-cy="completed-courses-table-div"]').should('contain.text', coursesSet1[2])
|
||||
|
||||
+732
-289
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const openCustomPopupForm = () => {
|
||||
cy.get('[data-cy="custom-pop-search-button"]').click()
|
||||
@@ -27,8 +27,8 @@ const deleteAllSearches = () => {
|
||||
.parent()
|
||||
.parent()
|
||||
.get('.dropdown')
|
||||
.then(d => {
|
||||
const searchItems = d.find('div[role=option] > span[class=text]')
|
||||
.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}`)
|
||||
@@ -64,7 +64,11 @@ const containsAmountOfStudents = (amount = 0) => {
|
||||
}
|
||||
|
||||
const containsSpecificStudents = (studentnumbers = []) => {
|
||||
cy.contains(`Students (${studentnumbers.length})`).click()
|
||||
cy.contains(`Students (${studentnumbers.length})`)
|
||||
.parent()
|
||||
.then($parentDiv => {
|
||||
if (!$parentDiv.hasClass('active')) cy.contains(`Students (${studentnumbers.length})`).click()
|
||||
})
|
||||
|
||||
studentnumbers.forEach(s => cy.contains(s))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,514 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const { getEmptyYears } = require('../support/commands')
|
||||
|
||||
describe('Evaluation overview', () => {
|
||||
describe('Programme view', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/evaluationoverview/programme/KH50_001', 'admin')
|
||||
})
|
||||
|
||||
it('Page opens correctly', () => {
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma')
|
||||
cy.contains('KH50_001')
|
||||
cy.contains("This view is an abridged version of Oodikone's Studyprogramme Overview")
|
||||
})
|
||||
|
||||
it('Progress data is shown correctly with graduated included', () => {
|
||||
cy.get('[data-cy="Graph-StudyTrackProgress"]').within(() => {
|
||||
const totalStats = ['12.9%', '6.9%', '9.7%', '9.2%', '3.7%', '6.0%', '51.6%']
|
||||
for (const stat of totalStats) {
|
||||
cy.contains(stat)
|
||||
}
|
||||
})
|
||||
|
||||
const years = getEmptyYears(true)
|
||||
|
||||
const tableContents = [
|
||||
...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', 32, 8, 3, 8, 8, 5, 0, 0],
|
||||
['2020 - 2021', 27, 2, 1, 3, 6, 2, 4, 9],
|
||||
['2019 - 2020', 33, 1, 0, 2, 1, 0, 3, 26],
|
||||
['2018 - 2019', 44, 0, 1, 1, 2, 0, 3, 37],
|
||||
['2017 - 2018', 47, 0, 1, 3, 0, 1, 2, 40],
|
||||
['Total', 217, 28, 15, 21, 20, 8, 13, 112],
|
||||
]
|
||||
|
||||
cy.checkTableStats(tableContents, 'StudyTrackProgress')
|
||||
})
|
||||
|
||||
it('Progress data is shown correctly with graduated excluded', () => {
|
||||
cy.get('[data-cy=GraduatedToggle]').click()
|
||||
cy.get('[data-cy="Graph-StudyTrackProgress"]').within(() => {
|
||||
const totalStats = ['30.8%', '15.4%', '22.0%', '20.9%', '4.4%', '5.5%']
|
||||
for (const stat of totalStats) {
|
||||
cy.contains(stat)
|
||||
}
|
||||
})
|
||||
|
||||
const years = getEmptyYears(true)
|
||||
|
||||
const tableContents = [
|
||||
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
['2023 - 2024', 8, 8, 0, 0, 0, 0, 0, 0],
|
||||
['2022 - 2023', 24, 9, 8, 4, 3, 0, 0, 0],
|
||||
['2021 - 2022', 31, 8, 3, 8, 8, 4, 0, 0],
|
||||
['2020 - 2021', 11, 2, 1, 2, 5, 0, 0, 1],
|
||||
['2019 - 2020', 7, 1, 0, 2, 1, 0, 0, 3],
|
||||
['2018 - 2019', 5, 0, 1, 1, 2, 0, 0, 1],
|
||||
['2017 - 2018', 5, 0, 1, 3, 0, 0, 1, 0],
|
||||
['Total', 91, 28, 14, 20, 19, 4, 1, 5],
|
||||
]
|
||||
|
||||
cy.checkTableStats(tableContents, 'StudyTrackProgress')
|
||||
})
|
||||
|
||||
it('Graduation times breakdown data is shown correctly', () => {
|
||||
cy.get('[data-cy=graduation-times-graph-breakdown]').within(() => {
|
||||
cy.contains('Graduation year')
|
||||
cy.contains('2021')
|
||||
cy.get('[aria-label="2021, 8. Overtime."]')
|
||||
cy.get('[aria-label="2021, 24. Max. year overtime."]')
|
||||
cy.get('[aria-label="2021, 14. On time."]').trigger('mouseover')
|
||||
cy.contains('Graduated On time: 14 students')
|
||||
})
|
||||
|
||||
cy.get('[data-cy=YearToggle]').click()
|
||||
|
||||
cy.get('[data-cy=graduation-times-graph-breakdown]').within(() => {
|
||||
cy.contains('Graduation year')
|
||||
cy.contains('2021 - 2022')
|
||||
cy.get('[aria-label="2021 - 2022, 13. Overtime."]')
|
||||
cy.get('[aria-label="2021 - 2022, 21. Max. year overtime."]')
|
||||
cy.get('[aria-label="2021 - 2022, 21. On time."]').trigger('mouseover')
|
||||
cy.contains('Graduated On time: 21 students')
|
||||
})
|
||||
})
|
||||
|
||||
it('Graduation times median time data is shown correctly', () => {
|
||||
cy.get('[data-cy=GraduationTimeToggle]').click()
|
||||
|
||||
cy.get('[data-cy=graduation-times-graph]').within(() => {
|
||||
cy.contains('Graduation year')
|
||||
cy.contains('2021')
|
||||
cy.contains('46 graduated').trigger('mouseover')
|
||||
cy.contains('46 students graduated in year 2021')
|
||||
cy.contains('median study time: 41 months')
|
||||
cy.contains('14 graduated on time')
|
||||
cy.contains('24 graduated max year overtime')
|
||||
cy.contains('8 graduated over year late')
|
||||
})
|
||||
|
||||
cy.get('[data-cy=YearToggle]').click()
|
||||
|
||||
cy.get('[data-cy=graduation-times-graph]').within(() => {
|
||||
cy.contains('Graduation year')
|
||||
cy.contains('2021 - 2022')
|
||||
cy.contains('55 graduated').trigger('mouseover')
|
||||
cy.contains('55 students graduated in year 2021 - 2022')
|
||||
cy.contains('median study time: 40 months')
|
||||
cy.contains('21 graduated on time')
|
||||
cy.contains('21 graduated max year overtime')
|
||||
cy.contains('13 graduated over year late')
|
||||
})
|
||||
})
|
||||
|
||||
it("'Programmes before or after' data is shown correctly", () => {
|
||||
cy.get('[data-cy=Graph-ProgrammesBeforeOrAfter]').within(() => {
|
||||
cy.contains('Matematiikan ja tilastotieteen maisteriohjelma')
|
||||
cy.contains('Tietojenkäsittelytieteen maisteriohjelma')
|
||||
cy.contains('Datatieteen maisteriohjelma')
|
||||
cy.contains('2021')
|
||||
cy.contains('32')
|
||||
cy.contains('19')
|
||||
})
|
||||
|
||||
let emptyYears = new Array(getEmptyYears(false).length).fill(0)
|
||||
|
||||
let tableContents = [
|
||||
['MH50_001', 'MAST', 'Matematiikan ja tilastotieteen maisteriohjelma', 0, 0, 1, 9, 32, 19, 15, ...emptyYears],
|
||||
['MH50_002', 'LSI', 'Life Science Informatics -maisteriohjelma', 0, 0, 0, 1, 2, 1, 1, ...emptyYears],
|
||||
[
|
||||
'MH50_003',
|
||||
'TCM',
|
||||
'Teoreettisten ja laskennallisten menetelmien maisteriohjelma',
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
...emptyYears,
|
||||
],
|
||||
['MH50_009', 'CSM', 'Tietojenkäsittelytieteen maisteriohjelma', 0, 0, 0, 0, 2, 3, 2, ...emptyYears],
|
||||
['MH50_010', 'DATA', 'Datatieteen maisteriohjelma', 0, 0, 0, 1, 5, 10, 1, ...emptyYears],
|
||||
['MH70_009', 'ECON', 'Taloustieteen maisteriohjelma', 0, 0, 0, 0, 4, 7, 4, ...emptyYears],
|
||||
]
|
||||
|
||||
cy.checkTableStats(tableContents, 'undefined')
|
||||
|
||||
cy.get('[data-cy=YearToggle]').click()
|
||||
|
||||
cy.get('[data-cy=Graph-ProgrammesBeforeOrAfter]').within(() => {
|
||||
cy.contains('Matematiikan ja tilastotieteen maisteriohjelma')
|
||||
cy.contains('Tietojenkäsittelytieteen maisteriohjelma')
|
||||
cy.contains('Datatieteen maisteriohjelma')
|
||||
cy.contains('2021 - 2022')
|
||||
cy.contains('28')
|
||||
cy.contains('16')
|
||||
})
|
||||
|
||||
emptyYears = new Array(getEmptyYears(true).length).fill(0)
|
||||
|
||||
tableContents = [
|
||||
['MH50_001', 'MAST', 'Matematiikan ja tilastotieteen maisteriohjelma', 0, 0, 4, 24, 28, 16, 4, ...emptyYears],
|
||||
['MH50_002', 'LSI', 'Life Science Informatics -maisteriohjelma', 0, 0, 1, 0, 3, 1, 0, ...emptyYears],
|
||||
[
|
||||
'MH50_003',
|
||||
'TCM',
|
||||
'Teoreettisten ja laskennallisten menetelmien maisteriohjelma',
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
...emptyYears,
|
||||
],
|
||||
['MH50_009', 'CSM', 'Tietojenkäsittelytieteen maisteriohjelma', 0, 0, 0, 0, 4, 3, 0, ...emptyYears],
|
||||
['MH50_010', 'DATA', 'Datatieteen maisteriohjelma', 0, 0, 1, 0, 12, 4, 0, ...emptyYears],
|
||||
['MH70_009', 'ECON', 'Taloustieteen maisteriohjelma', 0, 0, 0, 4, 6, 5, 0, ...emptyYears],
|
||||
]
|
||||
|
||||
cy.checkTableStats(tableContents, 'undefined')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Faculty view', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/evaluationoverview/faculty/H50', 'admin')
|
||||
})
|
||||
|
||||
it('Page opens correctly', () => {
|
||||
cy.contains('Matemaattis-luonnontieteellinen tiedekunta')
|
||||
cy.contains('H50')
|
||||
cy.contains("This view is an abridged version of Oodikone's Faculty Overview")
|
||||
})
|
||||
|
||||
describe('Progress stats', () => {
|
||||
describe('Bachelor stats', () => {
|
||||
it('graph with graduated included is shown correctly', () => {
|
||||
cy.get('[data-cy="Graph-FacultyBachelorsProgress"]').within(() => {
|
||||
const totalStats = ['12.2%', '5.3%', '7.5%', '8.2%', '5.3%', '6.3%', '55.2%']
|
||||
for (const stat of totalStats) {
|
||||
cy.contains(stat)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('graph with graduated excluded is shown correctly', () => {
|
||||
cy.get('[data-cy="GraduatedToggle"]').click()
|
||||
cy.get('[data-cy="Graph-FacultyBachelorsProgress"]').within(() => {
|
||||
const totalStats = ['30.5%', '12.5%', '18.0%', '19.5%', '7.0%', '3.1%', '9.4%']
|
||||
for (const stat of totalStats) {
|
||||
cy.contains(stat)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('table with graduated included is shown correctly', () => {
|
||||
cy.get('[data-cy="Table-FacultyBachelorsProgress"]')
|
||||
const years = getEmptyYears(true)
|
||||
const tableContents = [
|
||||
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
['2023 - 2024', 14, 13, 0, 0, 0, 0, 0, 1],
|
||||
['2022 - 2023', 32, 11, 10, 6, 4, 0, 1, 0],
|
||||
['2021 - 2022', 44, 10, 4, 9, 11, 7, 1, 2],
|
||||
['2020 - 2021', 38, 3, 1, 3, 7, 3, 8, 13],
|
||||
['2019 - 2020', 50, 2, 0, 2, 1, 4, 5, 36],
|
||||
['2018 - 2019', 75, 0, 1, 1, 2, 2, 3, 66],
|
||||
['2017 - 2018', 66, 0, 1, 3, 1, 1, 2, 58],
|
||||
['Total', 319, 39, 17, 24, 26, 17, 20, 176],
|
||||
]
|
||||
cy.checkTableStats(tableContents, 'FacultyBachelorsProgress')
|
||||
})
|
||||
|
||||
it('table with graduated excluded is shown correctly', () => {
|
||||
cy.get('[data-cy="GraduatedToggle"]').click()
|
||||
cy.get('[data-cy="Table-FacultyBachelorsProgress"]')
|
||||
const years = getEmptyYears(true)
|
||||
const tableContents = [
|
||||
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
['2023 - 2024', 14, 13, 0, 0, 0, 0, 0, 1],
|
||||
['2022 - 2023', 30, 11, 9, 6, 4, 0, 0, 0],
|
||||
['2021 - 2022', 41, 10, 4, 9, 11, 6, 0, 1],
|
||||
['2020 - 2021', 14, 3, 1, 2, 6, 0, 1, 1],
|
||||
['2019 - 2020', 14, 2, 0, 2, 1, 3, 2, 4],
|
||||
['2018 - 2019', 6, 0, 1, 1, 2, 0, 0, 2],
|
||||
['2017 - 2018', 9, 0, 1, 3, 1, 0, 1, 3],
|
||||
['Total', 128, 39, 16, 23, 25, 9, 4, 12],
|
||||
]
|
||||
cy.checkTableStats(tableContents, 'FacultyBachelorsProgress')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Bachelor + master stats', () => {
|
||||
it('graph with graduated included is shown correctly', () => {
|
||||
cy.get('[data-cy=Graph-FacultyBachelorMastersProgress]').within(() => {
|
||||
const totalStats = ['21.3%', '8.4%', '10.1%', '14.0%', '12.4%', '6.7%', '27.0%']
|
||||
for (const stat of totalStats) {
|
||||
cy.contains(stat)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('graph with graduated excluded is shown correctly', () => {
|
||||
cy.get('[data-cy="GraduatedToggle"]').click()
|
||||
cy.get('[data-cy=Graph-FacultyBachelorMastersProgress]').within(() => {
|
||||
const totalStats = ['28.7%', '11.6%', '14.0%', '18.6%', '15.5%', '3.1%', '8.5%']
|
||||
for (const stat of totalStats) {
|
||||
cy.contains(stat)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('table with graduated included is shown correctly', () => {
|
||||
cy.get('[data-cy="Table-FacultyBachelorMastersProgress"]')
|
||||
const years = getEmptyYears(true)
|
||||
const tableContents = [
|
||||
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
['2023 - 2024', 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
['2022 - 2023', 2, 2, 0, 0, 0, 0, 0, 0],
|
||||
['2021 - 2022', 4, 2, 0, 0, 0, 1, 0, 1],
|
||||
['2020 - 2021', 23, 13, 2, 4, 0, 4, 0, 0],
|
||||
['2019 - 2020', 32, 7, 7, 4, 7, 3, 3, 1],
|
||||
['2018 - 2019', 63, 10, 2, 5, 13, 7, 4, 22],
|
||||
['2017 - 2018', 54, 4, 4, 5, 5, 7, 5, 24],
|
||||
['Total', 178, 38, 15, 18, 25, 22, 12, 48],
|
||||
]
|
||||
cy.checkTableStats(tableContents, 'FacultyBachelorMastersProgress')
|
||||
})
|
||||
|
||||
it('table with graduated excluded is shown correctly', () => {
|
||||
cy.get('[data-cy="GraduatedToggle"]').click()
|
||||
cy.get('[data-cy="Table-FacultyBachelorMastersProgress"]')
|
||||
const years = getEmptyYears(true)
|
||||
const tableContents = [
|
||||
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
['2023 - 2024', 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
['2022 - 2023', 2, 2, 0, 0, 0, 0, 0, 0],
|
||||
['2021 - 2022', 4, 2, 0, 0, 0, 1, 0, 1],
|
||||
['2020 - 2021', 21, 13, 2, 4, 0, 2, 0, 0],
|
||||
['2019 - 2020', 31, 7, 7, 4, 7, 3, 2, 1],
|
||||
['2018 - 2019', 41, 9, 2, 5, 12, 7, 1, 5],
|
||||
['2017 - 2018', 30, 4, 4, 5, 5, 7, 1, 4],
|
||||
['Total', 129, 37, 15, 18, 24, 20, 4, 11],
|
||||
]
|
||||
cy.checkTableStats(tableContents, 'FacultyBachelorMastersProgress')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Master stats', () => {
|
||||
it('graph with graduated included is shown correctly', () => {
|
||||
cy.get('[data-cy=Graph-FacultyMastersProgress]').within(() => {
|
||||
const totalStats = ['16.1%', '12.9%', '6.5%', '12.9%', '9.7%', '16.1%', '25.8%']
|
||||
for (const stat of totalStats) {
|
||||
cy.contains(stat)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('graph with graduated excluded is shown correctly', () => {
|
||||
cy.get('[data-cy="GraduatedToggle"]').click()
|
||||
cy.get('[data-cy=Graph-FacultyMastersProgress]').within(() => {
|
||||
const totalStats = ['23.8%', '19.0%', '9.5%', '9.5%', '9.5%', '14.3%', '14.3%']
|
||||
for (const stat of totalStats) {
|
||||
cy.contains(stat)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('table with graduated included is shown correctly', () => {
|
||||
cy.get('[data-cy="Table-FacultyMastersProgress"]')
|
||||
const years = getEmptyYears(true)
|
||||
const tableContents = [
|
||||
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
['2023 - 2024', 4, 4, 0, 0, 0, 0, 0, 0],
|
||||
['2022 - 2023', 5, 0, 1, 1, 2, 0, 0, 1],
|
||||
['2021 - 2022', 8, 1, 2, 0, 2, 1, 1, 1],
|
||||
['2020 - 2021', 3, 0, 0, 0, 0, 0, 2, 1],
|
||||
['2019 - 2020', 7, 0, 0, 1, 0, 1, 1, 4],
|
||||
['2018 - 2019', 2, 0, 0, 0, 0, 1, 0, 1],
|
||||
['2017 - 2018', 2, 0, 1, 0, 0, 0, 1, 0],
|
||||
['Total', 31, 5, 4, 2, 4, 3, 5, 8],
|
||||
]
|
||||
cy.checkTableStats(tableContents, 'FacultyMastersProgress')
|
||||
})
|
||||
|
||||
it('table with graduated excluded is shown correctly', () => {
|
||||
cy.get('[data-cy="GraduatedToggle"]').click()
|
||||
cy.get('[data-cy="Table-FacultyMastersProgress"]')
|
||||
const years = getEmptyYears(true)
|
||||
const tableContents = [
|
||||
...years.map(year => [year, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
['2023 - 2024', 4, 4, 0, 0, 0, 0, 0, 0],
|
||||
['2022 - 2023', 5, 0, 1, 1, 2, 0, 0, 1],
|
||||
['2021 - 2022', 5, 1, 2, 0, 0, 0, 1, 1],
|
||||
['2020 - 2021', 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
['2019 - 2020', 3, 0, 0, 1, 0, 1, 1, 0],
|
||||
['2018 - 2019', 2, 0, 0, 0, 0, 1, 0, 1],
|
||||
['2017 - 2018', 2, 0, 1, 0, 0, 0, 1, 0],
|
||||
['Total', 21, 5, 4, 2, 2, 2, 3, 3],
|
||||
]
|
||||
cy.checkTableStats(tableContents, 'FacultyMastersProgress')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Doctor stats', () => {
|
||||
it('graph with graduated included is shown correctly', () => {
|
||||
cy.get('[data-cy=Graph-FacultyDoctoralProgress]').within(() => {
|
||||
const totalStats = ['6.0%', '18.0%', '16.0%', '14.0%', '46.0%']
|
||||
for (const stat of totalStats) {
|
||||
cy.contains(stat)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('graph with graduated excluded is shown correctly', () => {
|
||||
cy.get('[data-cy="GraduatedToggle"]').click()
|
||||
cy.get('[data-cy=Graph-FacultyDoctoralProgress]').within(() => {
|
||||
const totalStats = ['9.1%', '24.2%', '12.1%', '15.2%', '39.4%']
|
||||
for (const stat of totalStats) {
|
||||
cy.contains(stat)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('table with graduated included is shown correctly', () => {
|
||||
cy.get('[data-cy="Table-FacultyDoctoralProgress"]')
|
||||
const years = getEmptyYears(true)
|
||||
const tableContents = [
|
||||
...years.map(year => [year, 0, 0, 0, 0, 0, 0]),
|
||||
['2023 - 2024', 1, 1, 0, 0, 0, 0],
|
||||
['2022 - 2023', 9, 1, 7, 1, 0, 0],
|
||||
['2021 - 2022', 3, 1, 1, 0, 0, 1],
|
||||
['2020 - 2021', 5, 0, 0, 2, 1, 2],
|
||||
['2019 - 2020', 11, 0, 0, 1, 4, 6],
|
||||
['2018 - 2019', 14, 0, 1, 4, 2, 7],
|
||||
['2017 - 2018', 7, 0, 0, 0, 0, 7],
|
||||
['Total', 50, 3, 9, 8, 7, 23],
|
||||
]
|
||||
cy.checkTableStats(tableContents, 'FacultyDoctoralProgress')
|
||||
})
|
||||
|
||||
it('table with graduated excluded is shown correctly', () => {
|
||||
cy.get('[data-cy="GraduatedToggle"]').click()
|
||||
cy.get('[data-cy="Table-FacultyDoctoralProgress"]')
|
||||
const years = getEmptyYears(true)
|
||||
const tableContents = [
|
||||
...years.map(year => [year, 0, 0, 0, 0, 0, 0]),
|
||||
['2023 - 2024', 1, 1, 0, 0, 0, 0],
|
||||
['2022 - 2023', 9, 1, 7, 1, 0, 0],
|
||||
['2021 - 2022', 3, 1, 1, 0, 0, 1],
|
||||
['2020 - 2021', 4, 0, 0, 2, 0, 2],
|
||||
['2019 - 2020', 9, 0, 0, 1, 4, 4],
|
||||
['2018 - 2019', 5, 0, 0, 0, 1, 4],
|
||||
['2017 - 2018', 2, 0, 0, 0, 0, 2],
|
||||
['Total', 33, 3, 8, 4, 5, 13],
|
||||
]
|
||||
cy.checkTableStats(tableContents, 'FacultyDoctoralProgress')
|
||||
})
|
||||
})
|
||||
|
||||
it('Years in the tables can be clicked to show programme level breakdown', () => {
|
||||
cy.get('[data-cy="Table-FacultyBachelorsProgress"]').within(() => {
|
||||
cy.contains('2021 - 2022').click()
|
||||
cy.contains('31.3%').trigger('mouseover', { force: true })
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma')
|
||||
cy.contains('MAT - KH50_001')
|
||||
cy.contains('0 Credits: 0')
|
||||
cy.contains('1 ≤ Credits < 45: 10')
|
||||
cy.contains('45 ≤ Credits < 90: 9')
|
||||
cy.contains('90 ≤ Credits < 135: 11')
|
||||
cy.contains('135 ≤ Credits < 180: 2')
|
||||
cy.contains('180 ≤ Credits: 0')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Graduation times stats', () => {
|
||||
it('All the correct sections are displayed', () => {
|
||||
const checkSections = breakdown => {
|
||||
const sections = ['bachelor', 'bcMsCombo', 'master', 'doctor']
|
||||
for (const section of sections) {
|
||||
cy.get(`[data-cy=Section-${section}]`).within(() => {
|
||||
cy.get('div.graduations-chart-container').within(() => {
|
||||
cy.get(`div.faculty-${breakdown ? 'breakdown-' : ''}graph`)
|
||||
cy.get('div.graduations-message').contains("Click a bar to view that year's programme level breakdown")
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
checkSections(true)
|
||||
cy.get('[data-cy=GraduationTimeToggle]').click()
|
||||
checkSections(false)
|
||||
})
|
||||
|
||||
it('Graduation times breakdown data is shown correctly', () => {
|
||||
cy.get('[data-cy=Section-bachelor]').within(() => {
|
||||
cy.contains('Graduation year')
|
||||
cy.contains('2022')
|
||||
|
||||
cy.get('[aria-label="2022, 29. On time."]').trigger('mouseover')
|
||||
cy.contains('On time: 29')
|
||||
|
||||
cy.get('[aria-label="2022, 29. On time."]').trigger('click')
|
||||
cy.contains('Year 2022 by graduation year')
|
||||
|
||||
cy.get('[aria-label="MAT, 22. On time."]').trigger('mouseover')
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma')
|
||||
cy.contains('KH50_001')
|
||||
cy.contains('On time: 22')
|
||||
|
||||
cy.get('[aria-label="MFKK, 7. On time."]').trigger('mouseover')
|
||||
cy.contains('Matematiikan, fysiikan ja kemian opettajan kandiohjelma')
|
||||
cy.contains('KH50_004')
|
||||
cy.contains('On time: 7')
|
||||
})
|
||||
})
|
||||
|
||||
it('Graduation times median time data is shown correctly', () => {
|
||||
cy.get('[data-cy=GraduationTimeToggle]').click()
|
||||
|
||||
cy.get('[data-cy=Section-bachelor]').within(() => {
|
||||
cy.contains('Graduation year')
|
||||
cy.contains('2021')
|
||||
|
||||
cy.contains('70 graduated').trigger('mouseover')
|
||||
cy.contains('70 students graduated in year 2021')
|
||||
cy.contains('median study time: 39.5 months')
|
||||
cy.contains('28 graduated on time')
|
||||
cy.contains('32 graduated max year overtime')
|
||||
cy.contains('10 graduated over year late')
|
||||
|
||||
cy.contains('70 graduated').trigger('click')
|
||||
cy.contains('Year 2021 by graduation year')
|
||||
|
||||
cy.get('[aria-label="MAT, 41."]').trigger('mouseover')
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma')
|
||||
cy.contains('KH50_001')
|
||||
cy.contains('47 students graduated in year 2021')
|
||||
cy.contains('median study time: 41 months')
|
||||
cy.contains('14 graduated on time')
|
||||
cy.contains('25 graduated max year overtime')
|
||||
cy.contains('8 graduated over year late')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,8 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const path = require('path')
|
||||
|
||||
const timestamp = new Date().toISOString().split('T')[0] // YYYY-MM-DD
|
||||
const timestamp = new Date().toLocaleDateString('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' }) // YYYY-MM-DD, taking the local time zone into account
|
||||
const downloadsFolder = Cypress.config('downloadsFolder')
|
||||
|
||||
describe('Faculty overview', () => {
|
||||
@@ -23,7 +23,7 @@ describe('Faculty overview', () => {
|
||||
})
|
||||
|
||||
it('Credits produced by faculty are shown', () => {
|
||||
cy.get('[data-cy="Section-CreditsProducedByTheFaculty"]').should('be.visible')
|
||||
cy.get('[data-cy="Section-creditsProducedByTheFaculty"]').should('be.visible')
|
||||
cy.get('[data-cy="Graph-CreditsProducedByTheFaculty"]').should('be.visible')
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('Faculty overview', () => {
|
||||
describe('Basic information: basic user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/faculties')
|
||||
cy.contains('a', 'Maatalous-metsätieteellinen tiedekunta').click()
|
||||
cy.contains('a', 'Matemaattis-luonnontieteellinen tiedekunta').click()
|
||||
})
|
||||
|
||||
it('Basic information tab show all graphs and tables', () => {
|
||||
@@ -75,14 +75,14 @@ describe('Faculty overview', () => {
|
||||
})
|
||||
|
||||
it('Students of the faculty infobox works', () => {
|
||||
cy.get('[data-cy="StudentsOfTheFaculty-info-content"]').should('not.exist')
|
||||
cy.get('[data-cy="StudentsOfTheFaculty-open-info"]').click()
|
||||
cy.get('[data-cy="StudentsOfTheFaculty-info-content"]').should('be.visible')
|
||||
cy.get('[data-cy="StudentsOfTheFaculty-close-info"]').click()
|
||||
cy.get('[data-cy="StudentsOfTheFaculty-info-content"]').should('not.exist')
|
||||
cy.get('[data-cy="studentsOfTheFaculty-info-content"]').should('not.exist')
|
||||
cy.get('[data-cy="studentsOfTheFaculty-open-info"]').click()
|
||||
cy.get('[data-cy="studentsOfTheFaculty-info-content"]').should('be.visible')
|
||||
cy.get('[data-cy="studentsOfTheFaculty-close-info"]').click()
|
||||
cy.get('[data-cy="studentsOfTheFaculty-info-content"]').should('not.exist')
|
||||
})
|
||||
|
||||
it('Data can be exported to Excel files', () => {
|
||||
it('Data can be exported to Excel files', { retries: 3 }, () => {
|
||||
const sections = [
|
||||
'StudentsOfTheFaculty',
|
||||
'GraduatedOfTheFaculty',
|
||||
@@ -91,9 +91,9 @@ describe('Faculty overview', () => {
|
||||
]
|
||||
sections.forEach(section => {
|
||||
cy.get(`[data-cy="Graph-${section}"]`).within(() => {
|
||||
cy.get('[aria-label="View chart menu, Chart"]').click()
|
||||
cy.get('[aria-label="View chart menu, Chart"]').click({ force: true })
|
||||
cy.contains('li.highcharts-menu-item', 'Download XLS').click()
|
||||
const downloadedFile = `oodikone_${section}_H80_${timestamp}.xls`
|
||||
const downloadedFile = `oodikone_${section}_H50_${timestamp}.xls`
|
||||
cy.readFile(path.join(downloadsFolder, downloadedFile))
|
||||
})
|
||||
})
|
||||
@@ -155,7 +155,7 @@ describe('Faculty overview', () => {
|
||||
describe('Average graduation times', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/faculties')
|
||||
cy.contains('a', 'Kasvatustieteellinen tiedekunta').click()
|
||||
cy.contains('a', 'Matemaattis-luonnontieteellinen tiedekunta').click()
|
||||
cy.contains('Graduation times').click()
|
||||
})
|
||||
|
||||
@@ -178,42 +178,51 @@ describe('Faculty overview', () => {
|
||||
|
||||
cy.get('[data-cy="Section-bachelor"]').within(() => {
|
||||
cy.get('div[class="faculty-graph"]')
|
||||
cy.contains('1 graduated').should('have.length', 1)
|
||||
cy.contains('1 graduated').trigger('mouseover')
|
||||
cy.contains('1 students graduated in year 2019')
|
||||
cy.contains('median study time: 40 months')
|
||||
cy.contains('0 graduated on time')
|
||||
cy.contains('1 graduated max year overtime')
|
||||
cy.contains('44 graduated').trigger('mouseover')
|
||||
cy.contains('44 students graduated in year 2023')
|
||||
cy.contains('median study time: 37.5 months')
|
||||
cy.contains('21 graduated on time')
|
||||
cy.contains('11 graduated max year overtime')
|
||||
cy.contains('12 graduated over year late')
|
||||
|
||||
cy.contains('1 graduated').click()
|
||||
cy.contains('Year 2019 by graduation year')
|
||||
cy.contains('44 graduated').click()
|
||||
cy.contains('Year 2023 by graduation year')
|
||||
cy.get('div[class="programmes-graph"]').should('be.visible')
|
||||
cy.get('div[class="programmes-graph"]').within(() => {
|
||||
cy.contains('EDUK')
|
||||
cy.get('[aria-label="EDUK, 40."]').trigger('mouseover')
|
||||
cy.contains('Kasvatustieteiden kandiohjelma')
|
||||
cy.contains('KH60_001')
|
||||
cy.contains('MAT')
|
||||
cy.get('[aria-label="MAT, 36."]').trigger('mouseover')
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma')
|
||||
cy.contains('KH50_001')
|
||||
cy.contains('26 students graduated in year 2023')
|
||||
cy.contains('median study time: 36 months')
|
||||
cy.contains('14 graduated on time')
|
||||
cy.contains('6 graduated max year overtime')
|
||||
cy.contains('6 graduated over year late')
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('[data-cy="Section-master"]').within(() => {
|
||||
cy.get('div[class="faculty-graph"]')
|
||||
cy.contains('24 graduated').should('have.length', 1)
|
||||
cy.contains('24 graduated').trigger('mouseover')
|
||||
cy.contains('24 students graduated in year 2020')
|
||||
cy.contains('median study time: 22 months')
|
||||
cy.contains('0 graduated over year late')
|
||||
cy.contains('20 graduated on time')
|
||||
cy.contains('4 graduated max year overtime')
|
||||
cy.contains('4 graduated').trigger('mouseover')
|
||||
cy.contains('4 students graduated in year 2023')
|
||||
cy.contains('median study time: 23 months')
|
||||
cy.contains('3 graduated on time')
|
||||
cy.contains('0 graduated max year overtime')
|
||||
cy.contains('1 graduated over year late')
|
||||
|
||||
cy.contains('24 graduated').click()
|
||||
cy.contains('Year 2020 by graduation year')
|
||||
cy.contains('4 graduated').click()
|
||||
cy.contains('Year 2023 by graduation year')
|
||||
cy.get('div[class="programmes-graph"]').should('be.visible')
|
||||
cy.get('div[class="programmes-graph"]').within(() => {
|
||||
cy.contains('EDUM')
|
||||
cy.get('[aria-label="EDUM, 22."]').trigger('mouseover')
|
||||
cy.contains('Kasvatustieteiden maisteriohjelma')
|
||||
cy.contains('MH60_001')
|
||||
cy.contains('MAST')
|
||||
cy.get('[aria-label="MAST, 23."]').trigger('mouseover')
|
||||
cy.contains('Matematiikan ja tilastotieteen maisteriohjelma')
|
||||
cy.contains('MH50_001')
|
||||
cy.contains('3 students graduated in year 2023')
|
||||
cy.contains('median study time: 23 months')
|
||||
cy.contains('2 graduated on time')
|
||||
cy.contains('0 graduated max year overtime')
|
||||
cy.contains('1 graduated over year late')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -221,33 +230,32 @@ describe('Faculty overview', () => {
|
||||
it('Graduation times grouping and time types can be toggled', () => {
|
||||
cy.get('[data-cy="GraduationTimeToggle"]').click()
|
||||
cy.get('[data-cy="GroupByToggle"]').click()
|
||||
cy.get('[data-cy="Section-bachelor"]').should('not.exist')
|
||||
cy.get('[data-cy="Section-master"]').should('be.visible')
|
||||
|
||||
cy.get('[data-cy="Section-master"]').within(() => {
|
||||
cy.get('[data-cy="Section-bachelor"]').within(() => {
|
||||
cy.get('div[class="faculty-graph"]')
|
||||
cy.contains('19 graduated (44.2 % of class)').should('have.length', 1)
|
||||
cy.contains('19 graduated').trigger('mouseover')
|
||||
cy.contains('From class of 2018, 19/43 students have graduated')
|
||||
cy.contains('24 graduated').trigger('mouseover')
|
||||
cy.contains('From class of 2020 - 2021, 24/37 students have graduated')
|
||||
})
|
||||
|
||||
cy.get('[data-cy="GraduationTimeToggle"]').click()
|
||||
cy.get('[data-cy="Section-master"]').within(() => {
|
||||
cy.get('[aria-label="2018, 16. On time."]').click()
|
||||
cy.get('[data-cy="Section-bachelor"]').within(() => {
|
||||
cy.get('[aria-label="2018 - 2019, 29. On time."]').click()
|
||||
cy.get('div[class="programmes-breakdown-graph"]').should('be.visible')
|
||||
cy.get('div[class="programmes-breakdown-graph"]').within(() => {
|
||||
cy.contains('Year 2018 by start year')
|
||||
cy.contains('EDUM')
|
||||
cy.get('[aria-label="EDUM, 3. Max. year overtime."]').trigger('mouseover')
|
||||
cy.contains('Kasvatustieteiden maisteriohjelma')
|
||||
cy.contains('MH60_001')
|
||||
cy.contains('Year 2018 - 2019 by start year')
|
||||
cy.contains('MAT')
|
||||
cy.get('[aria-label="MAT, 20. Max. year overtime."]').trigger('mouseover')
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma')
|
||||
cy.contains('KH50_001')
|
||||
cy.contains('Max. year overtime: 20')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('Data can be exported to an Excel file', () => {
|
||||
cy.get('[data-cy="DownloadButton-AverageGraduationTimes"]').click()
|
||||
const downloadedFile = `oodikone_H60_graduation_times_${timestamp}.xlsx`
|
||||
const downloadedFile = `oodikone_H50_graduation_times_${timestamp}.xlsx`
|
||||
cy.readFile(path.join(downloadsFolder, downloadedFile))
|
||||
})
|
||||
})
|
||||
|
||||
+110
-108
@@ -1,6 +1,6 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const MOCKED_DATE = new Date(2022, 6, 12).getTime()
|
||||
const MOCKED_DATE = new Date('2024-08-30').getTime()
|
||||
|
||||
const checkFilteringResult = studentCount => {
|
||||
cy.contains(`Students (${studentCount})`)
|
||||
@@ -18,7 +18,6 @@ const clearSingleDropdownSelection = dataCyAttribute => {
|
||||
}
|
||||
|
||||
const testRangeFilter = (parentEl, min, max, expected) => {
|
||||
cy.clock(MOCKED_DATE, ['Date'])
|
||||
cy.cs(parentEl)
|
||||
.cs('range-selector-min')
|
||||
.find('input')
|
||||
@@ -29,13 +28,17 @@ const testRangeFilter = (parentEl, min, max, expected) => {
|
||||
.find('input')
|
||||
.invoke('val')
|
||||
.then(initialMax => {
|
||||
cy.cs(parentEl).cs('range-selector-min').find('input').type(`{selectall}{backspace}${min}`).blur()
|
||||
cy.cs(parentEl).cs('range-selector-max').find('input').type(`{selectall}{backspace}${max}`).blur()
|
||||
cy.cs(parentEl).cs('range-selector-min').find('input').clear()
|
||||
cy.cs(parentEl).cs('range-selector-min').find('input').type(min)
|
||||
cy.cs(parentEl).cs('range-selector-max').find('input').clear()
|
||||
cy.cs(parentEl).cs('range-selector-max').find('input').type(max)
|
||||
|
||||
checkFilteringResult(expected)
|
||||
|
||||
cy.cs(parentEl).cs('range-selector-min').find('input').type(`{selectall}{backspace}${initialMin}`).blur()
|
||||
cy.cs(parentEl).cs('range-selector-max').find('input').type(`{selectall}{backspace}${initialMax}`).blur()
|
||||
cy.cs(parentEl).cs('range-selector-min').find('input').clear()
|
||||
cy.cs(parentEl).cs('range-selector-min').find('input').type(initialMin)
|
||||
cy.cs(parentEl).cs('range-selector-max').find('input').clear()
|
||||
cy.cs(parentEl).cs('range-selector-max').find('input').type(initialMax)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -65,45 +68,45 @@ const createRunTestStepWithPreAndPostPartsFunction = amountWithoutFiltering => {
|
||||
}
|
||||
}
|
||||
|
||||
describe('Population Statistics with CS master', () => {
|
||||
const pathToCSMast2019 =
|
||||
'/populations?months=36&semesters=FALL&semesters=SPRING&studyRights={%22programme%22%3A%22MH50_009%22}&tag&year=2019'
|
||||
const defaultAmountOfStudents = 27
|
||||
describe("Population statistics with a master's programme", () => {
|
||||
const pathToMathMSc2020 =
|
||||
'/populations?months=49&semesters=FALL&semesters=SPRING&studyRights=%7B"programme"%3A"MH50_001"%7D&year=2020'
|
||||
const defaultAmountOfStudents = 26
|
||||
const runTestStepWithPreAndPostParts = createRunTestStepWithPreAndPostPartsFunction(defaultAmountOfStudents)
|
||||
beforeEach(() => {
|
||||
cy.init(pathToCSMast2019)
|
||||
cy.init(pathToMathMSc2020)
|
||||
})
|
||||
|
||||
it('Study track filter works', () => {
|
||||
runTestStepWithPreAndPostParts('StudyTrack', () => {
|
||||
const card = cy.cs('StudyTrack-filter-card')
|
||||
const programmeDropdown = card.cs('StudyTrack-filter-dropdown').selectFromDropdown(0)
|
||||
checkFilteringResult(1)
|
||||
checkFilteringResult(15)
|
||||
programmeDropdown.get('i.delete').click()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Studyright type filter', () => {
|
||||
describe('Study right type filter', () => {
|
||||
it('is visible', () => {
|
||||
cy.contains('Studyright type')
|
||||
cy.contains('Study right type')
|
||||
})
|
||||
|
||||
it('is set to all by default', () => {
|
||||
runTestStepWithPreAndPostParts('studyright-type', () => {
|
||||
const card = cy.cs('studyright-type-filter-card')
|
||||
runTestStepWithPreAndPostParts('studyRightTypeFilter', () => {
|
||||
const card = cy.cs('studyRightTypeFilter-filter-card')
|
||||
card.get('[data-cy="all"] input').should('be.checked')
|
||||
})
|
||||
})
|
||||
|
||||
it('works', () => {
|
||||
runTestStepWithPreAndPostParts('studyright-type', () => {
|
||||
const expectedBaMaStudents = 27
|
||||
const expectedMasterOnlyStudents = 0
|
||||
cy.cs('studyright-type-filter-card').cs('bama').click()
|
||||
runTestStepWithPreAndPostParts('studyRightTypeFilter', () => {
|
||||
const expectedBaMaStudents = 24
|
||||
const expectedMasterOnlyStudents = 2
|
||||
cy.cs('studyRightTypeFilter-filter-card').cs('bama').click()
|
||||
checkFilteringResult(expectedBaMaStudents)
|
||||
cy.cs('studyright-type-filter-card').cs('master').click()
|
||||
cy.cs('studyRightTypeFilter-filter-card').cs('master').click()
|
||||
checkFilteringResult(expectedMasterOnlyStudents)
|
||||
cy.cs('studyright-type-filter-card').cs('all').click()
|
||||
cy.cs('studyRightTypeFilter-filter-card').cs('all').click()
|
||||
checkFilteringResult(defaultAmountOfStudents)
|
||||
})
|
||||
})
|
||||
@@ -111,16 +114,16 @@ describe('Population Statistics with CS master', () => {
|
||||
})
|
||||
|
||||
describe('Population Statistics', () => {
|
||||
const pathToCSBach2018 =
|
||||
'/populations?months=36&semesters=FALL&semesters=SPRING&studyRights={%22programme%22%3A%22KH50_005%22}&tag&year=2018'
|
||||
const defaultAmountOfStudents = 159
|
||||
const pathToMathBSc2020 =
|
||||
'/populations?months=49&semesters=FALL&semesters=SPRING&studyRights=%7B"programme"%3A"KH50_001"%7D&year=2020'
|
||||
const defaultAmountOfStudents = 27
|
||||
const runTestStepWithPreAndPostParts = createRunTestStepWithPreAndPostPartsFunction(defaultAmountOfStudents)
|
||||
|
||||
beforeEach(() => {
|
||||
cy.init(pathToCSBach2018)
|
||||
cy.init(pathToMathBSc2020)
|
||||
})
|
||||
|
||||
it('Transfer filter is on not transferred by default', () => {
|
||||
it("Transfer filter is set to 'not transferred' by default", () => {
|
||||
cy.contains('By default only students who have not transferred to this study programme are shown.')
|
||||
|
||||
runTestStepWithPreAndPostParts('TransferredToProgramme', () => {
|
||||
@@ -133,7 +136,7 @@ describe('Population Statistics', () => {
|
||||
runTestStepWithPreAndPostParts('GraduatedFromProgramme', () => {
|
||||
const getCard = () => cy.cs('GraduatedFromProgramme-filter-card')
|
||||
|
||||
const graduated = 11
|
||||
const graduated = 16
|
||||
getCard().cs('option-graduated-true').click()
|
||||
checkFilteringResult(graduated)
|
||||
getCard().cs('option-graduated-false').click()
|
||||
@@ -142,45 +145,45 @@ describe('Population Statistics', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// it('Transfer filter works', () => {
|
||||
// runTestStepWithPreAndPostParts('TransferredToProgramme', () => {
|
||||
// const transferred = 1
|
||||
// cy.cs('TransferredToProgramme-filter-card').cs('option-have').click()
|
||||
// checkFilteringResult(transferred)
|
||||
// cy.cs('TransferredToProgramme-filter-card').cs('option-all').click()
|
||||
// checkFilteringResult(defaultAmountOfStudents + transferred)
|
||||
// cy.cs('TransferredToProgramme-filter-card').cs('option-havenot').click()
|
||||
// })
|
||||
// })
|
||||
it('Transfer filter works', () => {
|
||||
runTestStepWithPreAndPostParts('TransferredToProgramme', () => {
|
||||
const transferred = 3
|
||||
cy.cs('TransferredToProgramme-filter-card').cs('option-have').click()
|
||||
checkFilteringResult(transferred)
|
||||
cy.cs('TransferredToProgramme-filter-card').cs('option-all').click()
|
||||
checkFilteringResult(defaultAmountOfStudents + transferred)
|
||||
cy.cs('TransferredToProgramme-filter-card').cs('option-havenot').click()
|
||||
})
|
||||
})
|
||||
|
||||
it('Enrollment filter works', () => {
|
||||
runTestStepWithPreAndPostParts('EnrollmentStatus', () => {
|
||||
cy.cs('enrollmentStatusFilter-status').selectFromDropdown(0)
|
||||
cy.cs('enrollmentStatusFilter-semesters').selectFromDropdown(5)
|
||||
checkFilteringResult(148)
|
||||
checkFilteringResult(25)
|
||||
clearSemanticUIMultipleDropDownSelection('enrollmentStatusFilter-semesters')
|
||||
})
|
||||
})
|
||||
|
||||
it('Credit filter works', { retries: 3 }, () => {
|
||||
runTestStepWithPreAndPostParts('CreditsEarned', () => {
|
||||
testRangeFilter('CreditsEarned-filter-card', 50, 150, 118)
|
||||
testRangeFilter('CreditsEarned-filter-card', 50, 150, 12)
|
||||
})
|
||||
})
|
||||
|
||||
it('Age filter works', { retries: 3 }, () => {
|
||||
cy.clock(MOCKED_DATE, ['Date'])
|
||||
runTestStepWithPreAndPostParts('Age', () => {
|
||||
testRangeFilter('Age-filter-card', 20, 40, 47)
|
||||
testRangeFilter('Age-filter-card', 23, 30, 21)
|
||||
})
|
||||
})
|
||||
|
||||
it('Gender filter works', () => {
|
||||
runTestStepWithPreAndPostParts('Gender', () => {
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(0)
|
||||
checkFilteringResult(42)
|
||||
checkFilteringResult(13)
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(1)
|
||||
checkFilteringResult(117)
|
||||
checkFilteringResult(14)
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(2)
|
||||
checkFilteringResult(0)
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(3)
|
||||
@@ -194,38 +197,37 @@ describe('Population Statistics', () => {
|
||||
cy.cs('startYearAtUni-dropdown').selectFromDropdown(0)
|
||||
checkFilteringResult(1)
|
||||
clearSemanticUIMultipleDropDownSelection('startYearAtUni-dropdown')
|
||||
cy.cs('startYearAtUni-dropdown').selectFromDropdown(14)
|
||||
checkFilteringResult(136)
|
||||
cy.cs('startYearAtUni-dropdown').selectFromDropdown(6)
|
||||
checkFilteringResult(21)
|
||||
clearSemanticUIMultipleDropDownSelection('startYearAtUni-dropdown')
|
||||
})
|
||||
})
|
||||
|
||||
// Return when we have +2020 population with admission types in data
|
||||
it.skip('Admission type filter works', () => {
|
||||
runTestStepWithPreAndPostParts('AdmissionTypeFilter', () => {
|
||||
it('Admission type filter works', () => {
|
||||
runTestStepWithPreAndPostParts('AdmissionType', () => {
|
||||
cy.cs('admissionTypeFilter-dropdown').selectFromDropdown(0)
|
||||
checkFilteringResult(5)
|
||||
cy.cs('admissionTypeFilter-dropdown').selectFromDropdown(2)
|
||||
checkFilteringResult(10)
|
||||
checkFilteringResult(18)
|
||||
cy.cs('admissionTypeFilter-dropdown').selectFromDropdown(1)
|
||||
checkFilteringResult(6)
|
||||
clearSingleDropdownSelection('admissionTypeFilter-dropdown')
|
||||
})
|
||||
})
|
||||
|
||||
it('Courses filter works', () => {
|
||||
runTestStepWithPreAndPostParts('Courses', () => {
|
||||
const courses = ['TKT20002', 'MAT11002']
|
||||
cy.cs('courseFilter-course-dropdown').click().contains(`${courses[0]} - Ohjelmistotekniikka`).click()
|
||||
checkFilteringResult(133)
|
||||
cy.cs(`courseFilter-${courses[0]}-dropdown`).selectFromDropdown(1)
|
||||
checkFilteringResult(127)
|
||||
cy.cs('courseFilter-course-dropdown')
|
||||
.click()
|
||||
.contains(`${courses[1]} - Lineaarialgebra ja matriisilaskenta I`)
|
||||
.click()
|
||||
checkFilteringResult(100)
|
||||
cy.cs(`courseFilter-${courses[1]}-dropdown`).selectFromDropdown(2)
|
||||
checkFilteringResult(1)
|
||||
courses.forEach(course => cy.cs(`courseFilter-${course}-clear`).click())
|
||||
const courses = [
|
||||
{ code: 'MAT21012', name: 'Differentiaaliyhtälöt I' },
|
||||
{ code: 'MFK-M204', name: 'Matematiikkaa kaikkialla' },
|
||||
]
|
||||
cy.cs('courseFilter-course-dropdown').click().contains(`${courses[0].code} - ${courses[0].name}`).click()
|
||||
checkFilteringResult(10)
|
||||
cy.cs(`courseFilter-${courses[0].code}-dropdown`).selectFromDropdown(1) // Passed students
|
||||
checkFilteringResult(8)
|
||||
cy.cs('courseFilter-course-dropdown').click().contains(`${courses[1].code} - ${courses[1].name}`).click()
|
||||
checkFilteringResult(6)
|
||||
cy.cs(`courseFilter-${courses[1].code}-dropdown`).selectFromDropdown(1)
|
||||
checkFilteringResult(5)
|
||||
courses.forEach(({ code }) => cy.cs(`courseFilter-${code}-clear`).click())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -235,7 +237,7 @@ describe('Population Statistics', () => {
|
||||
runTestStepWithPreAndPostParts('Age', () => {
|
||||
const getCard = () => cy.cs('GraduatedFromProgramme-filter-card')
|
||||
getCard().cs('option-graduated-true').click()
|
||||
testRangeFilter('Age-filter-card', 20, 30, 1)
|
||||
testRangeFilter('Age-filter-card', 23, 30, 12)
|
||||
getCard().cs('option-all').click()
|
||||
})
|
||||
})
|
||||
@@ -244,35 +246,35 @@ describe('Population Statistics', () => {
|
||||
it('"Reset All Filters" button works', { retries: 3 }, () => {
|
||||
cy.cs('Gender-header').click()
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(0)
|
||||
checkFilteringResult(42)
|
||||
checkFilteringResult(13)
|
||||
cy.cs('StartYearAtUni-header').click()
|
||||
cy.cs('startYearAtUni-dropdown').selectFromDropdown(7)
|
||||
checkFilteringResult(33)
|
||||
cy.cs('startYearAtUni-dropdown').selectFromDropdown(5)
|
||||
checkFilteringResult(8)
|
||||
cy.cs('reset-all-filters').click()
|
||||
checkFilteringResult(defaultAmountOfStudents)
|
||||
})
|
||||
|
||||
it('Studyright type filter is not visible', () => {
|
||||
cy.contains('Studyright type').should('not.exist')
|
||||
it('Study right type filter is not visible', () => {
|
||||
cy.contains('Study right type').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Course Statistics', () => {
|
||||
const pathToDSAndAlgoSpring2019 =
|
||||
'/coursepopulation?coursecodes=%5B%22TKT20001%22%2C%2258131%22%5D&from=138&separate=true&to=138&years=Kev%C3%A4t%202019&unifyCourses=unifyStats'
|
||||
const defaultAmountOfStudents = 118
|
||||
const pathToLimits2021 =
|
||||
'/coursepopulation?coursecodes=%5B"MAT11003"%2C"57116"%2C"57016"%2C"AYMAT11003"%5D&from=72&separate=false&to=72&unifyCourses=unifyStats&years=2021-2022'
|
||||
const defaultAmountOfStudents = 48
|
||||
const runTestStepWithPreAndPostParts = createRunTestStepWithPreAndPostPartsFunction(defaultAmountOfStudents)
|
||||
|
||||
beforeEach(() => {
|
||||
cy.init(pathToDSAndAlgoSpring2019)
|
||||
cy.init(pathToLimits2021)
|
||||
})
|
||||
|
||||
it('Grade filter works', () => {
|
||||
runTestStepWithPreAndPostParts('Grade', () => {
|
||||
cy.cs('gradeFilter-5').click()
|
||||
checkFilteringResult(20)
|
||||
checkFilteringResult(23)
|
||||
cy.cs('gradeFilter-3').click()
|
||||
checkFilteringResult(38)
|
||||
checkFilteringResult(29)
|
||||
cy.cs('gradeFilter-3').click()
|
||||
cy.cs('gradeFilter-5').click()
|
||||
})
|
||||
@@ -288,24 +290,24 @@ describe('Course Statistics', () => {
|
||||
runTestStepWithPreAndPostParts('Programme', () => {
|
||||
const card = cy.cs('Programme-filter-card')
|
||||
const programmeDropdown = card.cs('Programme-filter-dropdown').selectFromDropdown(1)
|
||||
checkFilteringResult(116)
|
||||
checkFilteringResult(33)
|
||||
programmeDropdown.get('i.delete').click()
|
||||
})
|
||||
})
|
||||
|
||||
it('Age filter works', () => {
|
||||
it('Age filter works', { retries: 3 }, () => {
|
||||
cy.clock(MOCKED_DATE, ['Date'])
|
||||
runTestStepWithPreAndPostParts('Age', () => {
|
||||
testRangeFilter('Age-filter-card', 20, 40, 33)
|
||||
testRangeFilter('Age-filter-card', 21, 30, 36)
|
||||
})
|
||||
})
|
||||
|
||||
it('Gender filter works', () => {
|
||||
runTestStepWithPreAndPostParts('Gender', () => {
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(0)
|
||||
checkFilteringResult(27)
|
||||
checkFilteringResult(9)
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(1)
|
||||
checkFilteringResult(91)
|
||||
checkFilteringResult(39)
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(2)
|
||||
checkFilteringResult(0)
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(3)
|
||||
@@ -316,11 +318,11 @@ describe('Course Statistics', () => {
|
||||
|
||||
it('Starting year filter works', () => {
|
||||
runTestStepWithPreAndPostParts('StartYearAtUni', () => {
|
||||
cy.cs('startYearAtUni-dropdown').selectFromDropdown([0])
|
||||
cy.cs('startYearAtUni-dropdown').selectFromDropdown(0)
|
||||
checkFilteringResult(1)
|
||||
clearSemanticUIMultipleDropDownSelection('startYearAtUni-dropdown')
|
||||
cy.cs('startYearAtUni-dropdown').selectFromDropdown(13)
|
||||
checkFilteringResult(71)
|
||||
cy.cs('startYearAtUni-dropdown').selectFromDropdown(10)
|
||||
checkFilteringResult(24)
|
||||
clearSemanticUIMultipleDropDownSelection('startYearAtUni-dropdown')
|
||||
})
|
||||
})
|
||||
@@ -329,9 +331,10 @@ describe('Course Statistics', () => {
|
||||
cy.clock(MOCKED_DATE, ['Date'])
|
||||
runTestStepWithPreAndPostParts('Grade', () => {
|
||||
runTestStepWithPreAndPostParts('Age', () => {
|
||||
cy.cs('gradeFilter-3').click()
|
||||
testRangeFilter('Age-filter-card', 20, 30, 1)
|
||||
cy.cs('gradeFilter-3').click()
|
||||
cy.cs('gradeFilter-5').click()
|
||||
checkFilteringResult(23)
|
||||
testRangeFilter('Age-filter-card', 21, 30, 17)
|
||||
cy.cs('gradeFilter-5').click()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -354,7 +357,7 @@ describe('Custom Population Statistics', () => {
|
||||
it('Age filter works', () => {
|
||||
cy.clock(MOCKED_DATE, ['Date'])
|
||||
runTestStepWithPreAndPostParts('Age', () => {
|
||||
testRangeFilter('Age-filter-card', 20, 30, 1)
|
||||
testRangeFilter('Age-filter-card', 24, 28, 5)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -367,8 +370,8 @@ describe('Custom Population Statistics', () => {
|
||||
it('Programme filter works', () => {
|
||||
runTestStepWithPreAndPostParts('Programme', () => {
|
||||
const card = cy.cs('Programme-filter-card')
|
||||
const programmeDropdown = card.cs('Programme-filter-dropdown').selectFromDropdown(0)
|
||||
checkFilteringResult(5)
|
||||
const programmeDropdown = card.cs('Programme-filter-dropdown').selectFromDropdown(1)
|
||||
checkFilteringResult(3)
|
||||
programmeDropdown.get('i.delete').click()
|
||||
})
|
||||
})
|
||||
@@ -376,39 +379,38 @@ describe('Custom Population Statistics', () => {
|
||||
it('Gender filter works', () => {
|
||||
runTestStepWithPreAndPostParts('Gender', () => {
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(0)
|
||||
checkFilteringResult(3)
|
||||
checkFilteringResult(5)
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(1)
|
||||
checkFilteringResult(2)
|
||||
checkFilteringResult(3)
|
||||
cy.cs('genderFilter-dropdown').selectFromDropdown(2)
|
||||
checkFilteringResult(0)
|
||||
clearSingleDropdownSelection('genderFilter-dropdown')
|
||||
checkFilteringResult(5, true)
|
||||
})
|
||||
})
|
||||
|
||||
it('Starting year filter works', () => {
|
||||
runTestStepWithPreAndPostParts('StartYearAtUni', () => {
|
||||
cy.cs('startYearAtUni-dropdown').selectFromDropdown([0])
|
||||
checkFilteringResult(1)
|
||||
cy.cs('startYearAtUni-dropdown').selectFromDropdown(0)
|
||||
checkFilteringResult(2)
|
||||
clearSemanticUIMultipleDropDownSelection('startYearAtUni-dropdown')
|
||||
cy.cs('startYearAtUni-dropdown').click()
|
||||
})
|
||||
})
|
||||
|
||||
it('Courses filter works', () => {
|
||||
runTestStepWithPreAndPostParts('Courses', () => {
|
||||
const courses = ['MAT11001', 'TKT20004']
|
||||
cy.cs('courseFilter-course-dropdown').click().contains(`${courses[0]} - Johdatus yliopistomatematiikkaan`).click()
|
||||
const courses = [
|
||||
{ code: 'MAT11005', name: 'Integraalilaskenta' },
|
||||
{ code: 'MAT21007', name: 'Mitta ja integraali' },
|
||||
]
|
||||
cy.cs('courseFilter-course-dropdown').click().contains(`${courses[0].code} - ${courses[0].name}`).click()
|
||||
checkFilteringResult(7)
|
||||
cy.cs(`courseFilter-${courses[0].code}-dropdown`).selectFromDropdown(1)
|
||||
checkFilteringResult(6)
|
||||
cy.cs('courseFilter-course-dropdown').click().contains(`${courses[1].code} - ${courses[1].name}`).click()
|
||||
checkFilteringResult(5)
|
||||
cy.cs(`courseFilter-${courses[0]}-dropdown`).selectFromDropdown(1)
|
||||
checkFilteringResult(5)
|
||||
cy.cs('courseFilter-course-dropdown').click().contains(`${courses[1]} - Tietoliikenteen perusteet`).click()
|
||||
checkFilteringResult(5)
|
||||
cy.cs(`courseFilter-${courses[1]}-dropdown`).selectFromDropdown(1)
|
||||
checkFilteringResult(3)
|
||||
cy.cs(`courseFilter-${courses[1]}-dropdown`).selectFromDropdown(2)
|
||||
checkFilteringResult(1)
|
||||
courses.forEach(course => cy.cs(`courseFilter-${course}-clear`).click())
|
||||
cy.cs(`courseFilter-${courses[1].code}-dropdown`).selectFromDropdown(3)
|
||||
checkFilteringResult(2)
|
||||
courses.forEach(({ code }) => cy.cs(`courseFilter-${code}-clear`).click())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,58 +1,116 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const hasLanded = () => {
|
||||
cy.contains('This view displays amounts')
|
||||
cy.contains('Show number of')
|
||||
}
|
||||
|
||||
/*
|
||||
TODO FIX
|
||||
These use dumb numbers, because actual_studyright update in db is not yet
|
||||
in anon data, and the faculties do not get resolved.
|
||||
*/
|
||||
const chooseSemester = (semester, fromOrTo) => {
|
||||
cy.get(`[data-cy="semester-${fromOrTo}"]`)
|
||||
.click()
|
||||
.within(() => {
|
||||
cy.get('div.visible.menu.transition').within(() => {
|
||||
cy.contains('div.item', semester).click()
|
||||
})
|
||||
cy.get('div.menu.transition').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 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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe('When language center is opened', () => {
|
||||
describe('as an admin user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/languagecenterview', 'admin')
|
||||
cy.get('[data-cy="completions-button"]').click()
|
||||
cy.get('[data-cy="semester-from"]').click()
|
||||
cy.contains('Syksy 2017').click()
|
||||
cy.get('[data-cy="semester-to"]').click()
|
||||
hasLanded()
|
||||
})
|
||||
|
||||
it('Initial view is correct', () => {
|
||||
cy.contains('All courses total')
|
||||
cy.contains('Academic and Professional')
|
||||
cy.contains('AYKK-RUKIRJ')
|
||||
describe('Faculties tab', () => {
|
||||
beforeEach(() => {
|
||||
chooseSemester('Syksy 2017', 'from')
|
||||
chooseSemester('Kevät 2024', 'to')
|
||||
})
|
||||
|
||||
it('Initial view is correct', () => {
|
||||
cy.contains('All courses total')
|
||||
cy.contains('Academic and Professional')
|
||||
cy.contains('AYKK-RUKIRJ')
|
||||
})
|
||||
|
||||
it('Faculties tab shows numbers', () => {
|
||||
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, 34, 755, 15, 40, 6, 48, 1, 24, 1059, 49, 0, 2076]
|
||||
numbers.forEach((number, index) => {
|
||||
cy.get('td')
|
||||
.eq(index + 1)
|
||||
.contains(number)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('Faculties tab "exceeding" button works', () => {
|
||||
cy.get('[data-cy="difference-button"]').click()
|
||||
checkNumbers([...new Array(12).fill(0), 1, 227], 15)
|
||||
})
|
||||
|
||||
it('Faculties tab semester selector changes numbers', () => {
|
||||
chooseSemester('Syksy 2020', 'from')
|
||||
checkNumbers([28, 6, 28, 495, 4, 10, 2, 33, 0, 9, 535, 34, 0, 1184], 15)
|
||||
})
|
||||
})
|
||||
|
||||
it('Faculties tab shows numbers', () => {
|
||||
cy.contains('All courses total')
|
||||
cy.contains('1363')
|
||||
})
|
||||
describe('Semester tab', () => {
|
||||
beforeEach(() => {
|
||||
cy.contains('By semesters').click()
|
||||
chooseSemester('Syksy 2017', 'from')
|
||||
chooseSemester('Kevät 2024', 'to')
|
||||
})
|
||||
|
||||
it('Faculties tab "exceeding" button works', () => {
|
||||
cy.get('[data-cy="difference-button"]').click()
|
||||
cy.contains('0')
|
||||
cy.contains('Total')
|
||||
cy.contains('1363').should('not.exist')
|
||||
})
|
||||
it('Semester tab shows numbers', () => {
|
||||
checkNumbers([69, 26, 298, 58, 343, 98, 438, 138, 310, 90, 123, 74, 10, 1, 2076], 17, 'semesters')
|
||||
})
|
||||
|
||||
it('Faculties tab semester selector changes numbers', () => {
|
||||
cy.get('[data-cy="semester-from"]').click()
|
||||
cy.contains('Syksy 2020').click()
|
||||
cy.contains('1363').should('not.exist')
|
||||
cy.contains('All courses total')
|
||||
})
|
||||
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]
|
||||
|
||||
it('Semester tab opens and contains coloring filter', () => {
|
||||
cy.contains('By semesters').click()
|
||||
cy.contains('1363')
|
||||
cy.contains('Coloring mode')
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a user with no rights', () => {
|
||||
it('"Access denied" is shown', () => {
|
||||
cy.init('/languagecenterview', 'norights')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const deleteAllSearches = () => {
|
||||
cy.contains('Saved populations')
|
||||
@@ -7,8 +7,8 @@ const deleteAllSearches = () => {
|
||||
.parent()
|
||||
.parent()
|
||||
.get('.dropdown')
|
||||
.then(d => {
|
||||
const searchItems = d.find('div[role=option] > span[class=text]')
|
||||
.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).type('{enter}')
|
||||
|
||||
@@ -1,72 +1,89 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
// Now "Class statistics" in UI
|
||||
|
||||
describe('Population Statistics tests', () => {
|
||||
const pathToCSBach2017 =
|
||||
'/populations?months=36&semesters=FALL&semesters=SPRING&studyRights={%22programme%22%3A%22KH50_005%22}&tag&year=2017'
|
||||
const pathToCSMaster2019 =
|
||||
'/populations?months=27&semesters=FALL&semesters=SPRING&studyRights=%7B%22programme%22%3A%22MH50_009%22%7D&tag&year=2019'
|
||||
describe('when using basic user', () => {
|
||||
const selectStudyProgramme = programme => {
|
||||
cy.contains('Select study programme')
|
||||
cy.get('[data-cy=select-study-programme]').click().children().contains(programme).click()
|
||||
}
|
||||
|
||||
const selectStudyTrack = studyTrack => {
|
||||
cy.contains('Select study track')
|
||||
cy.get('[data-cy=select-study-track]').click().children().contains(studyTrack).click()
|
||||
}
|
||||
|
||||
describe('Population statistics tests', () => {
|
||||
const getPath = programme => {
|
||||
return `/populations?months=49&semesters=FALL&semesters=SPRING&studyRights=%7B"programme"%3A"${programme}"%7D&year=2020`
|
||||
}
|
||||
|
||||
const pathToMathBSc2020 = getPath('KH50_001')
|
||||
const pathToMathMSc2020 = getPath('MH50_001')
|
||||
|
||||
describe('When using basic user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/populations')
|
||||
})
|
||||
|
||||
it('Population statistics search infobox works', () => {
|
||||
cy.get('[data-cy="PopulationSearch-info-content"]').should('not.exist')
|
||||
cy.get('[data-cy="PopulationSearch-open-info"]').click()
|
||||
cy.get('[data-cy="PopulationSearch-info-content"]').should('be.visible')
|
||||
cy.get('[data-cy="PopulationSearch-info-content"]').should(
|
||||
'contain',
|
||||
'lukuvuosi, jolloin opiskelija on ilmoittautunut'
|
||||
)
|
||||
cy.get('[data-cy="PopulationSearch-close-info"]').click()
|
||||
cy.get('[data-cy="PopulationSearch-info-content"]').should('not.exist')
|
||||
})
|
||||
describe('Population search', () => {
|
||||
it('Info box works', () => {
|
||||
cy.get('[data-cy="PopulationSearch-info-content"]').should('not.exist')
|
||||
cy.get('[data-cy="PopulationSearch-open-info"]').click()
|
||||
cy.get('[data-cy="PopulationSearch-info-content"]').should('be.visible')
|
||||
cy.get('[data-cy="PopulationSearch-info-content"]').should('contain', 'Tässä osiossa voi tarkastella')
|
||||
cy.get('[data-cy="PopulationSearch-close-info"]').click()
|
||||
cy.get('[data-cy="PopulationSearch-info-content"]').should('not.exist')
|
||||
})
|
||||
|
||||
it('Population statistics search form is usable', () => {
|
||||
cy.contains('See class').should('be.disabled')
|
||||
cy.contains('Search for class')
|
||||
cy.contains('Class of')
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get('.form-control').as('enrollmentSelect')
|
||||
it('Form is usable', () => {
|
||||
cy.contains('Search for class')
|
||||
cy.contains('See class').should('be.disabled')
|
||||
cy.contains('Class of')
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get('.form-control').as('enrollmentSelect')
|
||||
})
|
||||
|
||||
cy.get('@enrollmentSelect')
|
||||
.its(`${[0]}.value`)
|
||||
.then(beforeVal => {
|
||||
cy.get('@enrollmentSelect').click()
|
||||
cy.get('.yearSelectInput .rdtPrev').click({ force: true })
|
||||
cy.get('.yearSelectInput table').contains('2018-2019').click({ force: true })
|
||||
cy.get('@enrollmentSelect').should('not.have.value', beforeVal)
|
||||
})
|
||||
|
||||
cy.contains('No study tracks available for this programme')
|
||||
selectStudyProgramme('Matematiikan ja tilastotieteen maisteriohjelma')
|
||||
cy.contains('See class').should('be.enabled')
|
||||
selectStudyTrack('Matematiikka ja soveltava matematiikka')
|
||||
cy.contains('See class').should('be.enabled')
|
||||
})
|
||||
|
||||
describe('Correct population is shown for programme', () => {
|
||||
it('without study tracks', () => {
|
||||
cy.contains('Select study programme')
|
||||
selectStudyProgramme('Matemaattisten tieteiden kandiohjelma')
|
||||
cy.contains('See class').click()
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma 2017 - 2018')
|
||||
cy.contains('class size 47 students')
|
||||
})
|
||||
|
||||
cy.get('@enrollmentSelect')
|
||||
.its(`${[0]}.value`)
|
||||
.then(beforeVal => {
|
||||
cy.get('@enrollmentSelect').click()
|
||||
cy.get('.yearSelectInput .rdtPrev').click({ force: true })
|
||||
cy.get('.yearSelectInput table').contains('2018-2019').click({ force: true })
|
||||
cy.get('@enrollmentSelect').should('not.have.value', beforeVal)
|
||||
it('with study tracks', () => {
|
||||
selectStudyProgramme('Matematiikan ja tilastotieteen maisteriohjelma')
|
||||
selectStudyTrack('Matematiikka ja soveltava matematiikka')
|
||||
cy.contains('See class').click()
|
||||
cy.contains('Matematiikan ja tilastotieteen maisteriohjelma 2017 - 2018')
|
||||
cy.contains('studytrack MAST-MSM')
|
||||
cy.contains('class size 1 students')
|
||||
})
|
||||
|
||||
cy.contains('Select study programme')
|
||||
cy.get('[data-cy=select-study-programme]')
|
||||
.click()
|
||||
.children()
|
||||
.contains('Tietojenkäsittelytieteen maisteriohjelma')
|
||||
.click()
|
||||
cy.contains('See class').should('be.enabled')
|
||||
})
|
||||
|
||||
it('Searching for population really shows population', () => {
|
||||
cy.contains('Select study programme')
|
||||
cy.get('[data-cy=select-study-programme]')
|
||||
.click()
|
||||
.children()
|
||||
.contains('Tietojenkäsittelytieteen kandiohjelma')
|
||||
.click()
|
||||
cy.contains('See class').click()
|
||||
cy.contains('class size')
|
||||
cy.contains('Tietojenkäsittelytieteen kandiohjelma')
|
||||
})
|
||||
})
|
||||
|
||||
it('Population statistics is usable on general level', () => {
|
||||
cy.visit(pathToCSBach2017)
|
||||
cy.visit(pathToMathBSc2020)
|
||||
cy.cs('filtered-students')
|
||||
cy.contains('Tietojenkäsittelytieteen kandiohjelma')
|
||||
cy.contains('class size 170 students')
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma')
|
||||
cy.contains('class size 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')
|
||||
@@ -74,66 +91,115 @@ describe('Population Statistics tests', () => {
|
||||
})
|
||||
|
||||
it('Courses of class is displayed and link to individual course stats page works', () => {
|
||||
cy.visit(pathToCSBach2017)
|
||||
cy.visit(pathToMathBSc2020)
|
||||
cy.contains('Courses of class').click()
|
||||
cy.intercept('/api/v3/courseyearlystats**').as('coursePage')
|
||||
cy.get('[data-cy=toggle-group-module-TKT1]').click()
|
||||
cy.contains('td', 'TKT10002').siblings().find('i.level.up').click()
|
||||
cy.get('[data-cy=toggle-group-module-MAT110]').click()
|
||||
cy.contains('td', 'MAT11001').siblings().find('i.level.up').click()
|
||||
cy.wait('@coursePage')
|
||||
cy.url().should('include', '/coursestatistics')
|
||||
cy.contains('TKT10002, 581325, AYTKT10002, A581325 Ohjelmoinnin perusteet')
|
||||
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('Courses of class curriculum selection works', () => {
|
||||
cy.visit(pathToCSBach2017)
|
||||
cy.visit(pathToMathBSc2020)
|
||||
cy.contains('Courses of class').click()
|
||||
cy.get('Opiskelijan digitaidot - Kumpula').should('not.exist')
|
||||
cy.get('[data-cy=curriculum-picker]').click()
|
||||
cy.contains('2020 - 2026').click()
|
||||
cy.contains('Opiskelijan digitaidot - Kumpula')
|
||||
cy.get('[data-cy=curriculum-picker]').contains('2020–2023')
|
||||
cy.get('[data-cy=toggle-group-module-MAT-tyo]')
|
||||
cy.get('[data-cy=curriculum-picker]').click().contains('2023–2026').click()
|
||||
cy.get('[data-cy=toggle-group-module-MAT-tyo]').should('not.exist')
|
||||
})
|
||||
|
||||
it("Empty 'tags' tab has a link to the page where tags can be created", () => {
|
||||
cy.visit(pathToCSBach2017)
|
||||
cy.contains('Students (170)')
|
||||
it('New fetch of courses data is done when curriculum is changed', () => {
|
||||
cy.visit(pathToMathBSc2020)
|
||||
cy.contains('Courses of class').click()
|
||||
cy.intercept('/api/v2/populationstatistics/courses').as('courseData')
|
||||
cy.get('[data-cy=curriculum-picker]').click().contains('2020–2023').click()
|
||||
cy.wait('@courseData').then(({ response }) => {
|
||||
expect(response.body).to.have.property('allStudents')
|
||||
expect(response.body).to.have.property('coursestatistics')
|
||||
expect(response.body.allStudents).to.equal(27)
|
||||
expect(response.body.coursestatistics.some(stat => stat.course.code === 'DIGI-100')).to.equal(true)
|
||||
})
|
||||
cy.get('[data-cy=curriculum-picker]').click().contains('2023–2026').click()
|
||||
cy.wait('@courseData').then(({ response }) => {
|
||||
expect(response.body).to.have.property('allStudents')
|
||||
expect(response.body).to.have.property('coursestatistics')
|
||||
expect(response.body.allStudents).to.equal(27)
|
||||
expect(response.body.coursestatistics.some(stat => stat.course.code === 'DIGI-100')).to.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
it('New fetch of courses data is done when filtered students change', () => {
|
||||
cy.visit(pathToMathBSc2020)
|
||||
cy.contains('Courses of class').click()
|
||||
cy.intercept('/api/v2/populationstatistics/courses').as('courseData')
|
||||
cy.wait('@courseData').then(({ response }) => {
|
||||
expect(response.body).to.have.property('allStudents')
|
||||
expect(response.body).to.have.property('coursestatistics')
|
||||
expect(response.body.allStudents).to.equal(27)
|
||||
})
|
||||
cy.get('[data-cy=GraduatedFromProgramme-filter-card]').within(() => {
|
||||
cy.get('[data-cy=GraduatedFromProgramme-header]').click()
|
||||
cy.get('[data-cy=option-graduated-true]').click()
|
||||
cy.wait('@courseData').then(({ response }) => {
|
||||
expect(response.body).to.have.property('allStudents')
|
||||
expect(response.body).to.have.property('coursestatistics')
|
||||
expect(response.body.allStudents).to.equal(16)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("Empty 'tags' tab has a link to the page where tags can be created", { retries: 2 }, () => {
|
||||
cy.visit(pathToMathBSc2020)
|
||||
cy.contains('Students (27)')
|
||||
.parent()
|
||||
.then($parentDiv => {
|
||||
if (!$parentDiv.hasClass('active')) cy.contains('Students (170)').click()
|
||||
if (!$parentDiv.hasClass('active')) cy.contains('Students (27)').click()
|
||||
})
|
||||
cy.get('[data-cy=student-table-tabs]').within(() => {
|
||||
cy.contains('Tags').click()
|
||||
})
|
||||
cy.contains('No tags defined. You can define them here.').find('a').click()
|
||||
cy.contains('Tags').click()
|
||||
cy.contains('Tietojenkäsittelytieteen kandiohjelma')
|
||||
cy.contains('Create a new tag')
|
||||
cy.url().should('include', '/study-programme/KH50_001?p_m_tab=0&p_tab=4')
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma')
|
||||
cy.contains('Create tags for study programme')
|
||||
cy.contains('button', 'Create a new tag').should('be.disabled')
|
||||
})
|
||||
|
||||
// This test sometimes fails on headless mode. It seems that the click on the
|
||||
// 'Fetch class with new settings' button doesn't always trigger history.push()
|
||||
// so the page doesn't reload. This is why waiting also doesn't help.
|
||||
it('Advanced settings work', { retries: 2 }, () => {
|
||||
cy.visit(pathToCSBach2017)
|
||||
cy.visit(pathToMathMSc2020)
|
||||
cy.get('[data-cy=advanced-toggle]').click()
|
||||
// only spring
|
||||
cy.cs('toggle-fall').click()
|
||||
cy.contains('Fetch class').click()
|
||||
cy.contains('Students (0)')
|
||||
cy.contains('Credit accumulation (for 17 students)')
|
||||
cy.url().should('not.include', 'semesters=FALL')
|
||||
// only fall
|
||||
cy.get('[data-cy=advanced-toggle]').click()
|
||||
cy.cs('toggle-fall').click()
|
||||
cy.cs('toggle-spring').click()
|
||||
cy.contains('Fetch class').click()
|
||||
cy.contains('Credit accumulation (for 170 students)')
|
||||
cy.contains('Credit accumulation (for 9 students)')
|
||||
cy.url().should('not.include', 'semesters=SPRING')
|
||||
// spring + fall
|
||||
cy.get('[data-cy=advanced-toggle]').click()
|
||||
cy.cs('toggle-spring').click()
|
||||
cy.contains('Fetch class').click()
|
||||
cy.contains('Credit accumulation (for 170 students)')
|
||||
cy.contains('Credit accumulation (for 26 students)')
|
||||
cy.url().should('include', 'semesters=FALL&semesters=SPRING')
|
||||
})
|
||||
|
||||
it('Credit Statistics, Credits gained tab works', () => {
|
||||
cy.selectStudyProgramme('Tietojenkäsittelytieteen kandiohjelma')
|
||||
it("'Credits gained' tab of 'Credit statistics' shows correct statistics for all students of the class and also students grouped by admission type", () => {
|
||||
cy.clock(new Date('2024-08-30').getTime(), ['Date'])
|
||||
cy.visit(pathToMathBSc2020)
|
||||
const totalStudents = 27
|
||||
cy.contains('Credit statistics')
|
||||
.parent()
|
||||
.then($parentDiv => {
|
||||
@@ -141,79 +207,122 @@ describe('Population Statistics tests', () => {
|
||||
})
|
||||
cy.contains('Credits gained').click()
|
||||
cy.get("[data-cy='credits-gained-main-table']").should('contain', 'All students of the class')
|
||||
const limits = [45, 90, 135, 180, null]
|
||||
const ranges = limits.map((limit, i) => (i === 0 ? [1, limit] : [limits[i - 1], limit]))
|
||||
const limits = [1, 45, 90, 135, 180, null]
|
||||
const ranges = limits.map((limit, i) => (i === 0 ? [null, 0] : [limits[i - 1], limit])).reverse()
|
||||
|
||||
cy.get('.credits-gained-table').should('contain', '(n = 170)')
|
||||
|
||||
for (const [start, end] of ranges) {
|
||||
let value = 'credits'
|
||||
|
||||
if (start !== null) {
|
||||
value = `${start} ≤ ${value}`
|
||||
}
|
||||
|
||||
if (end !== null) {
|
||||
value = `${value} < ${end}`
|
||||
}
|
||||
|
||||
cy.get('.credits-gained-table').should('contain', value)
|
||||
const getTableData = (selector, numbersOfStudents) => {
|
||||
const studentsInCategory = numbersOfStudents.reduce((acc, val) => acc + val, 0)
|
||||
return ranges.map((range, index) => ({
|
||||
selector: `[data-cy='credits-gained-table-${selector}']`,
|
||||
start: range[0],
|
||||
end: range[1],
|
||||
students: numbersOfStudents[index],
|
||||
percentage: `${((numbersOfStudents[index] / studentsInCategory) * 100).toFixed(1)}%`,
|
||||
}))
|
||||
}
|
||||
|
||||
cy.get("[data-cy='credits-gained-table-body'] td:nth-child(3)").then($els => {
|
||||
const sum = [...$els].map($el => parseInt($el.innerText, 10)).reduce((a, b) => a + b, 0)
|
||||
expect(sum).to.equal(170)
|
||||
cy.get("[data-cy='credits-gained-table-All students of the class'] table thead tr").within(() => {
|
||||
cy.get('th').eq(1).contains('Credits gained between 01.08.2020 and 30.08.2024 (49 months)')
|
||||
cy.get('th').eq(2).contains('Number of students')
|
||||
cy.get('th').eq(2).contains(`(n = ${totalStudents})`)
|
||||
cy.get('th').eq(3).contains('Percentage of population')
|
||||
})
|
||||
|
||||
cy.get("[data-cy='credits-gained-table-Ei valintatapaa']").should('not.exist')
|
||||
})
|
||||
|
||||
it('Credit Statistics, Credits gained tab shows stats by admissions', () => {
|
||||
cy.visit(pathToCSMaster2019)
|
||||
cy.cs('filtered-students')
|
||||
cy.contains('Credit statistics')
|
||||
cy.get("[data-cy='Credit statistics']")
|
||||
.parent()
|
||||
.then($parentDiv => {
|
||||
if (!$parentDiv.hasClass('active')) cy.get("[data-cy='Credit statistics']").click()
|
||||
for (const category of [
|
||||
getTableData('All students of the class', [9, 5, 7, 4, 2, 0]),
|
||||
getTableData('Avoin väylä', [0, 1, 3, 2, 0, 0]),
|
||||
getTableData('Muu', [0, 1, 1, 1, 0, 0]),
|
||||
getTableData('Todistusvalinta', [9, 3, 3, 1, 2, 0]),
|
||||
]) {
|
||||
category.forEach(({ selector, start, end, students, percentage }, index) => {
|
||||
let value
|
||||
if (start === null) {
|
||||
value = '0'
|
||||
} else if (end === null) {
|
||||
value = `${start} ≤ credits`
|
||||
} else {
|
||||
value = `${start} ≤ credits < ${end}`
|
||||
}
|
||||
cy.get(`${selector} [data-cy='credits-gained-table-body']`).within(() => {
|
||||
cy.get('tr')
|
||||
.eq(index)
|
||||
.within(() => {
|
||||
cy.get('td').eq(1).contains(value)
|
||||
cy.get('td').eq(2).contains(students)
|
||||
cy.get('td').eq(3).contains(percentage)
|
||||
})
|
||||
})
|
||||
})
|
||||
cy.contains('Credits gained').click()
|
||||
cy.get("[data-cy='credits-gained-table-Avoin väylä']").should('exist')
|
||||
cy.get("[data-cy='credits-gained-table-Yhteispisteet']").should('exist')
|
||||
cy.get("[data-cy='credits-gained-table-Muu']").should('exist')
|
||||
cy.get("[data-cy='credits-gained-table-Ei valintatapaa']").should('exist')
|
||||
}
|
||||
|
||||
cy.get(
|
||||
"[data-cy='credits-gained-table-All students of the class'] [data-cy='credits-gained-table-body'] td:nth-child(3)"
|
||||
).then(tds => {
|
||||
const sum = [...tds].reduce((acc, td) => acc + parseInt(td.innerText, 10), 0)
|
||||
expect(sum).to.equal(totalStudents)
|
||||
})
|
||||
})
|
||||
|
||||
it('Credit Statistics, Statistics pane works', () => {
|
||||
cy.selectStudyProgramme('Tietojenkäsittelytieteen kandiohjelma')
|
||||
it("'Statistics' tab of 'Credit statistics' shows correct statistics for all students of the class and also students grouped by admission type", () => {
|
||||
cy.visit(pathToMathBSc2020)
|
||||
cy.contains('Credit statistics')
|
||||
.parent()
|
||||
.then($parentDiv => {
|
||||
if (!$parentDiv.hasClass('active')) cy.contains('Credit statistics').click()
|
||||
})
|
||||
cy.get("[data-cy='credit-stats-tab'] > .menu > :nth-child(2)").click()
|
||||
const rows = ['Total credits', 'Average', 'Median', 'Standard deviation', 'Minimum', 'Maximum']
|
||||
const categories = [
|
||||
{
|
||||
selector: 'All students of the population',
|
||||
data: ['3773.50', '139.76', '141.00', '60.07', '19', '268'],
|
||||
size: 27,
|
||||
},
|
||||
{
|
||||
selector: 'Muu',
|
||||
data: ['359.00', '119.67', '111.00', '43.71', '71', '177'],
|
||||
size: 3,
|
||||
},
|
||||
{
|
||||
selector: 'Todistusvalinta',
|
||||
data: ['2742.50', '152.36', '178.00', '65.75', '19', '268'],
|
||||
size: 18,
|
||||
},
|
||||
{
|
||||
selector: 'Avoin väylä',
|
||||
data: ['672.00', '112.00', '104.00', '29.40', '82', '169'],
|
||||
size: 6,
|
||||
},
|
||||
]
|
||||
|
||||
cy.get("[data-cy='credit-stats-population-size']").should('contain', 'n = 170')
|
||||
cy.get("[data-cy='credit-stats-average']").should('contain', '114.74')
|
||||
cy.get("[data-cy='credit-stats-median']").should('contain', '109.00')
|
||||
cy.get("[data-cy='credit-stats-stdev']").should('contain', '65.53')
|
||||
cy.get("[data-cy='credit-stats-min']").should('contain', '5')
|
||||
cy.get("[data-cy='credit-stats-max']").should('contain', '314')
|
||||
for (const { selector, data, size } of categories) {
|
||||
cy.get(`[data-cy='statistics-table-${selector}']`).within(() => {
|
||||
cy.contains('h3', selector)
|
||||
cy.contains("[data-cy='credit-stats-population-size']", `n = ${size}`)
|
||||
cy.get('table tbody').within(() => {
|
||||
rows.forEach((text, index) => {
|
||||
cy.get('tr')
|
||||
.eq(index)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains(text)
|
||||
cy.get('td').eq(1).contains(data[index])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('when using admin', () => {
|
||||
beforeEach(() => {
|
||||
cy.init(pathToCSBach2017, 'admin')
|
||||
})
|
||||
describe('When using admin', () => {
|
||||
it('Student list checking works as intended', () => {
|
||||
const existing = '010113437'
|
||||
const nonExisting = '66666666'
|
||||
cy.visit(pathToCSBach2017)
|
||||
cy.contains('Students (170)')
|
||||
cy.init(pathToMathBSc2020, 'admin')
|
||||
const existing = '433237'
|
||||
const nonExisting = '550004'
|
||||
cy.contains('Students (27)')
|
||||
.parent()
|
||||
.then($parentDiv => {
|
||||
if (!$parentDiv.hasClass('active')) cy.contains('Students (170)').click()
|
||||
if (!$parentDiv.hasClass('active')) cy.contains('Students (27)').click()
|
||||
})
|
||||
cy.contains(existing)
|
||||
cy.contains(nonExisting).should('not.exist')
|
||||
@@ -227,16 +336,18 @@ describe('Population Statistics tests', () => {
|
||||
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', '010614509')
|
||||
cy.contains('#notsearched', '457144')
|
||||
})
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.contains('Student numbers in list and in Sisu').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when using IAM user', () => {
|
||||
describe('When using IAM user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init(pathToCSBach2017, 'onlyiamrights')
|
||||
cy.contains('Tietojenkäsittelytieteen kandiohjelma')
|
||||
cy.contains('class size 170 students')
|
||||
cy.init(pathToMathBSc2020, 'onlyiamrights')
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma 2020 - 2021')
|
||||
cy.contains('class size 30 students')
|
||||
})
|
||||
|
||||
it('Population statistics is visible', () => {
|
||||
@@ -245,13 +356,39 @@ describe('Population Statistics tests', () => {
|
||||
cy.contains('Excludes students with non-degree study right')
|
||||
cy.contains('Excludes students who have transferred out of this programme')
|
||||
})
|
||||
})
|
||||
|
||||
cy.contains('Credit accumulation (for 170 students)')
|
||||
cy.contains('Credit statistics')
|
||||
it('Only correct panes are visible', () => {
|
||||
const correctPanes = [
|
||||
'Credit accumulation (for 27 students)',
|
||||
'Credit statistics',
|
||||
'Age distribution',
|
||||
'Courses of class',
|
||||
]
|
||||
cy.get('.accordion.ui.fluid.styled').within(() => {
|
||||
cy.get('.title').should('have.length', correctPanes.length)
|
||||
correctPanes.forEach((pane, index) => {
|
||||
cy.get('.title').eq(index).contains(pane)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('Ages cannot be ungrouped', () => {
|
||||
cy.contains('Age distribution')
|
||||
.parent()
|
||||
.then($parentDiv => {
|
||||
if (!$parentDiv.hasClass('active')) cy.contains('Age distribution').click()
|
||||
})
|
||||
cy.contains('ui.checked.toggle.checkbox', 'Group ages').should('not.exist')
|
||||
})
|
||||
|
||||
it('Age filter is not visible', () => {
|
||||
cy.get("[data-cy='filtered-students']")
|
||||
cy.get("[data-cy='Age-filter-card']").should('not.exist')
|
||||
})
|
||||
|
||||
it('Students tab is not available', () => {
|
||||
cy.contains('Students (170)').should('not.exist')
|
||||
cy.contains('Students (27)').should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+271
-113
@@ -1,119 +1,277 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const student = {
|
||||
firstnames: 'Varpu Roope',
|
||||
lastname: 'Mårtensson',
|
||||
studentnumber: '550003',
|
||||
sis_person_id: 'hy-hlo-115926826',
|
||||
email: 'sisutestidata134902@testisisudata.fi',
|
||||
}
|
||||
|
||||
const typeStudentNumberAndClick = studentNumber => {
|
||||
cy.get('.prompt').type(studentNumber)
|
||||
cy.contains('td a', studentNumber).click()
|
||||
}
|
||||
|
||||
describe('Students tests', () => {
|
||||
beforeEach(() => {
|
||||
cy.init()
|
||||
cy.contains('Students').click()
|
||||
cy.contains('Show student names')
|
||||
})
|
||||
|
||||
const student = {
|
||||
firstnames: 'Matti Jaakko',
|
||||
lastname: 'Nieminen',
|
||||
studentnumber: '010654019',
|
||||
sis_person_id: 'hy-hlo-84830887',
|
||||
}
|
||||
|
||||
it('Students search form is usable', () => {
|
||||
cy.contains('Show student names')
|
||||
cy.url().should('include', '/students')
|
||||
cy.get('.prompt').type(student.lastname)
|
||||
cy.contains('Student number')
|
||||
cy.contains('Started')
|
||||
cy.contains('Credits')
|
||||
cy.contains(student.firstnames).should('not.exist')
|
||||
|
||||
cy.cs('toggleStudentNames').click()
|
||||
cy.contains(student.firstnames)
|
||||
|
||||
cy.cs('toggleStudentNames').click()
|
||||
cy.contains(student.firstnames).should('not.exist')
|
||||
})
|
||||
|
||||
it('can search with studentnumber too', () => {
|
||||
cy.get('.prompt').type(student.studentnumber)
|
||||
cy.contains(student.studentnumber)
|
||||
})
|
||||
|
||||
it('Can get student specific page by clicking student', () => {
|
||||
cy.url().should('include', '/students')
|
||||
cy.get('.prompt').type(student.studentnumber)
|
||||
cy.contains('td a', student.studentnumber).click()
|
||||
cy.contains('Tietojenkäsittelytieteen kandiohjelma (01.08.2017–31.07.2025')
|
||||
cy.contains(student.lastname).should('not.exist')
|
||||
cy.contains(student.firstnames).should('not.exist')
|
||||
|
||||
cy.cs('toggleStudentNames').click()
|
||||
cy.contains(student.lastname)
|
||||
cy.contains(student.firstnames)
|
||||
|
||||
cy.cs('toggleStudentNames').click()
|
||||
cy.contains(student.lastname).should('not.exist')
|
||||
cy.contains(student.firstnames).should('not.exist')
|
||||
})
|
||||
|
||||
it('Can get back to search menu', () => {
|
||||
cy.get('.prompt').type(student.studentnumber)
|
||||
cy.contains('td a', student.studentnumber).click()
|
||||
cy.go('back')
|
||||
cy.contains('Student number').should('not.exist')
|
||||
cy.contains('Credits').should('not.exist')
|
||||
})
|
||||
|
||||
it('Can jump to course', () => {
|
||||
cy.get('.prompt').type(student.studentnumber)
|
||||
cy.contains('td a', student.studentnumber).click()
|
||||
cy.contains('Introduction to Machine Learning (DATA11002)')
|
||||
.parent()
|
||||
.siblings()
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.get('.level').click()
|
||||
})
|
||||
cy.url().should('include', '/coursestatistics')
|
||||
cy.contains('Introduction to Machine Learning')
|
||||
})
|
||||
|
||||
it('Has correct Sisu link', () => {
|
||||
cy.get('.prompt').type(student.studentnumber)
|
||||
cy.contains('td a', student.studentnumber).click()
|
||||
cy.get('[data-cy=sisulink] > a')
|
||||
.should('have.attr', 'href')
|
||||
.and('include', 'https://sisu.helsinki.fi/tutor/role/staff/student/hy-hlo-84830887/basic/basic-info')
|
||||
})
|
||||
|
||||
it('Bachelor Honours section is shown', () => {
|
||||
cy.get('.prompt').type(student.studentnumber)
|
||||
cy.contains('td a', student.studentnumber).click()
|
||||
cy.contains('h4', 'Bachelor Honours')
|
||||
cy.contains('.tag.label', 'Not qualified for Honours')
|
||||
cy.contains('.tag.label', 'Has not graduated')
|
||||
})
|
||||
|
||||
it('Searching with bad inputs doesnt yield results', () => {
|
||||
cy.get('.prompt').type('SWAG LITTINEN')
|
||||
cy.contains('Student number').should('not.exist')
|
||||
|
||||
cy.get('.prompt').clear().type('01114')
|
||||
cy.contains('Student number').should('not.exist')
|
||||
})
|
||||
|
||||
it('Can jump to population page', () => {
|
||||
cy.get('.prompt').type(student.studentnumber)
|
||||
cy.contains('td a', student.studentnumber).click()
|
||||
cy.contains('.ui.table', 'Completed').within(() => {
|
||||
cy.get('i.level.up.alternate.icon').click()
|
||||
describe('when using basic user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init()
|
||||
cy.contains('Students').click()
|
||||
cy.url().should('include', '/students')
|
||||
cy.contains('Show student names')
|
||||
})
|
||||
cy.contains('Tietojenkäsittelytieteen kandiohjelma')
|
||||
cy.contains('class size')
|
||||
})
|
||||
})
|
||||
|
||||
// Use admin to see all students
|
||||
describe('Testing with admin rights', () => {
|
||||
it('Does not crash if student has no studyright or courses', () => {
|
||||
const studentNumber = '011825096'
|
||||
cy.init(`/students/${studentNumber}`, 'admin') // use admin to see all students
|
||||
cy.contains('Credits: 0')
|
||||
it('Students search form is usable', () => {
|
||||
cy.get('.prompt').type(student.lastname)
|
||||
cy.contains('Student number')
|
||||
cy.contains('Started')
|
||||
cy.contains('Credits')
|
||||
cy.contains(student.firstnames).should('not.exist')
|
||||
|
||||
cy.cs('toggleStudentNames').click()
|
||||
cy.contains(student.firstnames)
|
||||
|
||||
cy.cs('toggleStudentNames').click()
|
||||
cy.contains(student.firstnames).should('not.exist')
|
||||
})
|
||||
|
||||
it('Search term must be at least 4 characters long', () => {
|
||||
cy.get('.prompt').type(student.lastname.slice(0, 3))
|
||||
cy.contains('No search results or search term is not accurate enough')
|
||||
cy.get('.prompt').type(student.lastname.slice(3))
|
||||
cy.get('table tbody tr').should('have.length', 1)
|
||||
})
|
||||
|
||||
it('can search with studentnumber too', () => {
|
||||
cy.get('.prompt').type(student.studentnumber)
|
||||
cy.contains(student.studentnumber)
|
||||
})
|
||||
|
||||
it('Can get student specific page by clicking student', () => {
|
||||
cy.get('.prompt').type(student.studentnumber)
|
||||
cy.contains('td a', student.studentnumber).click()
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma (01.08.2020–31.07.2027)')
|
||||
cy.contains(student.lastname).should('not.exist')
|
||||
cy.contains(student.firstnames).should('not.exist')
|
||||
cy.contains(student.email).should('not.exist')
|
||||
|
||||
cy.cs('toggleStudentNames').click()
|
||||
cy.contains(student.lastname)
|
||||
cy.contains(student.firstnames)
|
||||
cy.contains(student.email)
|
||||
|
||||
cy.cs('toggleStudentNames').click()
|
||||
cy.contains(student.lastname).should('not.exist')
|
||||
cy.contains(student.firstnames).should('not.exist')
|
||||
cy.contains(student.email).should('not.exist')
|
||||
})
|
||||
|
||||
it("'Update student' button is not shown", () => {
|
||||
typeStudentNumberAndClick(student.studentnumber)
|
||||
cy.get('div.ui.fluid.card').within(() => {
|
||||
cy.contains('button', 'Update student').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
it('Can get back to search menu', () => {
|
||||
typeStudentNumberAndClick(student.studentnumber)
|
||||
cy.go('back')
|
||||
cy.contains('Student number').should('not.exist')
|
||||
cy.contains('Credits').should('not.exist')
|
||||
})
|
||||
|
||||
it('Can jump to course', () => {
|
||||
typeStudentNumberAndClick(student.studentnumber)
|
||||
cy.contains('Tilastollinen päättely I (MAT12004)')
|
||||
.parent()
|
||||
.siblings()
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.get('.level').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')
|
||||
})
|
||||
|
||||
it('Has correct Sisu link', () => {
|
||||
typeStudentNumberAndClick(student.studentnumber)
|
||||
cy.get('[data-cy=sisulink] > a')
|
||||
.should('have.attr', 'href')
|
||||
.and('include', `https://sisu.helsinki.fi/tutor/role/staff/student/${student.sis_person_id}/basic/basic-info`)
|
||||
})
|
||||
|
||||
it('Semester enrollments can be toggled', () => {
|
||||
typeStudentNumberAndClick(student.studentnumber)
|
||||
const programmes = [
|
||||
'Oikeustieteen tohtoriohjelma',
|
||||
'Matemaattisten tieteiden kandiohjelma',
|
||||
'Oikeustieteen maisterin koulutusohjelmaOikeusnotaarin koulutusohjelma',
|
||||
]
|
||||
cy.get('div.ui.fluid.card').within(() => {
|
||||
cy.contains('Enrollments').click()
|
||||
cy.get('table tbody tr')
|
||||
.should('have.length', 3)
|
||||
.each(($tr, index) => {
|
||||
cy.wrap($tr).within(() => {
|
||||
cy.get('td').eq(0).should('have.text', programmes[index])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('Searching with bad inputs doesnt yield results', () => {
|
||||
cy.get('.prompt').type('SWAG LITTINEN')
|
||||
cy.contains('Student number').should('not.exist')
|
||||
|
||||
cy.get('.prompt').clear().type('01114')
|
||||
cy.contains('Student number').should('not.exist')
|
||||
})
|
||||
|
||||
it('Can jump to population page', () => {
|
||||
typeStudentNumberAndClick(student.studentnumber)
|
||||
cy.contains('.ui.table', 'Completed').within(() => {
|
||||
cy.get('i.level.up.alternate.icon').click()
|
||||
})
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma 2020 - 2021')
|
||||
cy.contains('class size 30 students')
|
||||
})
|
||||
|
||||
it('Grade graph works in all three different modes', () => {
|
||||
typeStudentNumberAndClick(student.studentnumber)
|
||||
cy.contains('a.item', 'Grade graph').click()
|
||||
cy.contains('text.highcharts-title', 'Grade plot')
|
||||
cy.contains('a.item', 'Show group mean').click()
|
||||
cy.get('.labeled.input').within(() => {
|
||||
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.contains('text.highcharts-title', 'Grade plot')
|
||||
cy.get('.labeled.input').within(() => {
|
||||
cy.contains('.label', 'Group size')
|
||||
cy.get('input').clear().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('a.item', 'Show semester mean').click()
|
||||
cy.contains('.highcharts-container text', "Jan '22")
|
||||
})
|
||||
|
||||
describe('Bachelor Honours section', () => {
|
||||
it("Shows 'Qualified for Honours' tag and main modules info when the student is qualified", () => {
|
||||
cy.visit('/students/495976')
|
||||
cy.contains('.divider h4', 'Bachelor Honours')
|
||||
cy.contains('.green.tag.label', 'Qualified for Honours')
|
||||
cy.contains('Main courses and other modules').click()
|
||||
cy.get('[data-cy=main-modules] tbody tr')
|
||||
.should('have.length', 3)
|
||||
.each(($tr, index) => {
|
||||
const info = [
|
||||
['07.05.2020', 'Matemaattisten tieteiden kandiohjelma (KH50_001)', 'Hyv.'],
|
||||
['08.05.2018', 'Matematiikka, perusopinnot (MAT110)', '4'],
|
||||
['25.02.2020', 'Matematiikka, aineopinnot (MAT210)', '4'],
|
||||
]
|
||||
cy.wrap($tr).within(() => {
|
||||
info[index].forEach((text, i) => {
|
||||
cy.get('td').eq(i).contains(text)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("Shows 'Did not graduate in time' when the student has graduated but not in time", () => {
|
||||
cy.visit('/students/540355')
|
||||
cy.contains('.divider h4', 'Bachelor Honours')
|
||||
cy.contains('.red.tag.label', 'Not qualified for Honours')
|
||||
cy.contains('.red.tag.label', 'Did not graduate in time')
|
||||
})
|
||||
|
||||
it("Shows 'Module grades too low' when the student has graduated in time but has too low grades", () => {
|
||||
cy.visit('/students/547934')
|
||||
cy.contains('.divider h4', 'Bachelor Honours')
|
||||
cy.contains('.red.tag.label', 'Not qualified for Honours')
|
||||
cy.contains('.red.tag.label', 'Module grades too low')
|
||||
})
|
||||
|
||||
it("Shows 'Might need further inspection' when the student has graduated in time but has more than four main modules", () => {
|
||||
cy.visit('/students/478837')
|
||||
cy.contains('.divider h4', 'Bachelor Honours')
|
||||
cy.contains('.blue.tag.label', 'Might need further inspection')
|
||||
})
|
||||
|
||||
it("Shows 'Has not graduated' when the student has not graduated", () => {
|
||||
cy.visit(`students/${student.studentnumber}`)
|
||||
cy.contains('.divider h4', 'Bachelor Honours')
|
||||
cy.contains('.red.tag.label', 'Not qualified for Honours')
|
||||
cy.contains('.red.tag.label', 'Has not graduated')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Use admin to see all students
|
||||
describe('when using admin user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/students', 'admin')
|
||||
})
|
||||
|
||||
it('Does not crash if student has no studyright or courses', () => {
|
||||
typeStudentNumberAndClick('450730')
|
||||
cy.contains('Credits: 0')
|
||||
})
|
||||
|
||||
it("'Update student' button is shown", () => {
|
||||
typeStudentNumberAndClick(student.studentnumber)
|
||||
cy.get('div.ui.fluid.card').within(() => {
|
||||
cy.contains('button', 'Update student')
|
||||
})
|
||||
})
|
||||
|
||||
it('Bachelor Honours section is not shown for students outside of Faculty of Science', () => {
|
||||
cy.visit('/students/453146')
|
||||
cy.contains('.divider h4', 'Bachelor Honours').should('not.exist')
|
||||
})
|
||||
|
||||
it('When a study plan is selected, courses included in the study plan are highlighted with a blue background', () => {
|
||||
cy.visit('/students/550789')
|
||||
cy.contains('table tbody tr', 'Kulttuurien tutkimuksen kandiohjelma (01.08.2020–30.06.2023)').within(() => {
|
||||
cy.get('td').eq(0).click()
|
||||
})
|
||||
cy.contains('table tbody tr', 'Kandidaatintutkielma (KUKA-LIS222)')
|
||||
.should('have.attr', 'style')
|
||||
.and('equal', 'background-color: rgb(232, 244, 255);')
|
||||
})
|
||||
|
||||
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", () => {
|
||||
cy.visit('/students/550789')
|
||||
cy.contains('table tbody tr', 'Kulttuurien tutkimuksen kandiohjelma (01.08.2020–30.06.2023)').within(() => {
|
||||
cy.get('td')
|
||||
.eq(-1)
|
||||
.contains(/^185 cr$/)
|
||||
})
|
||||
})
|
||||
|
||||
it("If there's a study plan corresponding to the degree programme and the student hasn't graduated, percentage of completion is also displayed", () => {
|
||||
cy.visit('/students/458723')
|
||||
cy.contains('table tbody tr', 'Oikeusnotaarin koulutusohjelma (01.08.2020–31.07.2027)').within(() => {
|
||||
cy.get('td').eq(-1).contains('69% (125 cr)')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
|
||||
describe('Study guidance group tests', () => {
|
||||
describe('Study guidance group overview page', () => {
|
||||
@@ -30,7 +30,7 @@ describe('Study guidance group tests', () => {
|
||||
cy.contains('Study guidance groups')
|
||||
|
||||
const studyGuidanceGroupsTableContent = [
|
||||
['TKT kandit 2018', 3, 'Tietojenkäsittelytieteen kandiohjelma', '2018 - 2019'],
|
||||
['MAT kandit 2020', 3, 'Matemaattisten tieteiden kandiohjelma', '2020 - 2021'],
|
||||
['Oma ohjausryhmä', 2, 'Add study programme', 'Add year'],
|
||||
]
|
||||
|
||||
@@ -97,9 +97,9 @@ describe('Study guidance group tests', () => {
|
||||
})
|
||||
|
||||
it('shows the correct labels', () => {
|
||||
cy.get('.ui.medium.header').contains('TKT kandit 2018')
|
||||
cy.get('.ui.blue.tag.label:first').contains('Tietojenkäsittelytieteen kandiohjelma')
|
||||
cy.get('.ui.blue.tag.label:last').contains('2018 - 2019')
|
||||
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')
|
||||
})
|
||||
|
||||
it('shows the correct panes', () => {
|
||||
@@ -114,7 +114,7 @@ describe('Study guidance group tests', () => {
|
||||
cy.get('[data-cy="CreditDate-filter-card"][data-open="false"]')
|
||||
cy.get('.ui.primary.button').contains('Show starting from associated year').click()
|
||||
cy.get('[data-cy="CreditDate-filter-card"][data-open="true"]')
|
||||
cy.get('.ui.mini.icon.button.credit-date-filter-input').contains('01.08.2018')
|
||||
cy.get('.ui.mini.icon.button.credit-date-filter-input').contains('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-open="false"]')
|
||||
@@ -132,23 +132,24 @@ describe('Study guidance group tests', () => {
|
||||
const firstRowHeadings = [
|
||||
'Student number',
|
||||
'Credits',
|
||||
'Start of studyright',
|
||||
'Start of study right',
|
||||
'Started in programme',
|
||||
'Semesters present',
|
||||
'Graduation date',
|
||||
'Start year at uni',
|
||||
'Other programmes',
|
||||
'Transferred from',
|
||||
'Admission type',
|
||||
'Gender',
|
||||
'Citizenship',
|
||||
'Curriculum period',
|
||||
'Latest attainment date',
|
||||
'Tags',
|
||||
]
|
||||
const secondRowHeadings = ['All', 'HOPS', 'Since 1.8.2018']
|
||||
const secondRowHeadings = ['All', 'HOPS', 'Since 1.8.2020']
|
||||
cy.cs('Students (3)').click()
|
||||
cy.get('[data-cy="student-table-tabs"] table thead tr').then($tr => {
|
||||
const firstRowTexts = Array.from($tr[0].children).map(elem => elem.innerText.replace('\n', ' '))
|
||||
const firstRowTexts = Array.from($tr[0].children).map(elem => elem.innerText.replaceAll('\n', ' '))
|
||||
expect(firstRowTexts).to.deep.equal(firstRowHeadings)
|
||||
const secondRowTexts = Array.from($tr[1].children)
|
||||
.filter(elem => elem.style.display !== 'none')
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const { getEmptyYears } = require('../support/commands')
|
||||
|
||||
const deleteTag = name => {
|
||||
cy.contains('td', name).siblings().contains('Delete').click()
|
||||
@@ -7,36 +9,21 @@ const deleteTag = name => {
|
||||
cy.contains('td', name).should('not.exist')
|
||||
}
|
||||
|
||||
const getEmptyYears = isAcademicYear => {
|
||||
const today = new Date()
|
||||
const latestYear = isAcademicYear && today.getMonth() < 7 ? today.getFullYear() - 1 : today.getFullYear()
|
||||
|
||||
const years = []
|
||||
for (let year = latestYear; year > 2021; year--) {
|
||||
if (isAcademicYear) {
|
||||
years.push(`${year} - ${year + 1}`)
|
||||
} else {
|
||||
years.push(year)
|
||||
}
|
||||
}
|
||||
return years
|
||||
}
|
||||
|
||||
describe('Studyprogramme overview', () => {
|
||||
describe('Study programme overview', () => {
|
||||
/* Basic information overview -tests */
|
||||
describe('Basic information -view works for basic user', () => {
|
||||
describe('Basic information view works for basic user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/study-programme')
|
||||
cy.contains('a', 'Tietojenkäsittelytieteen kandiohjelma').click({ force: true })
|
||||
cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click({ force: true })
|
||||
})
|
||||
|
||||
// If the backend breaks for one of the sections, the section header is not rendered and this will fail
|
||||
it('Basic information -tab loads', () => {
|
||||
cy.get('[data-cy=Section-StudentsOfTheStudyprogramme]')
|
||||
cy.get('[data-cy=Section-CreditsProducedByTheStudyprogramme]')
|
||||
cy.get('[data-cy=Section-GraduatedAndThesisWritersOfTheProgramme]')
|
||||
cy.get('[data-cy=Section-ProgrammesBeforeOrAfter]')
|
||||
cy.get('[data-cy=Section-AverageGraduationTimes]')
|
||||
it('Basic information tab loads', () => {
|
||||
cy.get('[data-cy=Section-studentsOfTheStudyProgramme]')
|
||||
cy.get('[data-cy=Section-creditsProducedByTheStudyProgramme]')
|
||||
cy.get('[data-cy=Section-graduatedAndThesisWritersOfTheProgramme]')
|
||||
cy.get('[data-cy=Section-programmesBeforeOrAfter]')
|
||||
cy.get('[data-cy=Section-averageGraduationTimes]')
|
||||
})
|
||||
|
||||
it('Basic information contains correct students', () => {
|
||||
@@ -44,28 +31,49 @@ describe('Studyprogramme overview', () => {
|
||||
const tableContents = [
|
||||
// [Year, Started studying, Accepted, Graduated, Transferred Away, Transferred to]
|
||||
...years.map(year => [year, 0, 0, 0, 0, 0]),
|
||||
[2021, 0, 0, 0, 1, 0],
|
||||
[2020, 10, 10, 35, 2, 2],
|
||||
[2019, 83, 86, 17, 1, 1],
|
||||
[2018, 150, 161, 1, 0, 0],
|
||||
[2017, 158, 171, 0, 0, 0],
|
||||
[2023, 8, 8, 26, 0, 0],
|
||||
[2022, 25, 26, 48, 1, 4],
|
||||
[2021, 29, 32, 47, 0, 2],
|
||||
[2020, 26, 27, 12, 1, 3],
|
||||
[2019, 28, 34, 1, 0, 1],
|
||||
[2018, 40, 45, 0, 0, 0],
|
||||
[2017, 41, 47, 0, 0, 0],
|
||||
]
|
||||
|
||||
cy.checkTableStats(tableContents, 'StudentsOfTheStudyprogramme')
|
||||
cy.checkTableStats(tableContents, 'StudentsOfTheStudyProgramme')
|
||||
})
|
||||
|
||||
it('Basic information contains correct credits', () => {
|
||||
const years = getEmptyYears()
|
||||
const tableContents = [
|
||||
...years.map(year => [year, 0, 0]),
|
||||
[2021, 0, 0, 0, 0, 0],
|
||||
[2020, 4511, 4511, 0, 0, 406],
|
||||
[2019, 7117, 7097, 0, 0, 434],
|
||||
[2018, 5742, 5737, 0, 0, 150],
|
||||
[2017, 1887, 1882, 0, 0, 186],
|
||||
...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, 'CreditsProducedByTheStudyprogramme')
|
||||
cy.checkTableStats(tableContents, 'CreditsProducedByTheStudyProgramme')
|
||||
})
|
||||
|
||||
it("Toggling 'Show special categories' on displays additional information", () => {
|
||||
cy.contains('Show special categories').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],
|
||||
]
|
||||
|
||||
cy.checkTableStats(tableContents, 'CreditsProducedByTheStudyProgramme')
|
||||
})
|
||||
|
||||
it('Basic information contains correct thesis writers and graduates', () => {
|
||||
@@ -73,54 +81,48 @@ describe('Studyprogramme overview', () => {
|
||||
const tableContents = [
|
||||
// [Year, Graduated, Wrote thesis]
|
||||
...years.map(year => [year, 0, 0]),
|
||||
[2021, 0, 0],
|
||||
[2020, 35, 35],
|
||||
[2019, 17, 25],
|
||||
[2018, 1, 5],
|
||||
[2017, 0, 0],
|
||||
[2023, 26, 0],
|
||||
[2022, 48, 0],
|
||||
[2021, 47, 66],
|
||||
[2020, 12, 18],
|
||||
[2019, 1, 15],
|
||||
[2018, 0, 0],
|
||||
[2017, 0, 1],
|
||||
]
|
||||
|
||||
cy.checkTableStats(tableContents, 'GraduatedAndThesisWritersOfTheProgramme')
|
||||
})
|
||||
|
||||
it('Special studyrights can be excluded and basic data changes accordingly', () => {
|
||||
it('Special study rights can be excluded and basic data changes accordingly', () => {
|
||||
cy.get('[data-cy=StudentToggle]').click()
|
||||
const years = getEmptyYears()
|
||||
const studentTableContents = [
|
||||
// [Year, Started studying, Accepted, Graduated]
|
||||
...years.map(year => [year, 0, 0, 0]),
|
||||
[2021, 0, 0, 0],
|
||||
[2020, 10, 10, 35],
|
||||
[2019, 83, 86, 17],
|
||||
[2018, 150, 161, 1],
|
||||
[2017, 158, 171, 0],
|
||||
[2023, 8, 8, 24],
|
||||
[2022, 25, 26, 44],
|
||||
[2021, 29, 32, 46],
|
||||
[2020, 26, 27, 11],
|
||||
[2019, 28, 34, 1],
|
||||
[2018, 40, 45, 0],
|
||||
[2017, 41, 47, 0],
|
||||
]
|
||||
|
||||
cy.checkTableStats(studentTableContents, 'StudentsOfTheStudyprogramme')
|
||||
|
||||
const creditTableContents = [
|
||||
...years.map(year => [year, 0, 0]),
|
||||
[2021, 0, 0, 0, 0, 0],
|
||||
[2020, 4511, 4511, 0, 0, 406],
|
||||
[2019, 7117, 7097, 0, 0, 434],
|
||||
[2018, 5742, 5737, 0, 0, 150],
|
||||
[2017, 1887, 1882, 0, 0, 186],
|
||||
]
|
||||
|
||||
cy.checkTableStats(creditTableContents, 'CreditsProducedByTheStudyprogramme')
|
||||
cy.checkTableStats(studentTableContents, 'StudentsOfTheStudyProgramme')
|
||||
|
||||
const graduatedTableContents = [
|
||||
// [Year, Graduated, Wrote thesis]
|
||||
...years.map(year => [year, 0, 0]),
|
||||
[2021, 0, 0],
|
||||
[2020, 35, 35],
|
||||
[2019, 17, 25],
|
||||
[2018, 1, 5],
|
||||
[2017, 0, 0],
|
||||
[2023, 24, 0],
|
||||
[2022, 44, 0],
|
||||
[2021, 46, 60],
|
||||
[2020, 11, 17],
|
||||
[2019, 1, 15],
|
||||
[2018, 0, 0],
|
||||
[2017, 0, 1],
|
||||
]
|
||||
|
||||
cy.checkTableStats(graduatedTableContents, 'GraduatedAndThesisWritersOfTheProgramme')
|
||||
cy.get('[data-cy=StudentToggle]').click()
|
||||
})
|
||||
|
||||
it('Year can be changed to academic year, and data changes accordingly', () => {
|
||||
@@ -130,125 +132,214 @@ describe('Studyprogramme overview', () => {
|
||||
const studentTableContents = [
|
||||
// [Year, Started studying, Accepted, Graduated, Transferred away, Transferred to]
|
||||
...years.map(year => [year, 0, 0, 0, 0, 0]),
|
||||
['2021 - 2022', 0, 0, 0, 0, 0],
|
||||
['2020 - 2021', 10, 10, 6, 1, 2],
|
||||
['2019 - 2020', 83, 86, 38, 3, 0],
|
||||
['2018 - 2019', 150, 161, 8, 0, 1],
|
||||
['2017 - 2018', 158, 171, 1, 0, 0],
|
||||
['2023 - 2024', 8, 8, 4, 0, 0],
|
||||
['2022 - 2023', 25, 26, 36, 0, 0],
|
||||
['2021 - 2022', 29, 32, 57, 1, 6],
|
||||
['2020 - 2021', 26, 27, 30, 0, 3],
|
||||
['2019 - 2020', 28, 34, 7, 1, 1],
|
||||
['2018 - 2019', 40, 45, 0, 0, 0],
|
||||
['2017 - 2018', 41, 47, 0, 0, 0],
|
||||
]
|
||||
cy.checkTableStats(studentTableContents, 'StudentsOfTheStudyprogramme')
|
||||
cy.checkTableStats(studentTableContents, 'StudentsOfTheStudyProgramme')
|
||||
|
||||
const creditTableContents = [
|
||||
...years.map(year => [year, 0, 0]),
|
||||
['2021 - 2022', 0, 0, 0, 0, 0],
|
||||
['2020 - 2021', 352, 352, 0, 0, 296],
|
||||
['2019 - 2020', 6966, 6966, 0, 0, 430],
|
||||
['2018 - 2019', 7797, 7777, 0, 0, 259],
|
||||
['2017 - 2018', 4142, 4132, 0, 0, 191],
|
||||
...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],
|
||||
]
|
||||
|
||||
cy.checkTableStats(creditTableContents, 'CreditsProducedByTheStudyprogramme')
|
||||
cy.checkTableStats(creditTableContents, 'CreditsProducedByTheStudyProgramme')
|
||||
cy.get('[data-cy=YearToggle]').click()
|
||||
})
|
||||
|
||||
it('Basic information graphs render', () => {
|
||||
cy.get('[data-cy=Graph-StudentsOfTheStudyprogramme')
|
||||
cy.get('[data-cy=Graph-StudentsOfTheStudyProgramme')
|
||||
.should('contain', 'Started studying')
|
||||
.should('contain', 'Accepted')
|
||||
.should('contain', 'Graduated')
|
||||
.should('contain', 'Transferred away')
|
||||
.should('contain', 'Transferred to')
|
||||
|
||||
cy.get('[data-cy=Graph-CreditsProducedByTheStudyprogramme')
|
||||
cy.get('[data-cy=Graph-CreditsProducedByTheStudyProgramme')
|
||||
.should('contain', 'Degree students')
|
||||
.should('contain', 'Transferred')
|
||||
.should('contain', 7097)
|
||||
.should('contain', 434)
|
||||
.should('contain', 5796)
|
||||
.should('contain', 428)
|
||||
|
||||
cy.get('[data-cy=Graph-GraduatedAndThesisWritersOfTheProgramme')
|
||||
.should('contain', 'Graduated students')
|
||||
.should('contain', 'Wrote thesis')
|
||||
.should('contain', 1)
|
||||
.should('contain', 5)
|
||||
.should('contain', 17)
|
||||
.should('contain', 25)
|
||||
.should('contain', 35)
|
||||
.should('contain', 47)
|
||||
.should('contain', 66)
|
||||
|
||||
cy.get('[data-cy=graduation-times-graph-breakdownBachelor]')
|
||||
cy.get('[data-cy=GraduationTimeToggle]').click()
|
||||
cy.get('[data-cy=graduation-times-graphBachelor]').within(() => {
|
||||
cy.contains('Graduation year')
|
||||
cy.contains('2020')
|
||||
cy.contains('35 graduated').trigger('mouseover')
|
||||
cy.contains('median study time: 34 months')
|
||||
cy.contains('30 graduated on time')
|
||||
cy.contains('5 graduated max year overtime')
|
||||
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.get('[data-cy=Graph-ProgrammesBeforeOrAfter')
|
||||
.should('contain', 'Tietojenkäsittelytieteen maisteriohjelma')
|
||||
.should('contain', 'Datatieteen maisteriohjelma')
|
||||
.should('contain', 'Matematiikan ja tilastotieteen maisteriohjelma')
|
||||
.should('contain', 11)
|
||||
.should('contain', 12)
|
||||
.should('contain', 24)
|
||||
.should('contain', 32)
|
||||
.should('contain', 21)
|
||||
.should('contain', 17)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Graduation times of master programmes', () => {
|
||||
it('are split into two graphs', () => {
|
||||
cy.init('/study-programme', 'admin')
|
||||
cy.contains('a', 'Kasvatustieteiden maisteriohjelma').click({ force: true })
|
||||
cy.init('/study-programme')
|
||||
cy.contains('a', 'Matematiikan ja tilastotieteen maisteriohjelma').click({ force: true })
|
||||
|
||||
cy.get('[data-cy=graduation-times-graph-breakdownMaster]')
|
||||
cy.get('[data-cy=graduation-times-graph-breakdownBachelor]')
|
||||
|
||||
cy.get('[data-cy=GraduationTimeToggle]').click()
|
||||
cy.get('[data-cy=graduation-times-graphMaster]').within(() => {
|
||||
cy.contains('Master study right')
|
||||
cy.contains('Graduation year')
|
||||
cy.contains('2020')
|
||||
cy.contains('24 graduated').trigger('mouseover')
|
||||
cy.contains('study time: 22.5 months')
|
||||
cy.contains('19 graduated on time')
|
||||
cy.contains('5 graduated max year overtime')
|
||||
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.get('[data-cy=graduation-times-graphBachelor]').within(() => {
|
||||
cy.contains('Bachelor + master study right')
|
||||
cy.contains('Graduation year')
|
||||
cy.contains('2020')
|
||||
cy.contains('0 graduated').trigger('mouseover')
|
||||
cy.contains('study time: 0 months')
|
||||
cy.contains('0 graduated on time')
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/* Studytrack overview -tests */
|
||||
describe('Studytrack overview works for basic user', () => {
|
||||
/* Study track overview tests */
|
||||
describe('Study track overview works for basic user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/study-programme')
|
||||
cy.contains('a', 'Tietojenkäsittelytieteen kandiohjelma').click()
|
||||
cy.get('.attached').contains('Studytracks and class statistics').click()
|
||||
cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click()
|
||||
cy.get('.attached').contains('Study tracks and class statistics').click()
|
||||
})
|
||||
|
||||
// If the backend breaks for one of the sections, the section header is not rendered and this will fail
|
||||
it('Studytracks and class statistics -tab loads', () => {
|
||||
cy.get('[data-cy=Section-StudytrackOverview]')
|
||||
cy.get('[data-cy=Section-StudytrackProgress]')
|
||||
cy.get('[data-cy=Section-AverageGraduationTimesStudytracks]')
|
||||
it('Study tracks and class statistics -tab loads', () => {
|
||||
cy.get('[data-cy=Section-studyTrackOverview]')
|
||||
cy.get('[data-cy=Section-studyTrackProgress]')
|
||||
cy.get('[data-cy=Section-averageGraduationTimesStudyTracks]')
|
||||
})
|
||||
|
||||
it('Students of the studyprogramme are shown correctly', () => {
|
||||
it('Students of the study programme are shown correctly', () => {
|
||||
const tableContents = [
|
||||
// [Year, All, Started studying, Present, Absent, Inactive, Graduated, Men, Women, Other/Unknown, Finland, Other]
|
||||
['2020 - 2021', 12, 10, 0, 0, 12, 0, 10, 2, 0, 12, 0],
|
||||
['2019 - 2020', 86, 83, 0, 0, 85, 1, 65, 21, 0, 86, 0],
|
||||
['2018 - 2019', 162, 150, 0, 0, 151, 11, 118, 44, 0, 161, 1],
|
||||
['2017 - 2018', 171, 158, 0, 0, 130, 41, 135, 36, 0, 166, 5],
|
||||
['Total', 431, 401, 0, 0, 378, 53, 328, 103, 0, 425, 6],
|
||||
['2023 - 2024', 8, 8, 0, 0, 8, 0, 5, 3, 0, 8, 0],
|
||||
['2022 - 2023', 26, 25, 0, 0, 24, 2, 19, 7, 0, 25, 1],
|
||||
['2021 - 2022', 38, 29, 0, 0, 33, 5, 29, 9, 0, 35, 3],
|
||||
['2020 - 2021', 30, 26, 0, 0, 11, 19, 15, 15, 0, 29, 1],
|
||||
['2019 - 2020', 35, 28, 0, 0, 8, 27, 22, 13, 0, 34, 1],
|
||||
['2018 - 2019', 45, 40, 0, 0, 6, 39, 26, 19, 0, 44, 1],
|
||||
['2017 - 2018', 47, 41, 0, 0, 5, 42, 31, 16, 0, 47, 0],
|
||||
['Total', 229, 197, 0, 0, 95, 134, 147, 82, 0, 222, 7],
|
||||
]
|
||||
|
||||
cy.checkTableStats(tableContents, 'StudytrackOverview')
|
||||
cy.checkTableStats(tableContents, 'StudyTrackOverview')
|
||||
})
|
||||
|
||||
it('Years in the students table can be expanded and study track data will be shown', () => {
|
||||
cy.get('[data-cy=Table-StudyTrackOverview]').within(() => {
|
||||
cy.get('tbody tr.header-row')
|
||||
.eq(3)
|
||||
.within(() => {
|
||||
cy.get('td i.angle.right.icon').click()
|
||||
})
|
||||
|
||||
const dataForStudyTracks = [
|
||||
['Ekonometria, MAT-EKO', 2, 2, 0, 0, 0, 2, 1, 1, 0, 2, 0],
|
||||
['Matematiikka, MAT-MAT', 13, 10, 0, 0, 1, 12, 7, 6, 0, 12, 1],
|
||||
['Tietojenkäsittelyteoria, MAT-TIE', 2, 1, 0, 0, 1, 1, 2, 0, 0, 2, 0],
|
||||
['Tilastotiede, MAT-TIL', 4, 4, 0, 0, 0, 4, 1, 3, 0, 4, 0],
|
||||
]
|
||||
|
||||
dataForStudyTracks.forEach((data, index) => {
|
||||
cy.get('tbody tr.regular-row')
|
||||
.eq(index)
|
||||
.within(() => {
|
||||
cy.get('td').each((cell, i) => {
|
||||
cy.wrap(cell).contains(data[i])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('tbody tr.header-row')
|
||||
.eq(3)
|
||||
.within(() => {
|
||||
cy.get('td i.angle.down.icon').click()
|
||||
})
|
||||
|
||||
cy.get('tbody tr.regular-row').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
it('Links to class statistics page work', () => {
|
||||
cy.get('[data-cy=Table-StudyTrackOverview]').within(() => {
|
||||
cy.get('tbody tr.header-row')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('td i.level.up.alternate.icon').click()
|
||||
})
|
||||
})
|
||||
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma 2022 - 2023')
|
||||
cy.contains('class size 26 students')
|
||||
})
|
||||
|
||||
it('Links to class statistics page with all years combined work', { retries: 2 }, () => {
|
||||
cy.get('[data-cy=Table-StudyTrackOverview]').within(() => {
|
||||
cy.get('td.total-row-cell a').click()
|
||||
})
|
||||
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma')
|
||||
cy.contains('class size 227 students')
|
||||
})
|
||||
|
||||
it('Links to class statistics page with study track info included work', () => {
|
||||
cy.get('[data-cy=Table-StudyTrackOverview]').within(() => {
|
||||
cy.get('tbody tr.header-row')
|
||||
.eq(3)
|
||||
.within(() => {
|
||||
cy.get('td i.angle.right.icon').click()
|
||||
})
|
||||
})
|
||||
|
||||
cy.contains('td', 'Matematiikka, MAT-MAT').within(() => {
|
||||
cy.get('a').click()
|
||||
})
|
||||
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma 2020 - 2021')
|
||||
cy.contains('div.sub.header', 'studytrack MAT-MAT')
|
||||
cy.contains('class size 30 students')
|
||||
cy.contains('10 students out of 30 shown')
|
||||
})
|
||||
|
||||
it('Student progress data is shown correctly', () => {
|
||||
@@ -256,33 +347,124 @@ describe('Studyprogramme overview', () => {
|
||||
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]),
|
||||
['2021 - 2022', 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
['2020 - 2021', 12, 12, 0, 0, 0, 0, 0, 0],
|
||||
['2019 - 2020', 86, 26, 34, 21, 5, 0, 0, 0],
|
||||
['2018 - 2019', 162, 10, 28, 47, 35, 26, 12, 4],
|
||||
['2017 - 2018', 171, 20, 21, 26, 22, 22, 33, 27],
|
||||
['Total', 431, 68, 83, 94, 62, 48, 45, 31],
|
||||
['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],
|
||||
]
|
||||
|
||||
cy.checkTableStats(tableContents, 'StudytrackProgress')
|
||||
cy.checkTableStats(tableContents, 'StudyTrackProgress')
|
||||
})
|
||||
|
||||
it('Studytrack overview graphs render', () => {
|
||||
cy.get('[data-cy=Graph-StudytrackProgress]')
|
||||
it('Study track overview graphs render', () => {
|
||||
cy.get('[data-cy=Graph-StudyTrackProgress]')
|
||||
.should('contain', 'Less than 30 credits')
|
||||
.should('contain', '30–60 credits')
|
||||
.should('contain', 'At least 180 credits')
|
||||
.should('contain', '100.0%') // The percentage for less than 15 credits in 2017-2018, to check that the graph renders
|
||||
.should('contain', '49.8%') // The percentage for total, at least 180 credits, to check that the graph renders
|
||||
|
||||
cy.get('[data-cy=Graph-StudyTrackProgress]').contains('49.8%').trigger('mouseover', { force: true })
|
||||
cy.contains('At least 180 credits: 114')
|
||||
|
||||
cy.get("[data-cy='Section-KH50_001']").within(() => {
|
||||
cy.contains('Start year')
|
||||
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.contains("Click a bar to view that year's study track level breakdown").should('not.exist')
|
||||
cy.contains('Year 2019 - 2020 by start year')
|
||||
cy.get('[aria-label="MAT-MAT, 14. On time."]').trigger('mouseover')
|
||||
cy.contains('Matematiikka')
|
||||
cy.contains('MAT-MAT')
|
||||
cy.contains('On time: 14')
|
||||
})
|
||||
|
||||
cy.get('[data-cy=graduation-times-graph-breakdownBachelor]')
|
||||
cy.get('[data-cy=GraduationTimeToggle]').click()
|
||||
|
||||
cy.get('[data-cy=graduation-times-graphBachelor]').within(() => {
|
||||
cy.get("[data-cy='Section-KH50_001']").within(() => {
|
||||
cy.contains('Start year')
|
||||
cy.contains('2017 - 2018')
|
||||
cy.contains('41 graduated').trigger('mouseover')
|
||||
cy.contains('study time: 31 months')
|
||||
cy.contains('36 graduated on time')
|
||||
cy.contains('2020 - 2021')
|
||||
cy.contains('19 graduated').trigger('mouseover')
|
||||
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('1 graduated over year late')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Study track can be changed', () => {
|
||||
beforeEach(() => {
|
||||
cy.get('.studytrack-selector').contains('All students of the programme, KH50_001').click()
|
||||
cy.get('.studytrack-selector .visible.menu').contains('Matematiikka, MAT-MAT').click()
|
||||
})
|
||||
|
||||
it('Students of the study track are shown correctly', () => {
|
||||
cy.get("[data-cy='Section-studyTrackOverview']").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]
|
||||
['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, 1],
|
||||
['2019 - 2020', 17, 14, 0, 0, 0, 17, 10, 7, 0, 16, 1],
|
||||
['2018 - 2019', 23, 21, 0, 0, 2, 21, 11, 12, 0, 23, 0],
|
||||
['2017 - 2018', 28, 24, 0, 0, 1, 27, 15, 13, 0, 28, 0],
|
||||
['Total', 89, 73, 0, 0, 6, 83, 48, 41, 0, 87, 2],
|
||||
]
|
||||
cy.checkTableStats(tableContents, 'StudyTrackOverview')
|
||||
})
|
||||
|
||||
it('Links to class statistics page with study track info included work', () => {
|
||||
cy.get('[data-cy=Table-StudyTrackOverview]').within(() => {
|
||||
cy.get('tbody tr.regular-row')
|
||||
.eq(2)
|
||||
.within(() => {
|
||||
cy.get('a').click()
|
||||
})
|
||||
})
|
||||
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma 2020 - 2021')
|
||||
cy.contains('div.sub.header', 'studytrack MAT-MAT')
|
||||
cy.contains('class size 30 students')
|
||||
cy.contains('10 students out of 30 shown')
|
||||
})
|
||||
|
||||
it('Info message about missing progress stats is displayed', () => {
|
||||
cy.contains('.divider', 'Progress of students of the study track MAT-MAT by starting year')
|
||||
cy.contains(
|
||||
'.message',
|
||||
'Currently progress data is only available for all students of the study programme. Please select ”All students of the programme” to view the progress data.'
|
||||
)
|
||||
})
|
||||
|
||||
it('Average graduation times are displayed correctly', () => {
|
||||
cy.get("[data-cy='Section-averageGraduationTimesStudyTracks']")
|
||||
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.contains('Graduated On time: 9 students')
|
||||
})
|
||||
|
||||
cy.get('[data-cy=GraduationTimeToggle]').click()
|
||||
cy.get("[data-cy='graduation-times-graphBachelor']").within(() => {
|
||||
cy.contains('Start year')
|
||||
cy.contains('2020 - 2021')
|
||||
cy.contains('12 graduated').trigger('mouseover')
|
||||
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')
|
||||
cy.contains('2 graduated max year overtime')
|
||||
cy.contains('1 graduated over year late')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -291,7 +473,7 @@ describe('Studyprogramme overview', () => {
|
||||
describe('Programme courses works for basic user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/study-programme')
|
||||
cy.contains('a', 'Tietojenkäsittelytieteen kandiohjelma').click()
|
||||
cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click()
|
||||
cy.get('.attached').contains('Programme courses').click()
|
||||
})
|
||||
|
||||
@@ -301,83 +483,223 @@ describe('Studyprogramme overview', () => {
|
||||
})
|
||||
|
||||
it('time range selection works', () => {
|
||||
cy.get('[data-cy=CoursesSortableTable] tbody').within(() => {
|
||||
cy.get('tr')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains('MAT11008')
|
||||
cy.get('td').eq(1).contains('Advanced calculus')
|
||||
cy.get('td').eq(2).contains('75')
|
||||
})
|
||||
})
|
||||
cy.get('[data-cy=fromYear]').click().contains('2018').click()
|
||||
|
||||
cy.get('[data-cy=toYear').click().contains('2019').click()
|
||||
|
||||
cy.get('[data-cy=CoursesSortableTable]').within(() => {
|
||||
cy.get('tr').eq(1).contains('684')
|
||||
cy.get('[data-cy=CoursesSortableTable] tbody').within(() => {
|
||||
cy.get('tr')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains('MAT11008')
|
||||
cy.get('td').eq(1).contains('Advanced calculus')
|
||||
cy.get('td').eq(2).contains('15')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('calendar year -> academic year toggle works', () => {
|
||||
cy.get('[data-cy=fromYear]').click().contains('2018').click()
|
||||
cy.get('[data-cy=toYear').click().contains('2019').click()
|
||||
cy.get('[data-cy=CoursesSortableTable]').within(() => {
|
||||
cy.get('tr').eq(1).contains('684')
|
||||
})
|
||||
cy.get('[data-cy=calendarAcademicYearToggle]').first().click()
|
||||
cy.get('[data-cy=fromYear]').click().contains('2018-2019').click()
|
||||
cy.get('[data-cy=toYear').click().contains('2019-2020').click()
|
||||
cy.get('[data-cy=CoursesSortableTable]').within(() => {
|
||||
cy.get('tr').eq(1).contains('772')
|
||||
it("'Calendar year/Academic year' toggle works", () => {
|
||||
cy.get('[data-cy=calendarAcademicYearToggle]').click()
|
||||
cy.get('[data-cy=fromYear]').contains('2017-2018')
|
||||
cy.get('[data-cy=toYear').contains('2023-2024')
|
||||
cy.get('[data-cy=CoursesSortableTable] tbody').within(() => {
|
||||
cy.get('tr')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains('MAT11008')
|
||||
cy.get('td').eq(1).contains('Advanced calculus')
|
||||
cy.get('td').eq(2).contains('75')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('contains correct courses in alphabetical order', () => {
|
||||
cy.get('[data-cy=CoursesSortableTable]').within(() => {
|
||||
cy.get('tr').eq(1).contains('Aineopintojen harjoitustyö: Tietokantasovellus')
|
||||
cy.get('tr').eq(-1).contains('Äidinkielinen viestintä')
|
||||
cy.get('tr').eq(1).contains('Advanced calculus')
|
||||
cy.get('tr').eq(-1).contains('Äidinkielen opinnot')
|
||||
})
|
||||
})
|
||||
|
||||
it('different sorting options work', () => {
|
||||
cy.get('[data-cy=CoursesSortableTable]').within(() => {
|
||||
// Test sorting by different columns
|
||||
// Course code
|
||||
cy.get('th').eq(0).click()
|
||||
cy.get('tr').eq(2).contains('Tietojenkäsittelytieteen kisälliopetus')
|
||||
cy.get('tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains('odgi-zef0')
|
||||
cy.get('td').eq(1).contains('Työ- ja organisaatiopsykologian perusopinnot')
|
||||
})
|
||||
cy.get('th').eq(0).click()
|
||||
cy.get('tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains('MAT-yht')
|
||||
cy.get('td').eq(1).contains('Muut opinnot')
|
||||
})
|
||||
|
||||
// Course name
|
||||
cy.get('th').eq(1).click()
|
||||
cy.get('tr').eq(1).contains('Äidinkielinen viestintä')
|
||||
cy.get('tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains('MAT20003')
|
||||
cy.get('td').eq(1).contains('Äidinkielen opinnot')
|
||||
})
|
||||
cy.get('th').eq(1).click()
|
||||
cy.get('tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains('MAT11008')
|
||||
cy.get('td').eq(1).contains('Advanced calculus')
|
||||
})
|
||||
|
||||
// Total credits
|
||||
cy.get('th').eq(2).click()
|
||||
cy.get('tr').eq(1).contains('Tietorakenteet ja algoritmit')
|
||||
cy.get('tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains('MAT110')
|
||||
cy.get('td').eq(1).contains('Matematiikka, perusopinnot')
|
||||
})
|
||||
cy.get('th').eq(2).click()
|
||||
cy.get('tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains('MAT20001')
|
||||
cy.get('td').eq(1).contains('Kypsyysnäyte')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('show credits -> show students toggle works', () => {
|
||||
cy.get('[data-cy=CoursesSortableTable]').within(() => {
|
||||
cy.get('tr').eq(1).contains('1068')
|
||||
it("'Show credits/Show students' toggle works", () => {
|
||||
cy.get('[data-cy=CoursesSortableTable] thead tr').within(() => {
|
||||
cy.get('th').should('have.length', 8)
|
||||
const headers = [
|
||||
'Code',
|
||||
'Name',
|
||||
'Total credits',
|
||||
'Major credits',
|
||||
'Non-major credits',
|
||||
'Non-degree credits',
|
||||
'Transferred credits',
|
||||
'Type',
|
||||
]
|
||||
headers.forEach((header, index) => {
|
||||
cy.get('th').eq(index).contains(header)
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('[data-cy=creditsStudentsToggle]').first().click()
|
||||
cy.get('[data-cy=CoursesSortableTable] tbody').within(() => {
|
||||
cy.get('tr')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains('MAT11008')
|
||||
cy.get('td').eq(1).contains('Advanced calculus')
|
||||
cy.get('td').eq(2).contains('75')
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('[data-cy=CoursesSortableTable]').within(() => {
|
||||
cy.get('tr').eq(2).contains('267')
|
||||
cy.get('[data-cy=creditsStudentsToggle]').click()
|
||||
|
||||
cy.get('[data-cy=CoursesSortableTable] thead tr')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.length', 7)
|
||||
const headers = [
|
||||
'Code',
|
||||
'Name',
|
||||
'Total',
|
||||
'Breakdown of total',
|
||||
'Breakdown of passed students',
|
||||
'Not included in total nor passed',
|
||||
'Type',
|
||||
]
|
||||
headers.forEach((header, index) => {
|
||||
cy.get('th').eq(index).contains(header)
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('[data-cy=CoursesSortableTable] thead tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.length', 6)
|
||||
const headers = [
|
||||
'Passed',
|
||||
'Not completed',
|
||||
'Major students',
|
||||
'Non-major students',
|
||||
'Non-degree students',
|
||||
'Transferred students',
|
||||
]
|
||||
headers.forEach((header, index) => {
|
||||
cy.get('th').eq(index).contains(header)
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('[data-cy=CoursesSortableTable] tbody').within(() => {
|
||||
cy.get('tr')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('td').eq(0).contains('MAT11008')
|
||||
cy.get('td').eq(1).contains('Advanced calculus')
|
||||
cy.get('td').eq(2).contains('23')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Degree courses works for basic user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/study-programme')
|
||||
cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click()
|
||||
cy.get('.attached').contains('Degree courses').click()
|
||||
})
|
||||
|
||||
it('content loads', () => {
|
||||
cy.contains('h3', 'Select curriculum to edit:')
|
||||
cy.get('[data-cy=curriculum-picker]').contains('2023–2026')
|
||||
cy.contains('Change visibility of degree courses and select criteria for academic years')
|
||||
cy.contains('form', 'First year (12 months) last set: 0')
|
||||
cy.get('table').within(() => {
|
||||
cy.contains('Muut opinnot')
|
||||
cy.contains('Matematiikka, perusopinnot')
|
||||
cy.contains('div.green.label', 'visible')
|
||||
cy.contains('button', 'Set hidden')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/* Tag-tests */
|
||||
describe('Basic information -view works for basic user', () => {
|
||||
describe('Tags view works for basic user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/study-programme')
|
||||
cy.contains('a', 'Tietojenkäsittelytieteen kandiohjelma').click()
|
||||
cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click({ force: true })
|
||||
cy.get('.attached').contains('Tags').click()
|
||||
})
|
||||
|
||||
it('can create and delete tags for population', () => {
|
||||
const name = `tag-${new Date().getTime()}`
|
||||
cy.get('.tagNameSelectInput > .ui > input').type(name)
|
||||
cy.get('.yearSelectInput').type('2018')
|
||||
cy.get('.yearSelectInput').type('2022')
|
||||
cy.contains('Create a new tag').click()
|
||||
cy.contains(name)
|
||||
cy.contains('2018')
|
||||
cy.contains('2022')
|
||||
deleteTag(name)
|
||||
})
|
||||
|
||||
it('can create personal tags', () => {
|
||||
const name = `tag-${new Date().getTime()}`
|
||||
cy.get('.tagNameSelectInput > .ui > input').type(name)
|
||||
cy.get('.yearSelectInput').type('2018')
|
||||
cy.get('.yearSelectInput').type('2022')
|
||||
|
||||
cy.get('.ui > label').click()
|
||||
cy.contains('Create a new tag').click()
|
||||
@@ -385,53 +707,66 @@ describe('Studyprogramme overview', () => {
|
||||
deleteTag(name)
|
||||
})
|
||||
|
||||
it('can add tags to students', () => {
|
||||
describe('Adding tags to students and removing them works', () => {
|
||||
const name = `tag-${new Date().getTime()}`
|
||||
const studentInput = '477806,478275; 478953 479239\n 480080'
|
||||
const studentNumbers = studentInput.match(/[^\s,;]+/g)
|
||||
|
||||
const student = '010113437'
|
||||
cy.get('.tagNameSelectInput > .ui > input').type(name)
|
||||
cy.get('.yearSelectInput').type('2018')
|
||||
cy.contains('Create a new tag').click()
|
||||
cy.contains(name)
|
||||
it('can add tags to students', () => {
|
||||
cy.get('.tagNameSelectInput > .ui > input').type(name)
|
||||
cy.get('.yearSelectInput').type('2022')
|
||||
cy.contains('Create a new tag').click()
|
||||
cy.contains(name)
|
||||
|
||||
cy.contains('Add a tag to students').click()
|
||||
cy.get('.form > .field > .dropdown').click().get('.ui > input.search').type(name).click()
|
||||
cy.contains('Add a tag to students').click()
|
||||
cy.get('.form > .field > .dropdown').click().get('.ui > input.search').type(name).click()
|
||||
|
||||
cy.get('.form > .field > .dropdown > .visible').contains(name).click()
|
||||
cy.get('.form > .field > .dropdown > .visible').contains(name).click()
|
||||
|
||||
cy.get('textarea').type('010113437')
|
||||
cy.contains('Add tags').click()
|
||||
cy.get('textarea').type(studentInput)
|
||||
cy.contains('Add tags').click()
|
||||
|
||||
cy.contains('Successfully added tags to students.')
|
||||
cy.contains('Successfully added tags to students.')
|
||||
|
||||
cy.contains('Students').click()
|
||||
cy.get('.prompt').type(student)
|
||||
cy.contains('a', student).click()
|
||||
cy.contains(name)
|
||||
cy.contains('td', name).get('i.level.up.alternate.icon').click()
|
||||
|
||||
cy.go('back')
|
||||
cy.go('back')
|
||||
deleteTag(name)
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma 2022 - 2023')
|
||||
cy.contains(`Tagged with: ${name}`)
|
||||
cy.contains('Students (5)')
|
||||
.parent()
|
||||
.then($parentDiv => {
|
||||
if (!$parentDiv.hasClass('active')) cy.contains('Students (5)').click()
|
||||
})
|
||||
|
||||
cy.contains('Students').click()
|
||||
cy.get('.prompt').type(student)
|
||||
cy.contains('a', student).click()
|
||||
cy.contains(name).should('not.exist')
|
||||
for (const studentNumber of studentNumbers) {
|
||||
cy.contains(studentNumber)
|
||||
}
|
||||
|
||||
cy.go('back')
|
||||
deleteTag(name)
|
||||
})
|
||||
|
||||
it('deleting a tag from tag view also removes it from students', () => {
|
||||
cy.contains(name).should('not.exist')
|
||||
for (const studentNumber of studentNumbers) {
|
||||
cy.contains('Students').click()
|
||||
cy.get('.prompt').type(studentNumber)
|
||||
cy.contains('a', studentNumber).click()
|
||||
cy.contains(name).should('not.exist')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('IAM user', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/study-programme', 'onlyiamrights')
|
||||
|
||||
cy.contains('a', 'Tietojenkäsittelytieteen kandiohjelma').dblclick()
|
||||
cy.contains('a', 'Matemaattisten tieteiden kandiohjelma').click()
|
||||
})
|
||||
|
||||
it('can access programme and correct tabs are visible', () => {
|
||||
cy.reload()
|
||||
|
||||
cy.contains('Basic information')
|
||||
cy.contains('Studytracks and class statistics')
|
||||
cy.contains('Study tracks and class statistics')
|
||||
|
||||
cy.contains('Update statistics').should('not.exist')
|
||||
cy.contains('Degree courses').should('not.exist')
|
||||
@@ -440,19 +775,23 @@ describe('Studyprogramme overview', () => {
|
||||
it('can access basic information', () => {
|
||||
cy.contains('Basic information').click()
|
||||
|
||||
cy.get('[data-cy=Section-StudentsOfTheStudyprogramme]')
|
||||
cy.get('[data-cy=Section-CreditsProducedByTheStudyprogramme]')
|
||||
cy.get('[data-cy=Section-GraduatedAndThesisWritersOfTheProgramme]')
|
||||
cy.get('[data-cy=Section-ProgrammesBeforeOrAfter]')
|
||||
cy.get('[data-cy=Section-AverageGraduationTimes]')
|
||||
cy.get('[data-cy=Section-studentsOfTheStudyProgramme]')
|
||||
cy.get('[data-cy=Section-creditsProducedByTheStudyProgramme]')
|
||||
cy.get('[data-cy=Section-graduatedAndThesisWritersOfTheProgramme]')
|
||||
cy.get('[data-cy=Section-programmesBeforeOrAfter]')
|
||||
cy.get('[data-cy=Section-averageGraduationTimes]')
|
||||
})
|
||||
|
||||
it('can access studytracks', () => {
|
||||
cy.get('.attached').contains('Studytracks and class statistics').click()
|
||||
it('can access study tracks', () => {
|
||||
cy.get('.attached').contains('Study tracks and class statistics').click()
|
||||
|
||||
cy.get('[data-cy=Section-StudytrackOverview]')
|
||||
cy.get('[data-cy=Section-StudytrackProgress]')
|
||||
cy.get('[data-cy=Section-AverageGraduationTimesStudytracks]')
|
||||
cy.get('[data-cy=Section-studyTrackOverview]')
|
||||
cy.get('[data-cy=Section-studyTrackProgress]')
|
||||
cy.get('[data-cy=Section-averageGraduationTimesStudyTracks]')
|
||||
})
|
||||
|
||||
it("doesn't see other tabs", () => {
|
||||
cy.get('div.ui.tabular.menu a').should('have.length', 2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+35
-21
@@ -1,50 +1,64 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
|
||||
describe('Teachers page tests', () => {
|
||||
beforeEach(() => {
|
||||
// login as admin = has teacher rights
|
||||
cy.init('/teachers', 'admin')
|
||||
cy.url().should('include', '/teachers')
|
||||
})
|
||||
|
||||
const teacher1 = 'Landgraf Leo'
|
||||
const teacher2 = 'Vinogradov Jussi Petteri'
|
||||
const teacher1 = 'Luokkanen Liisa Viljami'
|
||||
const teacher2 = 'Perälä Juhani Susanna'
|
||||
|
||||
it('Check Statistics', () => {
|
||||
cy.get(':nth-child(1) > .ui > .search').click()
|
||||
cy.contains('Syksy 2020').click()
|
||||
cy.cs('course-providers').click()
|
||||
cy.contains('Tietojenkäsittelytieteen kandiohjelma').click()
|
||||
cy.contains('Matemaattisten tieteiden kandiohjelma').click()
|
||||
cy.cs('course-providers').children('.icon').click()
|
||||
cy.get('.form > .fluid').click()
|
||||
cy.contains('button', 'Search').click()
|
||||
cy.contains('Teacher')
|
||||
cy.contains(teacher1)
|
||||
cy.contains(teacher2)
|
||||
const headers = ['Name', 'Credits', 'Credits transferred', 'Passed']
|
||||
cy.get('table thead tr th').should('have.length', 4)
|
||||
headers.forEach((header, index) => {
|
||||
cy.get('table thead tr th').eq(index).should('contain', header)
|
||||
})
|
||||
cy.contains('td', teacher1).siblings().eq(0).contains('235')
|
||||
cy.contains('td', teacher1).siblings().eq(2).contains('97.40%')
|
||||
cy.contains('td', teacher2).siblings().eq(0).contains('395')
|
||||
cy.contains('td', teacher2).siblings().eq(2).contains('98.78%')
|
||||
})
|
||||
|
||||
it('Teacher search works', () => {
|
||||
cy.url().should('include', '/teachers')
|
||||
cy.get('.borderless > :nth-child(3)').click()
|
||||
cy.get('.prompt').type(teacher1.split()[0])
|
||||
cy.contains('td', teacher1)
|
||||
cy.get('.prompt').type(teacher1.split(' ')[0])
|
||||
cy.get('table tbody tr').should('have.length', 4)
|
||||
cy.get('table tbody tr').eq(2).contains('td', teacher1)
|
||||
})
|
||||
|
||||
it('Can check teacher page', () => {
|
||||
cy.visit('/teachers/hy-hlo-51367956')
|
||||
cy.contains('Landgraf Leo')
|
||||
cy.get('.borderless > :nth-child(3)').click()
|
||||
cy.get('.prompt').type(teacher2)
|
||||
|
||||
// Prevent opening in new tab
|
||||
cy.contains('a', teacher2).invoke('removeAttr', 'target').click()
|
||||
cy.url().should('include', '/teachers/hy-hlo-49026530')
|
||||
cy.contains(teacher2)
|
||||
cy.contains('Syksy 2023').click()
|
||||
cy.contains('Kevät 2019').click()
|
||||
cy.get('.ok-sortable-table tbody tr')
|
||||
.first()
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.contains('td:nth-child(1)', 'TKT20011')
|
||||
cy.contains('td:nth-child(2)', 'Aineopintojen harjoitustyö: Tietokantasovellus')
|
||||
cy.contains('td:nth-child(3)', '96')
|
||||
cy.contains('td:nth-child(4)', '0')
|
||||
cy.contains('td:nth-child(5)', '88.89%')
|
||||
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({ force: true })
|
||||
cy.get(':nth-child(1) > .ui > .search').click({ force: true })
|
||||
cy.contains('2020-2021').click({ force: true })
|
||||
cy.get('.borderless > :nth-child(2)').click()
|
||||
cy.get(':nth-child(1) > .ui > .search').click()
|
||||
cy.contains('2020-2021').click()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const progressLevels = ['Bachelors', 'BachelorMasters', 'Masters', 'Doctoral']
|
||||
|
||||
const checkProgressBarCharts = () => {
|
||||
progressLevels.forEach(level => cy.get(`[data-cy=Faculty${level}ProgressBarChart]`))
|
||||
}
|
||||
|
||||
const checkProgressTables = () => {
|
||||
progressLevels.forEach(level => cy.get(`[data-cy=Faculty${level}ProgressTable]`))
|
||||
}
|
||||
|
||||
const graduationTimesLevels = ['bachelor', 'master', 'doctor'] // ? Bachelor + master seems to be missing in test data
|
||||
|
||||
const checkAverageGraduationTimesBreakdownBarCharts = () => {
|
||||
graduationTimesLevels.forEach(level => {
|
||||
cy.get(`[data-cy=${level}BreakdownBarChart]`)
|
||||
})
|
||||
}
|
||||
|
||||
const checkAverageGraduationTimesMedianBarCharts = () => {
|
||||
graduationTimesLevels.forEach(level => {
|
||||
cy.get(`[data-cy=${level}MedianBarChart]`)
|
||||
})
|
||||
}
|
||||
|
||||
describe('University view', () => {
|
||||
beforeEach(() => {
|
||||
cy.init('/university')
|
||||
cy.contains('University')
|
||||
cy.contains('Programme MH90_001 (Veterinary medicine bachelor + licentiate) is currently excluded.')
|
||||
})
|
||||
|
||||
describe('Faculty progress tab', () => {
|
||||
it('contains all the correct progress bar charts', () => {
|
||||
checkProgressBarCharts()
|
||||
})
|
||||
|
||||
it('contains all the correct progress tables', () => {
|
||||
checkProgressTables()
|
||||
})
|
||||
|
||||
it("'Graduated included / Graduated excluded' toggle works", () => {
|
||||
cy.get('[data-cy=GraduatedToggle]').click()
|
||||
checkProgressBarCharts()
|
||||
checkProgressTables()
|
||||
})
|
||||
|
||||
it("'All study rights / Special study rights excluded' toggle works", () => {
|
||||
cy.get('[data-cy=StudyRightToggle]').click()
|
||||
checkProgressBarCharts()
|
||||
checkProgressTables()
|
||||
})
|
||||
|
||||
it('years in the tables can be clicked to show faculty level breakdown', () => {
|
||||
cy.get('[data-cy=StudyRightToggle]').click()
|
||||
cy.get('[data-cy=FacultyBachelorsProgressTableShowButton3]').click()
|
||||
cy.contains('29.5%').trigger('mouseover', { force: true })
|
||||
cy.contains('Matemaattis-luonnontieteellinen tiedekunta')
|
||||
cy.contains('H50')
|
||||
cy.contains('0 Credits: 0')
|
||||
cy.contains('1 ≤ Credits < 45: 13')
|
||||
cy.contains('45 ≤ Credits < 90: 10')
|
||||
cy.contains('90 ≤ Credits < 135: 16')
|
||||
cy.contains('135 ≤ Credits < 180: 3')
|
||||
cy.contains('180 ≤ Credits: 2')
|
||||
})
|
||||
|
||||
it('info boxes contain correct information', () => {
|
||||
cy.get('[data-cy=FacultyProgressInfoBoxButton]').click()
|
||||
cy.get('[data-cy=FacultyProgressInfoBoxContent]').contains('Kuvaa tiedekuntaan kuuluvien')
|
||||
cy.get('[data-cy=FacultyBachelorMastersProgressInfoBoxButton]').click()
|
||||
cy.get('[data-cy=FacultyBachelorMastersProgressInfoBoxContent]').contains('The starting year is the')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Faculty graduations tab', () => {
|
||||
beforeEach(() => {
|
||||
cy.get('[data-cy=FacultyGraduationsTab]').click()
|
||||
})
|
||||
|
||||
it('has all the correct median time bar charts', () => {
|
||||
checkAverageGraduationTimesBreakdownBarCharts()
|
||||
})
|
||||
|
||||
it("'Breakdown/Median times' toggle works", () => {
|
||||
cy.get('[data-cy=GraduationTimeToggle]').click()
|
||||
checkAverageGraduationTimesMedianBarCharts()
|
||||
})
|
||||
|
||||
it('info boxes contain correct information', () => {
|
||||
cy.get('[data-cy=AverageGraduationTimesInfoBoxButton]').click()
|
||||
cy.get('[data-cy=AverageGraduationTimesInfoBoxContent]').contains('Opiskelijoiden keskimääräiset valmistumisajat')
|
||||
})
|
||||
})
|
||||
})
|
||||
+78
-37
@@ -1,27 +1,62 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
|
||||
const visibleLinks = {
|
||||
norights: ['University', 'Faculties', 'Special populations', 'Feedback'],
|
||||
onlycoursestatistics: ['University', 'Courses', 'Special populations', 'Feedback'],
|
||||
}
|
||||
|
||||
visibleLinks.basic = [...visibleLinks.onlycoursestatistics, 'Faculties', 'Programmes', 'Students']
|
||||
visibleLinks.admin = [...visibleLinks.basic, 'Teachers', 'Admin']
|
||||
|
||||
const hasVisibleItems = visibleItems => {
|
||||
cy.get('[data-cy=nav-bar]').children().should('have.length', visibleItems)
|
||||
}
|
||||
|
||||
const containsLinks = links => {
|
||||
cy.get('[data-cy=nav-bar]').within(() => {
|
||||
for (const link of links) {
|
||||
cy.contains(link)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const userButtonWorks = (username, mocking = false) => {
|
||||
cy.get('[data-cy=nav-bar-user-button]').click()
|
||||
cy.contains(mocking ? `Mocking as ${username}` : `Logged in as ${username}`)
|
||||
cy.contains('Language')
|
||||
cy.contains('suomi')
|
||||
cy.contains('English')
|
||||
cy.contains('svenska')
|
||||
cy.contains(mocking ? 'Stop mocking' : 'Log out')
|
||||
}
|
||||
|
||||
describe('Users tests', () => {
|
||||
describe('Using user with just grp-oodikone-user, no other rights', () => {
|
||||
it('shows only frontpage, university, feedback', () => {
|
||||
cy.init('', 'norights') // login with norights user
|
||||
cy.get('[data-cy="navBar"]').contains('University').should('exist')
|
||||
cy.get('[data-cy="navBar"]').contains('Give feedback').should('exist')
|
||||
|
||||
cy.get('[data-cy="navBar"]').contains('Programmes').should('not.exist')
|
||||
cy.get('[data-cy="navBar"]').contains('Students').should('not.exist')
|
||||
cy.get('[data-cy="navBar"]').contains('Courses').should('not.exist')
|
||||
cy.get('[data-cy="navBar"]').contains('Teachers').should('not.exist')
|
||||
cy.get('[data-cy="navBar"]').contains('Users').should('not.exist')
|
||||
cy.get('[data-cy="navBar"]').contains('Faculty').should('not.exist')
|
||||
cy.get('[data-cy="navBar"]').contains('Updater').should('not.exist')
|
||||
describe('Using as user with just grp-oodikone-user, no other rights', () => {
|
||||
it('shows correct tabs', () => {
|
||||
cy.init('', 'norights')
|
||||
hasVisibleItems(7)
|
||||
containsLinks(visibleLinks.norights)
|
||||
userButtonWorks('norights')
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: Add user right checks for onlycoursestats user (should be like above + should
|
||||
// show course stats
|
||||
//
|
||||
// TODO: Add user right checks for basic user (should show also study programme,
|
||||
// student stats, course stats
|
||||
describe('Using as coursestatistics user', () => {
|
||||
it('shows correct tabs', () => {
|
||||
cy.init('', 'onlycoursestatistics')
|
||||
hasVisibleItems(7)
|
||||
containsLinks(visibleLinks.onlycoursestatistics)
|
||||
userButtonWorks('onlycoursestatistics')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Using as basic user', () => {
|
||||
it('shows correct tabs', () => {
|
||||
cy.init('')
|
||||
hasVisibleItems(10)
|
||||
containsLinks(visibleLinks.basic)
|
||||
userButtonWorks('basic')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Using as admin', () => {
|
||||
beforeEach(() => {
|
||||
@@ -29,26 +64,32 @@ describe('Users tests', () => {
|
||||
})
|
||||
|
||||
it('should see more stuff than others', () => {
|
||||
cy.get('[data-cy="navBar"]').contains('Programmes').should('exist')
|
||||
cy.get('[data-cy="navBar"]').contains('Students').should('exist')
|
||||
cy.get('[data-cy="navBar"]').contains('Courses').should('exist')
|
||||
// TODO: admin should probably see this as well, since can access this page: cy.get('[data-cy="navBar"]').contains('Teachers').should('exist')
|
||||
cy.get('[data-cy="navBar"]').contains('Users').should('exist')
|
||||
cy.get('[data-cy="navBar"]').contains('University').should('exist')
|
||||
hasVisibleItems(13)
|
||||
containsLinks(visibleLinks.admin)
|
||||
userButtonWorks('admin')
|
||||
})
|
||||
|
||||
it("mocking normal user shows only the mocked user's programmes", () => {
|
||||
cy.contains('mocking').should('not.exist')
|
||||
cy.cs('user-edit-button-basic').click()
|
||||
cy.get('i.spy').click()
|
||||
cy.contains('mocking as basic')
|
||||
cy.contains('Programmes').click().siblings().contains('Class statistics').click()
|
||||
cy.contains('label', 'Study programme')
|
||||
cy.contains('label', 'Study programme')
|
||||
.siblings()
|
||||
.within(() => {
|
||||
cy.get("div[role='option']").should('have.length', 2).contains('Tietojenkäsittelytieteen kandiohjelma')
|
||||
})
|
||||
describe('can mock as other users', () => {
|
||||
beforeEach(() => {
|
||||
cy.cs('user-edit-button-basic').click()
|
||||
cy.get('i.spy').click()
|
||||
})
|
||||
|
||||
it('user button shows mocked user', () => {
|
||||
userButtonWorks('basic', true)
|
||||
})
|
||||
|
||||
it("only the mocked user's programmes are visible", () => {
|
||||
cy.get('[data-cy=nav-bar-button-studyProgramme]').click()
|
||||
cy.get('[data-cy=nav-bar-button-class]').click()
|
||||
cy.contains('label', 'Study programme')
|
||||
.siblings()
|
||||
.within(() => {
|
||||
cy.get("div[role='option']").should('have.length', 2)
|
||||
cy.get("div[role='option']").eq(0).contains('Matemaattisten tieteiden kandiohjelma')
|
||||
cy.get("div[role='option']").eq(1).contains('Matematiikan ja tilastotieteen maisteriohjelma')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"studentSet1": ["010450938", "010589388", "010614509"],
|
||||
"studentSet1": ["433237", "457144", "458090", "474270", "479440"],
|
||||
"coursesSet1": ["TKT10001", "TKT10002", "TKT10003", "TKT10004"]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"studentNumbersForCSStudentsSet1": ["010450938", "010589388", "010614509"],
|
||||
"studentNumbersForCSStudentsSet2": ["010262566", "010307146"]
|
||||
"studentNumbersForCSStudentsSet1": ["433237", "457144", "458090", "465136"],
|
||||
"studentNumbersForCSStudentsSet2": ["474270", "479440", "470391", "474789"]
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
{
|
||||
"id": "sgg-cypress-1",
|
||||
"name": {
|
||||
"fi": "TKT kandit 2018"
|
||||
"fi": "MAT kandit 2020"
|
||||
},
|
||||
"members": [
|
||||
{ "personStudentNumber": "010450938" },
|
||||
{ "personStudentNumber": "010589388" },
|
||||
{ "personStudentNumber": "010262566" }
|
||||
{ "personStudentNumber": "433237" },
|
||||
{ "personStudentNumber": "457144" },
|
||||
{ "personStudentNumber": "458090" }
|
||||
],
|
||||
"tags": {
|
||||
"studyProgramme": "KH50_005",
|
||||
"year": "2018"
|
||||
"studyProgramme": "KH50_001",
|
||||
"year": "2020"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -19,7 +19,7 @@
|
||||
"name": {
|
||||
"fi": "Oma ohjausryhmä"
|
||||
},
|
||||
"members": [{ "personStudentNumber": "010614509" }, { "personStudentNumber": "010307146" }],
|
||||
"members": [{ "personStudentNumber": "474270" }, { "personStudentNumber": "479440" }],
|
||||
"tags": {
|
||||
"studyProgramme": null,
|
||||
"year": null
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
|
||||
+25
-16
@@ -1,4 +1,4 @@
|
||||
/// <reference types="Cypress" />
|
||||
/// <reference types="cypress" />
|
||||
//
|
||||
// ! IMPORTANT: here we need to set keys to be all lowercase, since
|
||||
// ! we're replacing headers after they've left browser / frontend.
|
||||
@@ -43,7 +43,7 @@ const onlyIamRightsUserHeaders = {
|
||||
uid: 'onlyiamrights',
|
||||
displayname: 'Only IAM rights user',
|
||||
'shib-session-id': 'mock-cypress-session',
|
||||
hygroupcn: 'hy-employees;hy-mltdk-tkt-jory',
|
||||
hygroupcn: 'hy-employees;hy-mltdk-mat-jory',
|
||||
mail: 'grp-toska+mockonlyiamrightsuser@helsinki.fi',
|
||||
hypersonsisuid: 'hy-hlo-1111111',
|
||||
}
|
||||
@@ -87,7 +87,7 @@ Cypress.Commands.add('init', (path = '', userId = 'basic') => {
|
||||
if (!headersToUse) throw Error(`${userId} is not valid user id!`)
|
||||
req.headers = headersToUse
|
||||
})
|
||||
const baseUrl = Cypress.config().baseUrl
|
||||
const { baseUrl } = Cypress.config()
|
||||
cy.visit(baseUrl.concat(path))
|
||||
})
|
||||
|
||||
@@ -99,9 +99,8 @@ Cypress.Commands.add('cs', { prevSubject: 'optional' }, (subject, name) => {
|
||||
|
||||
if (subject === undefined) {
|
||||
return cy.get(selector)
|
||||
} else {
|
||||
return cy.wrap(subject).find(selector)
|
||||
}
|
||||
return cy.wrap(subject).find(selector)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -128,20 +127,11 @@ Cypress.Commands.add('selectFromDropdown', { prevSubject: true }, (s, index) =>
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Move to page with studyProgramme population
|
||||
*/
|
||||
Cypress.Commands.add('selectStudyProgramme', name => {
|
||||
cy.cs('navbar-studyProgramme').click()
|
||||
cy.cs('navbar-class').click()
|
||||
cy.cs('select-study-programme').click().children().contains(name).click()
|
||||
cy.contains('See class').click()
|
||||
})
|
||||
|
||||
Cypress.Commands.add('checkTableStats', (correctStats, tableName) => {
|
||||
cy.get(`[data-cy=Table-${tableName}] tbody`).within(() => {
|
||||
correctStats.forEach((values, trIndex) => {
|
||||
cy.get('tr')
|
||||
.filter(':visible')
|
||||
.eq(trIndex)
|
||||
.within(() => {
|
||||
values.forEach((value, tdIndex) => {
|
||||
@@ -149,6 +139,25 @@ Cypress.Commands.add('checkTableStats', (correctStats, tableName) => {
|
||||
})
|
||||
})
|
||||
})
|
||||
cy.get('tr').should('have.length', correctStats.length)
|
||||
cy.get('tr').filter(':visible').should('have.length', correctStats.length)
|
||||
})
|
||||
})
|
||||
|
||||
const getEmptyYears = isAcademicYear => {
|
||||
const today = new Date()
|
||||
const latestYear = isAcademicYear && today.getMonth() < 7 ? today.getFullYear() - 1 : today.getFullYear()
|
||||
|
||||
const years = []
|
||||
for (let year = latestYear; year >= 2024; year--) {
|
||||
if (isAcademicYear) {
|
||||
years.push(`${year} - ${year + 1}`)
|
||||
} else {
|
||||
years.push(year)
|
||||
}
|
||||
}
|
||||
return years
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getEmptyYears,
|
||||
}
|
||||
|
||||
@@ -17,12 +17,8 @@
|
||||
require('@cypress/code-coverage/support')
|
||||
require('./commands')
|
||||
|
||||
// This error is benign and seems to be only present in Cypress
|
||||
const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/
|
||||
|
||||
Cypress.on('uncaught:exception', err => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
Cypress.on('uncaught:exception', error => {
|
||||
/* returning false here prevents Cypress from failing the test */
|
||||
if (resizeObserverLoopErrRe.test(err.message)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -23,13 +23,15 @@ services:
|
||||
JAMI_URL: 'http://jami:3003/'
|
||||
CRYPT_KEY: 'e16730fce263d5270ba8dcf84d068a32556542455e71331880d6c1c5e4db9b4e'
|
||||
build:
|
||||
context: services/backend
|
||||
context: services
|
||||
dockerfile: backend/Dockerfile
|
||||
|
||||
frontend:
|
||||
<<: *defaults
|
||||
container_name: frontend
|
||||
build:
|
||||
context: services/frontend
|
||||
context: services
|
||||
dockerfile: frontend/Dockerfile
|
||||
|
||||
kone-db:
|
||||
<<: *defaults
|
||||
@@ -44,7 +46,7 @@ services:
|
||||
<<: *defaults
|
||||
command: >
|
||||
sh -c 'redis-server --appendonly yes && redis-cli flushall'
|
||||
image: redis:6.2.14
|
||||
image: redis:6.2.16
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
@@ -81,7 +83,7 @@ services:
|
||||
|
||||
jami-db:
|
||||
<<: *defaults
|
||||
image: postgres:15.7
|
||||
image: postgres:15.8
|
||||
environment:
|
||||
- PGDATA=/data
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
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
|
||||
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}
|
||||
+16
-6
@@ -38,15 +38,21 @@ services:
|
||||
SIS_UPDATER_URL: http://sis-updater-scheduler:8082
|
||||
IMPORTER_DB_API_URL: 'https://localhost:3005'
|
||||
JAMI_URL: 'http://jami:3003/'
|
||||
SERVICE_PROVIDER: 'Fd'
|
||||
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:
|
||||
- ./environments/ha:/opt/app-root/src/src/environment
|
||||
- ./services/backend/src:/opt/app-root/src/src
|
||||
- ./services/shared/:/opt/app-root/src/src/shared
|
||||
|
||||
frontend:
|
||||
<<: *defaults
|
||||
build:
|
||||
@@ -56,9 +62,11 @@ services:
|
||||
ports:
|
||||
- 3000:3000
|
||||
environment:
|
||||
REACT_APP_SERVICE_PROVIDER: 'Fd'
|
||||
REACT_APP_SERVICE_PROVIDER: fd
|
||||
REACT_APP_LANGUAGE_CENTER_VIEW_ENABLED: 'false'
|
||||
volumes:
|
||||
- ./services/frontend/src/:/opt/app-root/src/src
|
||||
- ./services/shared/:/opt/app-root/src/src/shared
|
||||
|
||||
kone-db:
|
||||
<<: *defaults
|
||||
@@ -67,7 +75,7 @@ services:
|
||||
PGDATA: /data
|
||||
POSTGRES_DB: kone-db
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
image: postgres:15.7
|
||||
image: postgres:15.8
|
||||
ports:
|
||||
- 12349:5432
|
||||
volumes:
|
||||
@@ -77,7 +85,7 @@ services:
|
||||
<<: *defaults
|
||||
command: 'redis-server --appendonly yes'
|
||||
container_name: redis
|
||||
image: redis:6.2.14
|
||||
image: redis:6.2.16
|
||||
ports:
|
||||
- '6379:6379'
|
||||
volumes:
|
||||
@@ -90,7 +98,7 @@ services:
|
||||
PGDATA: /data
|
||||
POSTGRES_DB: sis-db
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
image: postgres:15.7
|
||||
image: postgres:15.8
|
||||
volumes:
|
||||
- sis-db-data:/data
|
||||
shm_size: 2gb
|
||||
@@ -124,6 +132,7 @@ services:
|
||||
SIS_IMPORTER_USER: dev
|
||||
SIS_NATS_TOKEN: dev
|
||||
SIS_NATS_URI: sis-updater-nats://sis-updater-nats:4222
|
||||
SERVICE_PROVIDER: fd
|
||||
ports:
|
||||
- 8082:8082
|
||||
volumes:
|
||||
@@ -155,6 +164,7 @@ services:
|
||||
SIS_NATS_TOKEN: dev
|
||||
SIS_NATS_URI: sis-updater-nats://sis-updater-nats:4222
|
||||
ROOT_ORG_ID: ${ROOT_ORG_ID}
|
||||
SERVICE_PROVIDER: fd
|
||||
volumes:
|
||||
- ./updater/sis-updater-worker/:/opt/app-root/src
|
||||
|
||||
@@ -165,7 +175,7 @@ services:
|
||||
PGDATA: /data
|
||||
POSTGRES_DB: user-db
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
image: postgres:15.7
|
||||
image: postgres:15.8
|
||||
volumes:
|
||||
- user-db-data:/data
|
||||
ports:
|
||||
|
||||
+4
-2
@@ -46,6 +46,7 @@ services:
|
||||
volumes:
|
||||
- backend-nodemod:/opt/app-root/src/node_modules
|
||||
- ./services/backend/:/opt/app-root/src
|
||||
- ./services/shared/:/opt/app-root/src/src/shared
|
||||
|
||||
frontend:
|
||||
<<: *defaults
|
||||
@@ -60,6 +61,7 @@ services:
|
||||
volumes:
|
||||
- frontend-nodemod:/opt/app-root/src/node_modules
|
||||
- ./services/frontend/:/opt/app-root/src
|
||||
- ./services/shared/:/opt/app-root/src/src/shared
|
||||
|
||||
kone-db:
|
||||
<<: *defaults
|
||||
@@ -74,7 +76,7 @@ services:
|
||||
<<: *defaults
|
||||
command: 'redis-server --appendonly yes'
|
||||
container_name: redis
|
||||
image: redis:6.2.14
|
||||
image: redis:6.2.16
|
||||
ports:
|
||||
- '6379:6379'
|
||||
volumes:
|
||||
@@ -187,7 +189,7 @@ services:
|
||||
|
||||
jami-db:
|
||||
<<: *defaults
|
||||
image: postgres:15.7
|
||||
image: postgres:15.8
|
||||
environment:
|
||||
- PGDATA=/data
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# Access rights in Oodikone
|
||||
|
||||
## Roles
|
||||
|
||||
Roles are defined [here](../services/backend/src/config/roles.ts). Criteria for each role can be found [here](../services/backend/src/services/userService.ts). Currently these roles exist:
|
||||
|
||||
- admin
|
||||
- courseStatistics
|
||||
- facultyStatistics
|
||||
- fullSisuAccess
|
||||
- katselmusViewer
|
||||
- openUniSearch
|
||||
- studyGuidanceGroups
|
||||
- teachers
|
||||
|
||||
The roles `openUniSearch` and `studyGuidanceGroups` simply enable certain views (_Open uni student population_ and _Guidance groups_ respectively).
|
||||
|
||||
### courseStatistics
|
||||
|
||||
- Can see statistics of every course but year statistics of ≤ 5 students are hidden
|
||||
|
||||
### facultyStatistics
|
||||
|
||||
- Can see statistics of every course and year
|
||||
|
||||
### katselemusViewer
|
||||
|
||||
- Enables the _Evaluation overview_ view
|
||||
|
||||
### teachers
|
||||
|
||||
- By default, the role only allows seeing the statistics of course providers (study 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.
|
||||
|
||||
### Other roles
|
||||
|
||||
| | Everybody\* | Admin | fullSisuAccess |
|
||||
| -------------------- | :---------: | :---: | :------------: |
|
||||
| Front page | ✅ | ✅ | ✅ |
|
||||
| Feedback | ✅ | ✅ | ✅ |
|
||||
| University | ✅ | ✅ | ✅ |
|
||||
| Language center view | | ✅ | |
|
||||
| Faculties | | ✅ | ✅ |
|
||||
| Updater | | ✅ | |
|
||||
| Users | | ✅ | |
|
||||
| Programmes | | ✅ | ✅ |
|
||||
| Students | | ✅ | ✅ |
|
||||
| Teachers | | ✅ | |
|
||||
| Guidance groups | | | |
|
||||
| Custom population | | ✅ | ✅ |
|
||||
| Completed courses | ✅ | ✅ | ✅ |
|
||||
| Close to graduation | | ✅ | ✅ |
|
||||
| Open uni population | | ✅ | |
|
||||
| Evaluation overview | | ✅ | |
|
||||
| Courses | | ✅ | ✅ |
|
||||
|
||||
\* = anyone who can log in to Oodikone, check the [IAM groups](#iam-groups) section for more information
|
||||
|
||||
## Programmes
|
||||
|
||||
There are two types of programme rights: limited and full. Limited rights mean that the user cannot see individual students of those programmes, while a user with full rights can.
|
||||
|
||||
Manually given programme rights are **always** full rights. Some of the programme rights are based on user's IAM groups: some only qualify for limited rights, some for full rights.
|
||||
|
||||
## IAM groups
|
||||
|
||||
A user must be a member of either the `grp-oodikone-basic-users` or `grp-oodikone-users` IAM group to be able to log in to Oodikone. The following IAM groups are used to give roles:
|
||||
|
||||
| IAM group | Role given based on the group |
|
||||
| ------------------------ | ----------------------------- |
|
||||
| grp-oodikone-basic-users | courseStatistics |
|
||||
| grp-oodikone-users | facultyStatistics |
|
||||
| grp-toska | admin |
|
||||
| grp-katselmus-\* | katselmusViewer |
|
||||
| hy-ypa-opa-dojo | openUniSearch |
|
||||
|
||||
`grp-oodikone-basic-users`: basically the whole staff (hy-\*-allstaff)
|
||||
|
||||
`grp-oodikone-users`: more limited (e.g. _koulutussuunnittelijat_, _ospa_, _opintoasiainpäälliköt_, _(vara)dekaanit_, _johtoryhmä_)
|
||||
|
||||
`grp-kielikeskus-esihenkilot`: gives access to the language center view
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
@@ -1,20 +1,52 @@
|
||||
# Databases
|
||||
|
||||
Oodikone uses three separate databases, each for different purposes. This document contains a simplified overview of each database and the relations between models.
|
||||
Oodikone uses three main PostgreSQL databases, each for a different purpose. This document contains a simplified overview of each database and the relations between models.
|
||||
|
||||
Use Adminer or refer to the actual database for a detailed view of each model and their attributes.
|
||||
|
||||
## sis-db
|
||||
## Storage
|
||||
|
||||
### sis-db
|
||||
|
||||
The `sis-db` database contains information originating from Sisu.
|
||||
|
||||
_To be documented._
|
||||
```mermaid
|
||||
---
|
||||
title: sis-db
|
||||
---
|
||||
erDiagram
|
||||
enrollment }o--|| course : ""
|
||||
enrollment }o--|| semesters : ""
|
||||
enrollment }o--|| student : ""
|
||||
credit_teachers }o--|| teacher : ""
|
||||
course_types
|
||||
curriculum_periods
|
||||
credit_teachers }o--|| credit : ""
|
||||
credit }o--|| course : ""
|
||||
credit }o--|| semesters : ""
|
||||
credit }o--|| credit_types : ""
|
||||
credit }o--|| student : ""
|
||||
studyplan }o--|| student : ""
|
||||
studyplan }o--|| sis_study_rights : ""
|
||||
sis_study_rights }o--|| studyright_extents : ""
|
||||
sis_study_rights }o--|| organization : ""
|
||||
sis_study_rights }o--|| student : ""
|
||||
sis_study_right_elements }o--|| sis_study_rights : ""
|
||||
course_providers }o--|| organization : ""
|
||||
course_providers }o--|| course : ""
|
||||
programme_module_children }o--|| programme_modules : "references parent"
|
||||
programme_module_children }o--|| programme_modules : "references child"
|
||||
programme_modules }o--o| organization : "belong to"
|
||||
```
|
||||
|
||||
## kone-db
|
||||
### kone-db
|
||||
|
||||
The `kone-db` database contains information used in various features of Oodikone. This functionality is native to Oodikone and not necessarily directly related to Sisu.
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: kone-db
|
||||
---
|
||||
erDiagram
|
||||
custom_population_searches
|
||||
excluded_courses
|
||||
@@ -27,11 +59,20 @@ erDiagram
|
||||
tag_student
|
||||
```
|
||||
|
||||
## user-db
|
||||
### user-db
|
||||
|
||||
The `user-db` database contains information about users of Oodikone.
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: user-db
|
||||
---
|
||||
erDiagram
|
||||
users
|
||||
```
|
||||
|
||||
## Caching
|
||||
|
||||
### Redis
|
||||
|
||||
Redis is used in the backend to cache calculation results.
|
||||
|
||||
@@ -4,7 +4,7 @@ Data used in Oodikone contains some numeric types (or _magic numbers_) that are
|
||||
|
||||
## Extent code
|
||||
|
||||
Appears in model Studyright (db: `studyright`) as _extentcode_ (db: `extentcode`) and in model SISStudyRight (db: `sis_study_rights`) as _extentCode_ (db: `extent_code`). Values are defined in the database table `studyright_extents`.
|
||||
Appears in model SISStudyRight (db: `sis_study_rights`) as _extentCode_ (db: `extent_code`). Values are defined in the database table `studyright_extents`.
|
||||
|
||||
| value | meaning |
|
||||
| ----: | :-------------------------------------------------- |
|
||||
@@ -26,33 +26,6 @@ Appears in model Studyright (db: `studyright`) as _extentcode_ (db: `extentcode`
|
||||
| 34 | Exchange studies (postgraduate studies) |
|
||||
| 99 | Non-Degree studies |
|
||||
|
||||
## Priority code
|
||||
|
||||
> [!WARNING]
|
||||
> Obsolete, to be deprecated
|
||||
|
||||
Appears in model Studyright (db: `studyright`) as _prioritycode_ (db: `prioritycode`).
|
||||
|
||||
| value | meaning | notes |
|
||||
| ----: | :-------- | :------------------------------------------------------------------------------------------ |
|
||||
| 1 | Primary | |
|
||||
| 2 | Secondary | |
|
||||
| 5 | Rescinded | seemingly not in use anymore |
|
||||
| 6 | Option | Ba+Ma students can add a master programme to their study plan before graduating as bachelor |
|
||||
| 30 | Graduated | |
|
||||
|
||||
## Type
|
||||
|
||||
> [!WARNING]
|
||||
> Obsolete, to be deprecated
|
||||
|
||||
Appears in model ElementDetails (db: `element_details`) as _type_ (db: `type`).
|
||||
|
||||
| value | meaning |
|
||||
| ----: | :----------------------------------------------- |
|
||||
| 20 | study programme |
|
||||
| 30 | module, studytrack or kokonaisuus e.g. neurology |
|
||||
|
||||
## Phase
|
||||
|
||||
Appears in model SISStudyRightElement (db: `sis_study_right_elements`) as _phase_ (db: `phase`).
|
||||
@@ -72,7 +45,15 @@ The level of the programme (bachelor's, master's etc.) of the study right can be
|
||||
|
||||
## Enrollment type
|
||||
|
||||
Appears in model SemesterEnrollment (db: `semester_enrollments`) as _enrollmenttype_ (db: `enrollmenttype`).
|
||||
This information is used in the model SISStudyRight (db: `sis_study_rights`), in the field _semesterEnrollments_ (db: `semester_enrollments`).
|
||||
|
||||
The `semester_enrollments` column is an array of JSON objects. Each object contains:
|
||||
|
||||
- `type` (the type of enrollment),
|
||||
- `semester` (the code for a semester),
|
||||
- and sometimes `statutoryAbsence` (this field is included only when the enrollment type is 2).
|
||||
|
||||
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 |
|
||||
| ----: | :------- | :------------------------------- |
|
||||
@@ -84,8 +65,6 @@ Appears in model SemesterEnrollment (db: `semester_enrollments`) as _enrollmentt
|
||||
|
||||
Appears in model Credit (db: `credit`) as _credittypecode_ (db: `credittypecode`). Values are defined in the database table `credit_types`.
|
||||
|
||||
Completed/Improved/Transferred/Failed
|
||||
|
||||
| value | meaning |
|
||||
| ----: | :--------------- |
|
||||
| 4 | Completed |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Resources
|
||||
|
||||
A collection of useful resources and documentation.
|
||||
A collection of useful tools, documentation and other resources.
|
||||
|
||||
## AI tools
|
||||
|
||||
@@ -19,6 +19,9 @@ A collection of useful resources and documentation.
|
||||
|
||||
## Database
|
||||
|
||||
- [pgcli](https://www.pgcli.com)
|
||||
- A useful commandline tool to interact with the database
|
||||
- Install: `$ pip install pgcli`
|
||||
- [Postgres documentation](https://www.postgresql.org/docs/15/index.html)
|
||||
- [Sequelize documentation](https://sequelize.org/docs/v6/)
|
||||
|
||||
@@ -39,12 +42,18 @@ A collection of useful resources and documentation.
|
||||
|
||||
## User interface
|
||||
|
||||
- [Material UI documentation](https://mui.com/material-ui/getting-started/)
|
||||
- [Material Icons](https://mui.com/material-ui/material-icons/)
|
||||
- [Semantic UI React documentation](https://react.semantic-ui.com)
|
||||
|
||||
<!--
|
||||
|
||||
## Visual Studio Code
|
||||
|
||||
_Todo: add relevant plugins_
|
||||
|
||||
-->
|
||||
|
||||
## Web technologies
|
||||
|
||||
- [MDN Web Docs](https://developer.mozilla.org/en-US/)
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import pluginJs from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import pluginCypress from 'eslint-plugin-cypress/flat'
|
||||
import pluginImport from 'eslint-plugin-import'
|
||||
import pluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||
import pluginReact from 'eslint-plugin-react'
|
||||
import pluginReactHooks from 'eslint-plugin-react-hooks'
|
||||
import tsEslint from 'typescript-eslint'
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
{
|
||||
files: ['**/*.{js,ts,jsx,tsx}'],
|
||||
},
|
||||
{
|
||||
ignores: ['eslint.config.mjs', '**/build/', '**/dist/', '**/node_modules/'],
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
linterOptions: {
|
||||
reportUnusedDisableDirectives: 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: [
|
||||
'services/frontend/tsconfig.json',
|
||||
'services/frontend/tsconfig.node.json',
|
||||
'services/backend/tsconfig.json',
|
||||
'services/shared/tsconfig.json',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
pluginJs.configs.recommended,
|
||||
...tsEslint.configs.recommendedTypeChecked,
|
||||
...tsEslint.configs.stylisticTypeChecked,
|
||||
pluginCypress.configs.recommended,
|
||||
pluginReact.configs.flat.recommended,
|
||||
pluginReact.configs.flat['jsx-runtime'],
|
||||
pluginImport.flatConfigs.recommended,
|
||||
pluginImport.flatConfigs.typescript,
|
||||
pluginPrettierRecommended, // This must be the last plugin so it can override other configs
|
||||
{
|
||||
plugins: {
|
||||
'react-hooks': pluginReactHooks,
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
camelcase: 'off', // TODO: Enable this rule (at least for frontend)
|
||||
'class-methods-use-this': 'error',
|
||||
'consistent-return': 'error',
|
||||
'id-denylist': ['error', 'c', 'd', 'e', 'err', 't'],
|
||||
'import/no-commonjs': 'error',
|
||||
'import/no-default-export': 'error',
|
||||
'import/no-extraneous-dependencies': ['error', { devDependencies: false }],
|
||||
'import/no-unused-modules': ['error', { missingExports: true, unusedExports: true }],
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
alphabetize: { order: 'asc', caseInsensitive: true },
|
||||
groups: [['builtin', 'external'], ['internal'], ['parent'], ['sibling', 'index']],
|
||||
},
|
||||
],
|
||||
'no-alert': 'error',
|
||||
'no-async-promise-executor': 'error',
|
||||
'no-await-in-loop': 'error',
|
||||
'no-console': 'error',
|
||||
'no-implied-eval': 'error',
|
||||
'no-param-reassign': ['error', { props: false }],
|
||||
'no-promise-executor-return': 'error',
|
||||
'no-return-assign': 'error',
|
||||
'no-return-await': 'error',
|
||||
'object-shorthand': ['error', 'always'],
|
||||
'prefer-const': 'error',
|
||||
'prefer-destructuring': ['error', { VariableDeclarator: { object: true } }],
|
||||
quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: false }],
|
||||
'react/display-name': 'off', // TODO: Delete this override
|
||||
'react/function-component-definition': [
|
||||
'error',
|
||||
{ namedComponents: 'arrow-function', unnamedComponents: 'arrow-function' },
|
||||
],
|
||||
'react/jsx-filename-extension': ['error', { allow: 'as-needed', extensions: ['.jsx', '.tsx'] }],
|
||||
'react/jsx-sort-props': ['error', { reservedFirst: false }],
|
||||
'react/no-array-index-key': 'error',
|
||||
'react/no-this-in-sfc': 'error',
|
||||
'react/no-unescaped-entities': 'off',
|
||||
'react/no-unknown-property': 'off', // TODO: Delete this override
|
||||
'react/prefer-stateless-function': 'error',
|
||||
'react/prop-types': 'off',
|
||||
...pluginReactHooks.configs.recommended.rules,
|
||||
'react-hooks/exhaustive-deps': 'off', // TODO: Delete this override
|
||||
'@typescript-eslint/array-type': 'off',
|
||||
'@typescript-eslint/consistent-indexed-object-style': 'off',
|
||||
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'with-single-extends' }],
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-misused-promises': 'off', // Most of these errors come from Express route handlers that are handled correctly by express-async-errors
|
||||
'@typescript-eslint/no-unsafe-argument': 'off',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||
'@typescript-eslint/no-unsafe-call': 'off',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||
'@typescript-eslint/no-unsafe-return': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{ argsIgnorePattern: '^_', caughtErrors: 'none', ignoreRestSiblings: true, varsIgnorePattern: '^_' },
|
||||
],
|
||||
'@typescript-eslint/restrict-template-expressions': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
rules: {
|
||||
...tsEslint.configs.disableTypeChecked.rules,
|
||||
'dot-notation': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['services/backend/src/rapodiff/**/*'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['cypress/**/*.js', 'services/backend/**/*.js', 'updater/**/*.js'],
|
||||
languageOptions: {
|
||||
sourceType: 'commonjs',
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'import/no-commonjs': 'off',
|
||||
'import/no-unused-modules': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['services/backend/**/*.{js,ts}', 'updater/**/*.js'],
|
||||
rules: {
|
||||
// TODO: Most of these overrides should probably be removed
|
||||
'consistent-return': 'off',
|
||||
'import/no-default-export': 'off',
|
||||
'import/no-unused-modules': 'off',
|
||||
'no-await-in-loop': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'no-promise-executor-return': 'off',
|
||||
'no-return-await': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
settings: {
|
||||
'import/internal-regex': '^@/shared/',
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project: [
|
||||
'services/frontend/tsconfig.json',
|
||||
'services/frontend/tsconfig.node.json',
|
||||
'services/backend/tsconfig.json',
|
||||
'services/shared/tsconfig.json',
|
||||
],
|
||||
},
|
||||
},
|
||||
react: {
|
||||
version: '18.3',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
Generated
+1067
-1060
File diff suppressed because it is too large
Load Diff
+18
-20
@@ -7,7 +7,7 @@
|
||||
"cypress:run": "cypress run --browser chrome",
|
||||
"instrument": "./scripts/instrument.sh",
|
||||
"eslint": "eslint",
|
||||
"lint:all": "eslint --ext .js,.jsx,.ts,.tsx services/ updater/ --report-unused-disable-directives",
|
||||
"lint:all": "eslint services/ updater/",
|
||||
"stylelint": "stylelint",
|
||||
"actionlint": "node-actionlint",
|
||||
"prettier": "prettier",
|
||||
@@ -23,7 +23,7 @@
|
||||
"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 oodikone_backend_1 node /opt/app-root/src/src/rapodiff/rapodiff.js",
|
||||
"rapodiff": "docker exec -t oodikone_backend_1 /opt/app-root/src/node_modules/.bin/tsx /opt/app-root/src/src/rapodiff/rapodiff.js",
|
||||
"install:local": "npm i && cd services/frontend && npm i && cd ../backend && npm i && cd ../../updater/sis-updater-scheduler && npm i && cd ../sis-updater-worker && npm i && cd ../..",
|
||||
"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",
|
||||
"install:both": "npm run install:local && npm run install:docker"
|
||||
@@ -38,29 +38,27 @@
|
||||
},
|
||||
"homepage": "https://github.com/UniversityOfHelsinkiCS/oodikone#readme",
|
||||
"devDependencies": {
|
||||
"@cypress/code-coverage": "^3.12.39",
|
||||
"@cypress/code-coverage": "^3.13.6",
|
||||
"@eslint/js": "^9.14.0",
|
||||
"@istanbuljs/schema": "^0.1.3",
|
||||
"@typescript-eslint/eslint-plugin": "^7.16.1",
|
||||
"@typescript-eslint/parser": "^7.16.1",
|
||||
"cypress": "^13.2.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"cypress": "^13.15.2",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-import-resolver-custom-alias": "^1.3.2",
|
||||
"eslint-plugin-cypress": "^3.0.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-cypress": "^4.1.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"husky": "^9.0.0",
|
||||
"lint-staged": "^15.0.0",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"globals": "^15.12.0",
|
||||
"husky": "^9.1.6",
|
||||
"lint-staged": "^15.2.10",
|
||||
"node-actionlint": "^1.2.2",
|
||||
"nyc": "^17.0.0",
|
||||
"nyc": "^17.1.0",
|
||||
"prettier": "3.3.3",
|
||||
"stylelint": "^16.2.1",
|
||||
"stylelint-config-standard": "^36.0.0"
|
||||
"stylelint": "^16.10.0",
|
||||
"stylelint-config-standard": "^36.0.1",
|
||||
"typescript-eslint": "^8.13.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
+15
-3
@@ -5,18 +5,30 @@
|
||||
{
|
||||
"groupName": "all non-major dependencies",
|
||||
"groupSlug": "all-minor-patch",
|
||||
"matchPackagePatterns": ["*"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"matchCurrentVersion": "!/^0/",
|
||||
"automerge": true,
|
||||
"matchPackageNames": ["*"]
|
||||
},
|
||||
{
|
||||
"matchDatasources": ["docker"],
|
||||
"matchPackageNames": ["node"],
|
||||
"versionCompatibility": "^(?<version>[^-]+)(?<compatibility>-.*)?$",
|
||||
"versioning": "node"
|
||||
},
|
||||
{
|
||||
"matchDatasources": ["docker"],
|
||||
"matchPackageNames": ["node"],
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"rebaseWhen": "never",
|
||||
"vulnerabilityAlerts": {
|
||||
"labels": ["security"]
|
||||
}
|
||||
},
|
||||
"addLabels": ["dependencies"],
|
||||
"rangeStrategy": "bump",
|
||||
"schedule": ["before 5:00am"],
|
||||
"timezone": "Europe/Helsinki"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This file includes functions that are used to setup oodikone. File doesn't run anything
|
||||
# by itself and should be sourced from other script.
|
||||
# This file includes functions that are used to setup Oodikone
|
||||
# File doesn't run anything by itself and should be sourced from other scripts
|
||||
|
||||
# === Config ===
|
||||
|
||||
@@ -202,16 +202,13 @@ set_up_oodikone_from_scratch() {
|
||||
)
|
||||
|
||||
infomsg "Cleaning up docker setup and node_modules from possible previous installations"
|
||||
local compose_down_args="--remove-orphans --volumes --rmi all"
|
||||
"$PROJECT_ROOT"/run.sh both down "$compose_down_args" || infomsg "Cleaning errored, but will continue"
|
||||
docker-compose --file "$PROJECT_ROOT"/docker-compose.ci.yml "$compose_down_args" || infomsg "Cleaning errored, but will continue"
|
||||
"$PROJECT_ROOT"/run.sh both down --remove-orphans --volumes --rmi all || infomsg "Cleaning errored, but will continue"
|
||||
for folder in "${folders_to_set_up[@]}"; do
|
||||
cd "$folder" || die "Couldn't change directory to folder $folder"
|
||||
rm -rf node_modules|| die "Couldn't remove node_modules in folder $folder"
|
||||
done
|
||||
|
||||
infomsg "Installing npm packages locally to root and each subfolder to enable \
|
||||
linting and formatting"
|
||||
infomsg "Installing npm packages locally to root and each subfolder to enable linting and formatting"
|
||||
|
||||
for folder in "${folders_to_set_up[@]}"; do
|
||||
cd "$folder" || die "Couldn't change directory to folder $folder"
|
||||
@@ -228,7 +225,22 @@ linting and formatting"
|
||||
infomsg "Building images"
|
||||
"$PROJECT_ROOT"/run.sh both anon build
|
||||
|
||||
successmsg "Setup ready, oodikone can be started! See README for more info."
|
||||
TARGET_PATH="$PROJECT_ROOT/services/shared"
|
||||
LINK_PATHS=(
|
||||
"$PROJECT_ROOT/services/backend/src/shared"
|
||||
"$PROJECT_ROOT/services/frontend/src/shared"
|
||||
)
|
||||
|
||||
for SYMLINK_PATH in "${LINK_PATHS[@]}"; do
|
||||
infomsg "Linking $TARGET_PATH to $SYMLINK_PATH"
|
||||
if [ ! -L "$SYMLINK_PATH" ]; then
|
||||
ln -s "$TARGET_PATH" "$SYMLINK_PATH"
|
||||
else
|
||||
infomsg "Symbolic link $SYMLINK_PATH already exists. Skipping creation."
|
||||
fi
|
||||
done
|
||||
|
||||
successmsg "Setup ready, Oodikone can be started! See README for more info."
|
||||
}
|
||||
|
||||
docker_prune() {
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This file includes some utility functions that are used in oodikone scripts.
|
||||
# This file includes some utility functions that are used in Oodikone scripts
|
||||
# Functions are based on https://betterdev.blog/minimal-safe-bash-script-template/
|
||||
|
||||
# Print messages and logs that are not script output to stderr
|
||||
@@ -27,7 +27,7 @@ warningmsg() {
|
||||
}
|
||||
|
||||
# Quit and exit with given message and error code
|
||||
# By default uses exit code 1, message must be given.
|
||||
# By default uses exit code 1, message must be given
|
||||
die() {
|
||||
local msg=$1
|
||||
local code=${2-1}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM registry.access.redhat.com/ubi9/nodejs-20-minimal as build
|
||||
FROM registry.access.redhat.com/ubi9/nodejs-22-minimal AS build
|
||||
|
||||
ENV TZ="Europe/Helsinki"
|
||||
|
||||
@@ -19,17 +19,16 @@ ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
|
||||
ARG SENTRY_RELEASE
|
||||
ENV SENTRY_RELEASE=$SENTRY_RELEASE
|
||||
|
||||
COPY ./package*.json ./
|
||||
|
||||
COPY ./backend/package*.json ./
|
||||
RUN npm ci --include=dev
|
||||
|
||||
COPY . .
|
||||
COPY ./shared ./src/shared
|
||||
COPY ./backend .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
|
||||
|
||||
FROM registry.access.redhat.com/ubi9/nodejs-20-minimal
|
||||
FROM registry.access.redhat.com/ubi9/nodejs-22-minimal
|
||||
|
||||
ENV TZ="Europe/Helsinki"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:20-alpine
|
||||
FROM node:22-alpine
|
||||
|
||||
ENV TZ="Europe/Helsinki"
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
require('./src/util/sentry') // Sentry must be the first import
|
||||
require('./src/app')
|
||||
const logger = require('./src/util/logger')
|
||||
|
||||
|
||||
Generated
+2306
-2646
File diff suppressed because it is too large
Load Diff
@@ -2,49 +2,49 @@
|
||||
"name": "backend",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"rapodiff": "node /opt/app-root/src/src/rapodiff.js"
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/node": "^5.30.0",
|
||||
"@sentry/tracing": "^6.18.2",
|
||||
"@sentry/node": "^8.37.1",
|
||||
"@sentry/profiling-node": "^8.37.1",
|
||||
"axios": "^0.28.1",
|
||||
"bluebird": "^3.7.2",
|
||||
"bullmq": "^4.14.2",
|
||||
"compression": "^1.7.4",
|
||||
"bullmq": "^5.25.4",
|
||||
"compression": "^1.7.5",
|
||||
"cors": "^2.8.5",
|
||||
"cron": "^1.8.2",
|
||||
"express": "^4.19.2",
|
||||
"cron": "^3.1.9",
|
||||
"express": "^4.21.1",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lru-cache": "^10.2.2",
|
||||
"lru-cache": "^11.0.2",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.37",
|
||||
"moment-timezone": "^0.5.46",
|
||||
"morgan": "^1.10.0",
|
||||
"oodikone-shared": "file:../oodikone-shared",
|
||||
"papaparse": "^5.4.1",
|
||||
"pg": "^8.11.5",
|
||||
"redis": "^3.1.2",
|
||||
"pg": "^8.13.1",
|
||||
"redis": "^4.7.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"sequelize": "^6.37.3",
|
||||
"sequelize": "^6.37.5",
|
||||
"sequelize-typescript": "^2.1.6",
|
||||
"umzug": "^2.3.0",
|
||||
"umzug": "^3.8.2",
|
||||
"unfuck-utf8-headers-middleware": "^1.0.1",
|
||||
"winston": "^3.8.2",
|
||||
"winston": "^3.17.0",
|
||||
"winston-gelf-transporter": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/compression": "^1.7.5",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/lodash": "^4.17.6",
|
||||
"@types/lodash": "^4.17.13",
|
||||
"@types/morgan": "^1.9.9",
|
||||
"@types/node": "^20.14.2",
|
||||
"@types/umzug": "^2.3.7",
|
||||
"@types/validator": "^13.12.0",
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "^5.5.3"
|
||||
"@types/node": "^22.9.0",
|
||||
"@types/validator": "^13.12.2",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"overrides": {
|
||||
"winston-gelf-transporter": {
|
||||
"gelf-pro": "^1.3.4"
|
||||
"gelf-pro": "^1.3.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ const express = require('express')
|
||||
const { baseUrl, backendPort } = require('./config')
|
||||
const { initializeDatabaseConnection, dbConnections } = require('./database/connection')
|
||||
const { startCron } = require('./events')
|
||||
const routes = require('./routes')
|
||||
const routes = require('./routes').default
|
||||
const logger = require('./util/logger')
|
||||
require('./worker/worker')
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@ const isDev = process.env.NODE_ENV === 'development'
|
||||
const isStaging = process.env.REACT_APP_STAGING === 'true'
|
||||
const isProduction = !isStaging && process.env.NODE_ENV === 'production'
|
||||
|
||||
// Sentry
|
||||
const sentryRelease = process.env.SENTRY_RELEASE || ''
|
||||
const sentryEnvironment = process.env.SENTRY_ENVIRONMENT || ''
|
||||
const sentryDSN = process.env.SENTRY_DSN || ''
|
||||
const runningInCI = process.env.CI === 'true'
|
||||
|
||||
// IAM group
|
||||
@@ -23,6 +19,8 @@ const jamiUrl = process.env.JAMI_URL || ''
|
||||
// Importer client
|
||||
const importerUrl = process.env.IMPORTER_DB_API_URL || ''
|
||||
const importerToken = process.env.IMPORTER_DB_API_TOKEN || ''
|
||||
const importerDbApiUser = process.env.IMPORTER_DB_API_USER || ''
|
||||
const importerDbApiPassword = process.env.IMPORTER_DB_API_PASSWORD || ''
|
||||
|
||||
// Sisu
|
||||
const sisUrl = process.env.SIS_HOST
|
||||
@@ -35,14 +33,21 @@ const frontUrl = process.env.FRONT_URL
|
||||
const backendPort = 8080
|
||||
|
||||
// System run in whose environment
|
||||
const serviceProvider = process.env.SERVICE_PROVIDER || 'Toska'
|
||||
const serviceProvider = process.env.SERVICE_PROVIDER ? process.env.SERVICE_PROVIDER.toLowerCase() : 'toska'
|
||||
|
||||
// Optional logout-url configuration
|
||||
const configLogoutUrl = serviceProvider === 'Toska' ? undefined : process.env.LOGOUT_URL
|
||||
const configLogoutUrl = serviceProvider === 'toska' ? undefined : process.env.LOGOUT_URL
|
||||
|
||||
// Other stuff
|
||||
const { DB_URL_KONE, DB_URL_USER, SECRET_TOKEN, SIS_DB_URL, SIS_UPDATER_URL, CRYPT_KEY } = process.env
|
||||
|
||||
const rootOrgId = process.env.ROOT_ORG_ID || 'hy-university-root-id'
|
||||
|
||||
const languageCenterViewEnabled =
|
||||
process.env.LANGUAGE_CENTER_VIEW_ENABLED == null ? true : process.env.LANGUAGE_CENTER_VIEW_ENABLED === 'true'
|
||||
|
||||
const concurrentWorkers = process.env.CONCURRENT_WORKERS ? parseInt(process.env.CONCURRENT_WORKERS, 10) : 2
|
||||
|
||||
let DB_MAX_CONNECTIONS = parseInt(process.env.DB_MAX_CONNECTIONS, 10)
|
||||
if (Number.isNaN(DB_MAX_CONNECTIONS)) {
|
||||
DB_MAX_CONNECTIONS = 5 // sequelize's default
|
||||
@@ -75,9 +80,6 @@ module.exports = {
|
||||
SIS_DB_URL,
|
||||
SIS_UPDATER_URL,
|
||||
runningInCI,
|
||||
sentryRelease,
|
||||
sentryEnvironment,
|
||||
sentryDSN,
|
||||
isProduction,
|
||||
pateToken,
|
||||
jamiUrl,
|
||||
@@ -86,11 +88,16 @@ module.exports = {
|
||||
backendPort,
|
||||
importerUrl,
|
||||
importerToken,
|
||||
importerDbApiUser,
|
||||
importerDbApiPassword,
|
||||
isDev,
|
||||
isStaging,
|
||||
rootOrgId,
|
||||
serviceProvider,
|
||||
configLogoutUrl,
|
||||
sisUrl,
|
||||
sisGrapqlAppAccount,
|
||||
sisGrapqlAppKey,
|
||||
languageCenterViewEnabled,
|
||||
concurrentWorkers,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Linter was not having any of this so neeeded to add resolveJsonModule:true to tsconfig
|
||||
// Linter was not having any of this so needed to add resolveJsonModule:true to tsconfig
|
||||
// export const { facultyCodes, ignoredFacultyCodes, magicFacultyCode } = require('../environment/organizationConfig.json')
|
||||
|
||||
export { facultyCodes, ignoredFacultyCodes, magicFacultyCode } from '../environment/organizationConfig.json'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import EventEmitter from 'events'
|
||||
import { Sequelize } from 'sequelize-typescript'
|
||||
import Umzug from 'umzug'
|
||||
import { SequelizeStorage, Umzug } from 'umzug'
|
||||
|
||||
import conf from '../config'
|
||||
import {
|
||||
@@ -10,22 +10,18 @@ import {
|
||||
Credit,
|
||||
CreditTeacher,
|
||||
CreditType,
|
||||
ElementDetail,
|
||||
CurriculumPeriod,
|
||||
Enrollment,
|
||||
Organization,
|
||||
ProgrammeModule,
|
||||
ProgrammeModuleChild,
|
||||
Semester,
|
||||
SemesterEnrollment,
|
||||
SISStudyRight,
|
||||
SISStudyRightElement,
|
||||
Student,
|
||||
Studyplan,
|
||||
Studyright,
|
||||
StudyrightElement,
|
||||
StudyrightExtent,
|
||||
Teacher,
|
||||
Transfer,
|
||||
} from '../models'
|
||||
import {
|
||||
CustomPopulationSearch,
|
||||
@@ -70,22 +66,18 @@ class DbConnection extends EventEmitter {
|
||||
Credit,
|
||||
CreditTeacher,
|
||||
CreditType,
|
||||
ElementDetail,
|
||||
CurriculumPeriod,
|
||||
Enrollment,
|
||||
Organization,
|
||||
ProgrammeModule,
|
||||
ProgrammeModuleChild,
|
||||
Semester,
|
||||
SemesterEnrollment,
|
||||
SISStudyRight,
|
||||
SISStudyRightElement,
|
||||
Student,
|
||||
Studyplan,
|
||||
Studyright,
|
||||
StudyrightElement,
|
||||
StudyrightExtent,
|
||||
Teacher,
|
||||
Transfer,
|
||||
],
|
||||
})
|
||||
}
|
||||
@@ -126,7 +118,7 @@ const sequelizeKone = new Sequelize(conf.DB_URL_KONE!, {
|
||||
],
|
||||
})
|
||||
|
||||
sequelizeKone.query(`SET SESSION search_path to ${conf.DB_SCHEMA_KONE}`)
|
||||
void sequelizeKone.query(`SET SESSION search_path to ${conf.DB_SCHEMA_KONE}`)
|
||||
|
||||
const sequelizeUser = new Sequelize(conf.DB_URL_USER!, {
|
||||
logging: false,
|
||||
@@ -160,21 +152,16 @@ const initializeDatabaseConnection = async () => {
|
||||
const schema = dbName === 'kone-db' ? conf.DB_SCHEMA_KONE : undefined
|
||||
const migrationsFolder = dbName === 'kone-db' ? 'migrations_kone' : 'migrations_user'
|
||||
const migrator = new Umzug({
|
||||
storage: 'sequelize',
|
||||
storageOptions: {
|
||||
sequelize: seq,
|
||||
tableName: 'migrations',
|
||||
schema,
|
||||
},
|
||||
storage: new SequelizeStorage({ sequelize: seq, tableName: 'migrations', schema }),
|
||||
migrations: {
|
||||
params: [seq.getQueryInterface(), Sequelize],
|
||||
path: `${process.cwd()}${conf.isProduction || conf.isStaging ? '/dist' : ''}/src/database/${migrationsFolder}`,
|
||||
pattern: /\.js$/,
|
||||
glob: `${process.cwd()}${conf.isProduction || conf.isStaging ? '/dist' : ''}/src/database/${migrationsFolder}/*.js`,
|
||||
},
|
||||
context: seq.getQueryInterface(),
|
||||
logger: console,
|
||||
})
|
||||
try {
|
||||
const migrations = await migrator.up()
|
||||
logger.info({ message: `${dbName} migrations up to date: `, meta: migrations.map(migration => migration.file) })
|
||||
await migrator.up()
|
||||
logger.info({ message: `${dbName} migrations up to date` })
|
||||
} catch (error) {
|
||||
logger.error({ message: `${dbName} migrations failed`, meta: error })
|
||||
throw error
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.sequelize.query(`
|
||||
-- Adminer 4.6.2 PostgreSQL dump
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.sequelize.query(`
|
||||
-- Adminer 4.6.2 PostgreSQL dump
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
return queryInterface.sequelize.transaction(async transaction => {
|
||||
await queryInterface.sequelize.query(
|
||||
"SELECT setval('mandatory_courses_id_seq', (SELECT MAX(id) FROM mandatory_courses))",
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
return queryInterface.sequelize.transaction(async transaction => {
|
||||
await queryInterface.removeConstraint('tag_student', 'tag_student_tag_id_fkey', { transaction })
|
||||
await queryInterface.sequelize.query('DELETE FROM tag_student WHERE tag_id IS NULL', { transaction })
|
||||
|
||||
+4
-2
@@ -1,7 +1,9 @@
|
||||
const { STRING } = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.bulkDelete('tag', [])
|
||||
await queryInterface.addColumn('tag', 'year', { type: Sequelize.STRING })
|
||||
await queryInterface.addColumn('tag', 'year', { type: STRING })
|
||||
},
|
||||
down: async () => {},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
const { BIGINT } = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.addColumn('tag', 'personal_user_id', {
|
||||
type: Sequelize.BIGINT,
|
||||
type: BIGINT,
|
||||
})
|
||||
},
|
||||
down: async () => {},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
const { BIGINT, STRING, DATE, ARRAY } = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.createTable('custom_population_searches', {
|
||||
id: {
|
||||
type: BIGINT,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
const { STRING, DATE, JSONB } = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.createTable('programme_modules', {
|
||||
id: {
|
||||
primaryKey: true,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ const { INTEGER } = require('sequelize')
|
||||
// this has to be done
|
||||
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.addColumn('programme_modules', 'order', {
|
||||
type: INTEGER,
|
||||
})
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
const { INTEGER, STRING, DATE } = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.createTable(
|
||||
'excluded_courses',
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.dropTable('teacher_course_groups')
|
||||
await queryInterface.dropTable('course_groups')
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.sequelize.query('DROP SEQUENCE "course_groups_id_seq"')
|
||||
await queryInterface.sequelize.query('DROP SEQUENCE "teacher_course_group_id_seq"')
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.dropTable('filters')
|
||||
},
|
||||
down: async () => {},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.dropTable('usage_statistics')
|
||||
},
|
||||
down: async () => {},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.dropTable('unit_tag')
|
||||
},
|
||||
down: async () => {},
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.dropTable('course_duplicates')
|
||||
},
|
||||
down: async () => {},
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.dropTable('programme_module_children')
|
||||
await queryInterface.dropTable('programme_modules')
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
const { BIGINT, STRING, DATE } = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.createTable(
|
||||
'study_guidance_group_tags',
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.dropTable('thesis_courses')
|
||||
},
|
||||
down: async () => {},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
const { BIGINT, STRING, DATE, ARRAY } = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.createTable('open_uni_population_searches', {
|
||||
id: {
|
||||
type: BIGINT,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
const { INTEGER, STRING, ARRAY, DATE } = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.createTable('progress_criteria', {
|
||||
code: {
|
||||
type: STRING,
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
const { ARRAY, STRING, INTEGER } = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.addColumn('progress_criteria', 'courses_year_four', {
|
||||
type: Sequelize.ARRAY(Sequelize.STRING),
|
||||
type: ARRAY(STRING),
|
||||
})
|
||||
await queryInterface.addColumn('progress_criteria', 'courses_year_five', {
|
||||
type: Sequelize.ARRAY(Sequelize.STRING),
|
||||
type: ARRAY(STRING),
|
||||
})
|
||||
await queryInterface.addColumn('progress_criteria', 'courses_year_six', {
|
||||
type: Sequelize.ARRAY(Sequelize.STRING),
|
||||
type: ARRAY(STRING),
|
||||
})
|
||||
await queryInterface.addColumn('progress_criteria', 'credits_year_four', {
|
||||
type: Sequelize.INTEGER,
|
||||
type: INTEGER,
|
||||
})
|
||||
await queryInterface.addColumn('progress_criteria', 'credits_year_five', {
|
||||
type: Sequelize.INTEGER,
|
||||
type: INTEGER,
|
||||
})
|
||||
await queryInterface.addColumn('progress_criteria', 'credits_year_six', {
|
||||
type: Sequelize.INTEGER,
|
||||
type: INTEGER,
|
||||
})
|
||||
},
|
||||
down: async () => {},
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.dropTable('mandatory_courses')
|
||||
await queryInterface.dropTable('mandatory_course_labels')
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.sequelize.query('DROP SEQUENCE "mandatory_courses_id_seq"')
|
||||
await queryInterface.sequelize.query('DROP SEQUENCE "mandatory_course_labels_id_seq"')
|
||||
},
|
||||
|
||||
+6
-4
@@ -1,16 +1,18 @@
|
||||
const { STRING } = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.sequelize.query('TRUNCATE TABLE progress_criteria')
|
||||
await queryInterface.sequelize.query('TRUNCATE TABLE excluded_courses')
|
||||
await queryInterface.removeConstraint('excluded_courses', 'excluded_courses_programme_code_course_code_key')
|
||||
await queryInterface.addColumn('progress_criteria', 'curriculum_version', {
|
||||
type: Sequelize.STRING,
|
||||
type: STRING,
|
||||
})
|
||||
await queryInterface.addColumn('excluded_courses', 'curriculum_version', {
|
||||
type: Sequelize.STRING,
|
||||
type: STRING,
|
||||
})
|
||||
},
|
||||
down: async queryInterface => {
|
||||
down: async ({ context: queryInterface }) => {
|
||||
await queryInterface.removeColumn('progress_criteria', 'curriculum_version')
|
||||
await queryInterface.removeColumn('excluded_courses', 'curriculum_version')
|
||||
},
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
const Sequelize = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.createTable('study_programme_pins', {
|
||||
user_id: {
|
||||
primaryKey: true,
|
||||
@@ -12,7 +12,7 @@ module.exports = {
|
||||
},
|
||||
})
|
||||
},
|
||||
down: async queryInterface => {
|
||||
down: async ({ context: queryInterface }) => {
|
||||
await queryInterface.dropTable('study_programme_pins')
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface =>
|
||||
up: async ({ context: queryInterface }) =>
|
||||
queryInterface.sequelize.query(
|
||||
`
|
||||
--
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface =>
|
||||
up: async ({ context: queryInterface }) =>
|
||||
queryInterface.sequelize.query(
|
||||
`
|
||||
--
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
return queryInterface.sequelize.transaction(async transaction => {
|
||||
await queryInterface.sequelize.query(
|
||||
'ALTER TABLE user_accessgroup DROP CONSTRAINT IF EXISTS "user_accessgroup_accessGroupId_fkey"',
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
return queryInterface.bulkDelete('access_groups', {
|
||||
group_code: 'oodilearn',
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: queryInterface => {
|
||||
up: ({ context: queryInterface }) => {
|
||||
return queryInterface.bulkInsert('access_groups', [
|
||||
{
|
||||
group_code: 'cooldata',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: queryInterface => {
|
||||
up: ({ context: queryInterface }) => {
|
||||
return queryInterface.bulkInsert('access_groups', [
|
||||
{
|
||||
group_code: 'courseStatistics',
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: queryInterface => {
|
||||
up: ({ context: queryInterface }) => {
|
||||
return queryInterface.bulkDelete('access_groups', [
|
||||
{
|
||||
group_code: 'faculties',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.dropTable('user_unit')
|
||||
},
|
||||
down: async () => {},
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface =>
|
||||
up: async ({ context: queryInterface }) =>
|
||||
queryInterface.sequelize.query(`
|
||||
--- Some lääkis special programmes
|
||||
INSERT INTO faculty_programmes VALUES ('H30', '447000', 'NOW()', 'NOW()');
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
up: async queryInterface =>
|
||||
up: async ({ context: queryInterface }) =>
|
||||
queryInterface.sequelize.query(`
|
||||
--- Some kasvis special programmes
|
||||
INSERT INTO faculty_programmes VALUES ('H60', 'ED400', 'NOW()', 'NOW()');
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const { STRING } = require('sequelize')
|
||||
|
||||
module.exports = {
|
||||
up: async queryInterface => {
|
||||
up: async ({ context: queryInterface }) => {
|
||||
await queryInterface.addColumn('users', 'sisu_person_id', STRING)
|
||||
},
|
||||
down: async queryInterface => {
|
||||
down: async ({ context: queryInterface }) => {
|
||||
await queryInterface.removeColumn('users', 'sisu_person_id')
|
||||
},
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user