mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-13 03:13:40 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed265c70f9 | ||
|
|
862321da55 | ||
|
|
5164b155aa | ||
|
|
53b6b480b6 | ||
|
|
cf9420b758 | ||
|
|
9cce15cf31 | ||
|
|
cd3b6ace84 | ||
|
|
4af5783dac | ||
|
|
5ec875cfc8 | ||
|
|
ecefaf11a1 | ||
|
|
d32acd720e | ||
|
|
609fa4520d | ||
|
|
9017925129 | ||
|
|
bd0258b194 | ||
|
|
40b7440123 | ||
|
|
9f65f9b55e | ||
|
|
a99201dd21 | ||
|
|
25c383957c |
@@ -103,6 +103,7 @@ describe('Population Statistics tests', () => {
|
||||
cy.contains("Credits gained during first").parentsUntil(".tab").get("table").within(() => {
|
||||
cy.get("tr").eq(1).find("td").eq(1).invoke("text").then(text => filteredStudents = Number(text))
|
||||
cy.get("tr").eq(1).click()
|
||||
cy.wait(1000)
|
||||
}).then(() => {
|
||||
checkAmountOfStudents(filteredStudents)
|
||||
})
|
||||
|
||||
Generated
+112
-96
@@ -48,9 +48,9 @@
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
|
||||
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
|
||||
"version": "6.10.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
|
||||
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
@@ -77,6 +77,12 @@
|
||||
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
|
||||
"dev": true
|
||||
},
|
||||
"arch": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz",
|
||||
"integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==",
|
||||
"dev": true
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
@@ -102,12 +108,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"async": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.4.0.tgz",
|
||||
"integrity": "sha1-SZAgDxjqW4N8LMT4wDGmmFw4VhE=",
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
|
||||
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.14.0"
|
||||
"lodash": "^4.17.10"
|
||||
}
|
||||
},
|
||||
"asynckit": {
|
||||
@@ -341,9 +347,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
|
||||
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
@@ -380,9 +386,9 @@
|
||||
}
|
||||
},
|
||||
"concurrently": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.0.tgz",
|
||||
"integrity": "sha512-pwzXCE7qtOB346LyO9eFWpkFJVO3JQZ/qU/feGeaAHiX1M3Rw3zgXKc5cZ8vSH5DGygkjzLFDzA/pwoQDkRNGg==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.1.tgz",
|
||||
"integrity": "sha512-48+FE5RJ0qc8azwKv4keVQWlni1hZeSjcWr8shBelOBtBHcKj1aJFM9lHRiSc1x7lq416pkvsqfBMhSRja+Lhw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.4.1",
|
||||
@@ -403,9 +409,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.1.tgz",
|
||||
"integrity": "sha512-y0j31WJc83wPu31vS1VlAFW5JGrnGC+j+TtGAa1fRQphy48+fDYiDmX8tjGloToEsMkxnouOg/1IzXGKkJnZMg==",
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz",
|
||||
"integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
@@ -454,41 +460,49 @@
|
||||
}
|
||||
},
|
||||
"cypress": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-3.2.0.tgz",
|
||||
"integrity": "sha512-PN0wz6x634QyNL56/voTzJoeScDfwtecvSfFTHfv5MkHuECVSR4VQcEZTvYtKWln3CMBMUkWbBKPIwwu2+a/kw==",
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-3.4.1.tgz",
|
||||
"integrity": "sha512-1HBS7t9XXzkt6QHbwfirWYty8vzxNMawGj1yI+Fu6C3/VZJ8UtUngMW6layqwYZzLTZV8tiDpdCNBypn78V4Dg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@cypress/listr-verbose-renderer": "0.4.1",
|
||||
"@cypress/xvfb": "1.2.4",
|
||||
"arch": "2.1.1",
|
||||
"bluebird": "3.5.0",
|
||||
"cachedir": "1.3.0",
|
||||
"chalk": "2.4.2",
|
||||
"check-more-types": "2.24.0",
|
||||
"commander": "2.15.1",
|
||||
"common-tags": "1.8.0",
|
||||
"debug": "3.1.0",
|
||||
"debug": "3.2.6",
|
||||
"execa": "0.10.0",
|
||||
"executable": "4.1.1",
|
||||
"extract-zip": "1.6.7",
|
||||
"fs-extra": "4.0.1",
|
||||
"getos": "3.1.0",
|
||||
"glob": "7.1.3",
|
||||
"fs-extra": "5.0.0",
|
||||
"getos": "3.1.1",
|
||||
"is-ci": "1.2.1",
|
||||
"is-installed-globally": "0.1.0",
|
||||
"lazy-ass": "1.6.0",
|
||||
"listr": "0.12.0",
|
||||
"lodash": "4.17.11",
|
||||
"lodash": "4.17.15",
|
||||
"log-symbols": "2.2.0",
|
||||
"minimist": "1.2.0",
|
||||
"moment": "2.24.0",
|
||||
"ramda": "0.24.1",
|
||||
"request": "2.88.0",
|
||||
"request-progress": "0.4.0",
|
||||
"request-progress": "3.0.0",
|
||||
"supports-color": "5.5.0",
|
||||
"tmp": "0.0.33",
|
||||
"tmp": "0.1.0",
|
||||
"url": "0.11.0",
|
||||
"yauzl": "2.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashdash": {
|
||||
@@ -507,12 +521,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"decamelize": {
|
||||
@@ -630,6 +644,12 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
},
|
||||
"yauzl": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
|
||||
@@ -705,13 +725,13 @@
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.1.tgz",
|
||||
"integrity": "sha1-f8DGyJV/mD9X8waiTlud3Y0N2IA=",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
|
||||
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^3.0.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
@@ -740,12 +760,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"getos": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/getos/-/getos-3.1.0.tgz",
|
||||
"integrity": "sha512-i9vrxtDu5DlLVFcrbqUqGWYlZN/zZ4pGMICCAcZoYsX3JA54nYp8r5EThw5K+m2q3wszkx4Th746JstspB0H4Q==",
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/getos/-/getos-3.1.1.tgz",
|
||||
"integrity": "sha512-oUP1rnEhAr97rkitiszGP9EgDVYnmchgFzfqRzSkgtfv7ai6tEi7Ko8GgjNXts7VLWEqrTWyhsOKLe5C5b/Zkg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "2.4.0"
|
||||
"async": "2.6.1"
|
||||
}
|
||||
},
|
||||
"getpass": {
|
||||
@@ -758,9 +778,9 @@
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
|
||||
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
|
||||
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
@@ -781,9 +801,9 @@
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.15",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
|
||||
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz",
|
||||
"integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==",
|
||||
"dev": true
|
||||
},
|
||||
"har-schema": {
|
||||
@@ -923,9 +943,9 @@
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
@@ -1075,9 +1095,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz",
|
||||
"integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
@@ -1266,9 +1286,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.once": {
|
||||
@@ -1317,18 +1337,18 @@
|
||||
}
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.39.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.39.0.tgz",
|
||||
"integrity": "sha512-DTsrw/iWVvwHH+9Otxccdyy0Tgiil6TWK/xhfARJZF/QFhwOgZgOIvA2/VIGpM8U7Q8z5nDmdDWC6tuVMJNibw==",
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
|
||||
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
|
||||
"dev": true
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.23",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.23.tgz",
|
||||
"integrity": "sha512-ROk/m+gMVSrRxTkMlaQOvFmFmYDc7sZgrjjM76abqmd2Cc5fCV7jAMA5XUccEtJ3cYiYdgixUVI+fApc2LkXlw==",
|
||||
"version": "2.1.24",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
|
||||
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mime-db": "~1.39.0"
|
||||
"mime-db": "1.40.0"
|
||||
}
|
||||
},
|
||||
"mimic-fn": {
|
||||
@@ -1376,9 +1396,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"nice-try": {
|
||||
@@ -1387,12 +1407,6 @@
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node-eta": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-eta/-/node-eta-0.1.1.tgz",
|
||||
"integrity": "sha1-QGYQmzk3HHYccrfr2pqeoKXeEh8=",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||
@@ -1523,12 +1537,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
|
||||
"dev": true
|
||||
},
|
||||
"p-defer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
|
||||
@@ -1654,15 +1662,15 @@
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
|
||||
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"dev": true
|
||||
},
|
||||
"psl": {
|
||||
"version": "1.1.31",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz",
|
||||
"integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz",
|
||||
"integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==",
|
||||
"dev": true
|
||||
},
|
||||
"pump": {
|
||||
@@ -1771,13 +1779,12 @@
|
||||
}
|
||||
},
|
||||
"request-progress": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/request-progress/-/request-progress-0.4.0.tgz",
|
||||
"integrity": "sha1-wZVOOQhqqFJpxWYLzuAUKmpw1+c=",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
|
||||
"integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"node-eta": "^0.1.1",
|
||||
"throttleit": "^0.0.2"
|
||||
"throttleit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
@@ -1817,6 +1824,15 @@
|
||||
"onetime": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
||||
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"run-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz",
|
||||
@@ -2013,18 +2029,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"throttleit": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz",
|
||||
"integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
|
||||
"integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=",
|
||||
"dev": true
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
|
||||
"integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
"rimraf": "^2.6.3"
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
@@ -2052,9 +2068,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
|
||||
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
|
||||
"dev": true
|
||||
},
|
||||
"tunnel-agent": {
|
||||
|
||||
+4
-4
@@ -5,8 +5,8 @@
|
||||
"main": "./scripts/run.sh",
|
||||
"scripts": {
|
||||
"cypress:open": "CYPRESS_baseUrl=http://localhost:8081 cypress open",
|
||||
"cypress:run": "CYPRESS_baseUrl=http://localhost:8081 cypress run --config video=false -P ./",
|
||||
"cypress:record": "cypress run --config videoUploadOnPasses=false -P ./",
|
||||
"cypress:run": "CYPRESS_baseUrl=http://localhost:8081 cypress run --config video=true --config videoCompression=false -P ./",
|
||||
"cypress:record": "cypress run --config videoUploadOnPasses=false --config videoCompression=false -P ./",
|
||||
"concurrently": "concurrently",
|
||||
"test": "./scripts/run_all_tests.sh",
|
||||
"test_services": "./scripts/run_service_tests.sh",
|
||||
@@ -40,8 +40,8 @@
|
||||
},
|
||||
"homepage": "https://github.com/UniversityOfHelsinkiCS/oodikone#readme",
|
||||
"devDependencies": {
|
||||
"concurrently": "^4.1.0",
|
||||
"cypress": "^3.2.0",
|
||||
"concurrently": "^4.1.1",
|
||||
"cypress": "^3.4.1",
|
||||
"husky": "^1.3.1"
|
||||
},
|
||||
"dependencies": {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const router = require('express').Router()
|
||||
const Population = require('../services/populations')
|
||||
const Filters = require('../services/filters')
|
||||
const { updateStudents } = require('../services/updaterService')
|
||||
const { updateStudents, updateOldestStudents } = require('../services/updaterService')
|
||||
const { isValidStudentId } = require('../util/index')
|
||||
|
||||
const Student = require('../services/students')
|
||||
@@ -208,6 +208,21 @@ router.post('/updatedatabase', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/updatedatabase/oldest', async (req, res) => {
|
||||
const amount = req.body
|
||||
if (!Number.isInteger(Number(amount))) {
|
||||
res.status(400).end()
|
||||
}
|
||||
try {
|
||||
const response = await updateOldestStudents(amount)
|
||||
if (response) {
|
||||
res.status(200).json('Scheduled')
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(418).json(err)
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/v3/populationstatistics/studyprogrammes', async (req, res) => {
|
||||
try {
|
||||
const { rights, roles } = req
|
||||
|
||||
@@ -14,8 +14,14 @@ const updateStudents = async (studentNumbers) => {
|
||||
return response.data
|
||||
}
|
||||
|
||||
const updateOldestStudents = async (amount) => {
|
||||
const response = await client.post('/update/oldest', amount)
|
||||
return response.data
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
ping,
|
||||
updateStudents
|
||||
updateStudents,
|
||||
updateOldestStudents
|
||||
}
|
||||
@@ -15,7 +15,7 @@ stan.on('connect', function () {
|
||||
const attSub = stan.subscribe('UpdateAttainmentDates', opts)
|
||||
const prioSub = stan.subscribe('PriorityWrite', 'updater.workers.prio', opts)
|
||||
|
||||
sub.on('message', async (msg) => {
|
||||
const writeStudent = async (msg) => {
|
||||
let data = null
|
||||
try {
|
||||
data = JSON.parse(msg.getData())
|
||||
@@ -33,7 +33,11 @@ stan.on('connect', function () {
|
||||
console.log('update failed', id, err)
|
||||
}
|
||||
msg.ack()
|
||||
})
|
||||
}
|
||||
|
||||
sub.on('message', writeStudent)
|
||||
prioSub.on('message', writeStudent)
|
||||
|
||||
attSub.on('message', async (msg) => {
|
||||
try {
|
||||
await updateAttainmentMeta()
|
||||
@@ -42,19 +46,4 @@ stan.on('connect', function () {
|
||||
}
|
||||
msg.ack()
|
||||
})
|
||||
prioSub.on('message', async (msg) => {
|
||||
let data = null
|
||||
try {
|
||||
data = JSON.parse(msg.getData())
|
||||
await updateStudent(data)
|
||||
stan.publish('status', `${data.studentInfo.studentnumber}:DONE`, (err) => { if (err) console.log(err) })
|
||||
} catch (err) {
|
||||
let id = 'null'
|
||||
if (data) {
|
||||
id = data.studentInfo ? data.studentInfo.studentnumber : 'meta'
|
||||
}
|
||||
console.log('priority student update failed', id, err)
|
||||
}
|
||||
msg.ack()
|
||||
})
|
||||
})
|
||||
+15
-15
@@ -3666,9 +3666,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.14",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz",
|
||||
"integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw=="
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
},
|
||||
"lodash.sortby": {
|
||||
"version": "4.7.0",
|
||||
@@ -4313,9 +4313,9 @@
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||
},
|
||||
"pg": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-7.11.0.tgz",
|
||||
"integrity": "sha512-YO4V7vCmEMGoF390LJaFaohWNKaA2ayoQOEZmiHVcAUF+YsRThpf/TaKCgSvsSE7cDm37Q/Cy3Gz41xiX/XjTw==",
|
||||
"version": "7.12.0",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-7.12.0.tgz",
|
||||
"integrity": "sha512-q54Ic0oBXfDZMwheP8ALeUX32TUXvF7SNgAlZjyhkDuFCJkQCgcLBz0Be5uOrAj3ljSok/CI9lRbYzEko0z1Zw==",
|
||||
"requires": {
|
||||
"buffer-writer": "2.0.0",
|
||||
"packet-reader": "1.0.0",
|
||||
@@ -4344,9 +4344,9 @@
|
||||
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
|
||||
},
|
||||
"pg-pool": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.6.tgz",
|
||||
"integrity": "sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g=="
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.7.tgz",
|
||||
"integrity": "sha512-UiJyO5B9zZpu32GSlP0tXy8J2NsJ9EFGFfz5v6PSbdz/1hBLX1rNiiy5+mAm5iJJYwfCv4A0EBcQLGWwjbpzZw=="
|
||||
},
|
||||
"pg-types": {
|
||||
"version": "2.0.1",
|
||||
@@ -4858,9 +4858,9 @@
|
||||
}
|
||||
},
|
||||
"sequelize": {
|
||||
"version": "5.10.2",
|
||||
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.10.2.tgz",
|
||||
"integrity": "sha512-A7QTOuT/tqPChkT6TW7ujHO0hbngctBIHU9BoGuFkzMN7cMkzkxpGJdztOl3rxe9EQnQFRbUOHbvUvPBZI4CPA==",
|
||||
"version": "5.11.0",
|
||||
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.11.0.tgz",
|
||||
"integrity": "sha512-JcCX87sqiAelFtIzmvgEGWoFjcwqaJIDI9MNm9RtBh5mY5j+L/9sk644imkd1kX3julvEXeu9KCXY/Ofje0rCQ==",
|
||||
"requires": {
|
||||
"bluebird": "^3.5.0",
|
||||
"cls-bluebird": "^2.1.0",
|
||||
@@ -5762,9 +5762,9 @@
|
||||
}
|
||||
},
|
||||
"wkx": {
|
||||
"version": "0.4.7",
|
||||
"resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.7.tgz",
|
||||
"integrity": "sha512-pHf546L96TK8RradLt1cWaIffstgv/zXZ14CGz5KnBs1AxBX0wm+IDphjJw0qrEqRv8P9W9CdTt8Z1unMRZ19A==",
|
||||
"version": "0.4.8",
|
||||
"resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz",
|
||||
"integrity": "sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
|
||||
@@ -20,10 +20,11 @@
|
||||
"dotenv": "^8.0.0",
|
||||
"eslint": "^6.1.0",
|
||||
"jest": "^24.8.0",
|
||||
"lodash": "^4.17.15",
|
||||
"node-nats-streaming": "^0.2.6",
|
||||
"nodemon": "^1.19.1",
|
||||
"pg": "^7.11.0",
|
||||
"sequelize": "^5.10.2",
|
||||
"pg": "^7.12.0",
|
||||
"sequelize": "^5.11.0",
|
||||
"umzug": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const { sequelize, } = require('../database/connection')
|
||||
const { sortBy, flatMap } = require('lodash')
|
||||
|
||||
const {
|
||||
Student, Credit, Course, CreditTeacher, Teacher,
|
||||
@@ -17,48 +18,62 @@ const awaitforEach = async (arr, func) => {
|
||||
}
|
||||
|
||||
const updateAttainments = async (studyAttainments, transaction) => {
|
||||
for (const { course } of studyAttainments) {
|
||||
// sort data to avoid deadlocks
|
||||
const courses = sortBy(studyAttainments.map(e => e.course), 'code')
|
||||
const disciplines = sortBy(flatMap(studyAttainments, e => e.course.disciplines || []), 'discipline_id', 'course_id')
|
||||
const providers = sortBy(flatMap(studyAttainments, e => e.course.providers), 'providercode')
|
||||
const courseproviders = sortBy(flatMap(studyAttainments, e => e.course.courseproviders), 'coursecode', 'providercode')
|
||||
const credits = sortBy(studyAttainments.map(e => e.credit), 'id')
|
||||
const teachers = sortBy(flatMap(studyAttainments, e => e.teachers || []), 'id')
|
||||
const creditTeachers = sortBy(flatMap(studyAttainments, e => e.creditTeachers), 'teacher_id', 'credit_id')
|
||||
|
||||
for (const course of courses) {
|
||||
await Course.upsert(course, { transaction })
|
||||
}
|
||||
for (const { course } of studyAttainments) {
|
||||
const { disciplines } = course
|
||||
disciplines && disciplines.length > 0 && await awaitforEach(disciplines, async courseDiscipline => await CourseDisciplines.upsert(courseDiscipline, { transaction }))
|
||||
for (const courseDiscipline of disciplines) {
|
||||
await CourseDisciplines.upsert(courseDiscipline, { transaction })
|
||||
}
|
||||
for (const { course } of studyAttainments) {
|
||||
const { providers } = course
|
||||
providers.length > 0 && await awaitforEach(providers, async provider => await Provider.upsert(provider, { transaction }))
|
||||
for (const provider of providers) {
|
||||
await Provider.upsert(provider, { transaction })
|
||||
}
|
||||
for (const { course } of studyAttainments) {
|
||||
const { courseproviders } = course
|
||||
courseproviders.length > 0 && await awaitforEach(courseproviders, async courseProvider => await CourseProvider.upsert(courseProvider, { transaction }))
|
||||
// must be after providers inserted
|
||||
for (const courseProvider of courseproviders) {
|
||||
await CourseProvider.upsert(courseProvider, { transaction })
|
||||
}
|
||||
for (const { credit } of studyAttainments) {
|
||||
for (const credit of credits) {
|
||||
await Credit.upsert(credit, { transaction })
|
||||
}
|
||||
for (const { teachers } of studyAttainments) {
|
||||
teachers && teachers.length > 0 && await awaitforEach(teachers, async teacher => await Teacher.upsert(teacher, { transaction }))
|
||||
for (const teacher of teachers) {
|
||||
await Teacher.upsert(teacher, { transaction })
|
||||
}
|
||||
// must be after teachers inserted
|
||||
for (const { creditTeachers } of studyAttainments) {
|
||||
creditTeachers.length > 0 && await awaitforEach(creditTeachers, async cT => await CreditTeacher.upsert(cT, { transaction }))
|
||||
for (const creditTeacher of creditTeachers) {
|
||||
await CreditTeacher.upsert(creditTeacher, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
const updateStudyRights = async (studyRights, transaction) => {
|
||||
for (const { studyRightExtent } of studyRights) {
|
||||
// sort data to avoid deadlocks
|
||||
const studyRightExtents = sortBy(studyRights.map(e => e.studyRightExtent), 'extentcode')
|
||||
const studyrights = sortBy(studyRights.map(e => e.studyright), 'studyrightid')
|
||||
const elementDetails = sortBy(flatMap(studyRights, e => e.elementDetails), 'code')
|
||||
const studyRightElements = sortBy(flatMap(studyRights, e => e.studyRightElements), 'startdate', 'enddate', 'studyrightid', 'code')
|
||||
const transfers = sortBy(flatMap(studyRights, e => e.transfers), 'transferdate', 'studentnumber', 'studyrightid', 'sourcecode', 'targetcode')
|
||||
|
||||
for (const studyRightExtent of studyRightExtents) {
|
||||
await StudyrightExtent.upsert(studyRightExtent, { transaction })
|
||||
}
|
||||
for (const { studyright } of studyRights) {
|
||||
for (const studyright of studyrights) {
|
||||
await Studyright.create(studyright, { transaction })
|
||||
}
|
||||
for (const { elementDetails } of studyRights) {
|
||||
await awaitforEach(elementDetails, async elementdetails => await ElementDetails.upsert(elementdetails, { transaction }))
|
||||
for (const elementdetail of elementDetails) {
|
||||
await ElementDetails.upsert(elementdetail, { transaction })
|
||||
}
|
||||
for (const { studyRightElements } of studyRights) {
|
||||
await awaitforEach(studyRightElements, async StudyRightElement => await StudyrightElement.create(StudyRightElement, { transaction }))
|
||||
for (const StudyRightElement of studyRightElements) {
|
||||
await StudyrightElement.create(StudyRightElement, { transaction })
|
||||
}
|
||||
for (const { transfers } of studyRights) {
|
||||
await awaitforEach(transfers, async transfer => await Transfers.upsert(transfer, { transaction }))
|
||||
for (const transfer of transfers) {
|
||||
await Transfers.upsert(transfer, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,11 +83,15 @@ const deleteStudentStudyrights = async (studentnumber, transaction) => {
|
||||
}
|
||||
|
||||
const updateStudent = async (student) => {
|
||||
const { studentInfo, studyAttainments, semesterEnrollments, studyRights } = student
|
||||
let { studentInfo, studyAttainments, semesterEnrollments, studyRights } = student
|
||||
console.log('starting', studentInfo.studentnumber)
|
||||
console.time(studentInfo.studentnumber)
|
||||
|
||||
// sort data to avoid deadlocks
|
||||
semesterEnrollments = sortBy(semesterEnrollments, 'studentnumber', 'semestercode')
|
||||
|
||||
const transaction = await sequelize.transaction()
|
||||
try {
|
||||
console.log('starting', studentInfo.studentnumber)
|
||||
console.time(studentInfo.studentnumber)
|
||||
await deleteStudentStudyrights(studentInfo.studentnumber, transaction) // this needs to be done because Oodi just deletes deprecated studyrights from students ( big yikes )
|
||||
|
||||
await Student.upsert(studentInfo, { transaction })
|
||||
|
||||
@@ -257,18 +257,18 @@ describe('Updater works', () => {
|
||||
})
|
||||
|
||||
test('study right elements are written to database', async () => {
|
||||
const studyrightElements = await StudyrightElement.findAll({ order: [['id', 'DESC']] })
|
||||
const studyrightElements = await StudyrightElement.findAll({ order: ['startdate', 'enddate', 'studyrightid', 'code'] })
|
||||
const { id, startdate, enddate, createdAt, updatedAt, studyrightid, code, studentnumber } = studyrightElements[0]
|
||||
|
||||
|
||||
expect(studyrightElements.length).toBe(21)
|
||||
expect(id).toBe(21)
|
||||
expect(startdate.toISOString()).toBe(new Date('2003-07-31 21:00:00+00').toISOString())
|
||||
expect(enddate.toISOString()).toBe(new Date('2010-07-30 21:00:00+00').toISOString())
|
||||
expect(id).toBe(11)
|
||||
expect(startdate.toISOString()).toBe(new Date('2003-07-31T21:00:00.000Z').toISOString())
|
||||
expect(enddate.toISOString()).toBe(new Date('2007-08-24T21:00:00.000Z').toISOString())
|
||||
expect(createdAt).not.toBe(undefined)
|
||||
expect(updatedAt).not.toBe(undefined)
|
||||
expect(studyrightid).toBe('62346253')
|
||||
expect(code).toBe('0394')
|
||||
expect(studyrightid).toBe('45853818')
|
||||
expect(code).toBe('A1995MAAT')
|
||||
expect(studentnumber).toBe('014272112')
|
||||
|
||||
})
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import { shape, func, bool } from 'prop-types'
|
||||
import { shape, func, bool, arrayOf, string } from 'prop-types'
|
||||
import { getTranslate } from 'react-localize-redux'
|
||||
import { Segment, Header } from 'semantic-ui-react'
|
||||
import qs from 'query-string'
|
||||
import { intersection, difference } from 'lodash'
|
||||
import { getCoursePopulation, getCoursePopulationCourses } from '../../redux/coursePopulation'
|
||||
import { getCourseStats } from '../../redux/coursestats'
|
||||
import CreditAccumulationGraphHighCharts from '../CreditAccumulationGraphHighCharts'
|
||||
@@ -13,8 +14,9 @@ import PopulationCourses from '../PopulationCourseStats'
|
||||
import infoTooltips from '../../common/InfoToolTips'
|
||||
import InfoBox from '../InfoBox'
|
||||
import SegmentDimmer from '../SegmentDimmer'
|
||||
import CourseStudentsFilters from '../CourseStudentsFilters'
|
||||
|
||||
const CourseStudents = ({ getCoursePopulationDispatch, getCoursePopulationCoursesDispatch, getCourseStatsDispatch, studentData, courses, pending, history, translate, courseData }) => {
|
||||
const CourseStudents = ({ getCoursePopulationDispatch, getCoursePopulationCoursesDispatch, getCourseStatsDispatch, studentData, courses, pending, history, translate, courseData, selectedStudents }) => {
|
||||
const parseQueryFromUrl = () => {
|
||||
const { location } = history
|
||||
const query = qs.parse(location.search)
|
||||
@@ -31,13 +33,14 @@ const CourseStudents = ({ getCoursePopulationDispatch, getCoursePopulationCourse
|
||||
setYear(query.year)
|
||||
}, [])
|
||||
const { CreditAccumulationGraph, CoursesOf } = infoTooltips.PopulationStatistics
|
||||
const selectedStudents = studentData.students ? studentData.students.map(student => student.studentNumber) : []
|
||||
const header = courseData[code] ? `${courseData[code].name} ${headerYear}` : null
|
||||
|
||||
return (
|
||||
<div className="segmentContainer">
|
||||
{studentData.students ? (
|
||||
<Segment className="contentSegment">
|
||||
<Header className="segmentTitle" size="large" textAlign="center">Population of course {header}</Header>
|
||||
<CourseStudentsFilters samples={studentData.students} coursecode={code} />
|
||||
<Segment>
|
||||
<Header size="medium" dividing>
|
||||
{translate('populationStatistics.graphSegmentHeader')} (for {selectedStudents.length} students)
|
||||
@@ -82,16 +85,44 @@ CourseStudents.propTypes = {
|
||||
studentData: shape({}).isRequired,
|
||||
history: shape({}).isRequired,
|
||||
translate: func.isRequired,
|
||||
courseData: shape({}).isRequired
|
||||
courseData: shape({}).isRequired,
|
||||
selectedStudents: arrayOf(string).isRequired
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ coursePopulation, localize, courseStats }) => ({
|
||||
studentData: coursePopulation.students,
|
||||
courses: coursePopulation.courses,
|
||||
pending: coursePopulation.pending,
|
||||
translate: getTranslate(localize),
|
||||
query: coursePopulation.query,
|
||||
courseData: courseStats.data
|
||||
})
|
||||
const mapStateToProps = ({ coursePopulation, localize, courseStats, populationFilters }) => {
|
||||
const samples = coursePopulation.students.students ? coursePopulation.students.students : []
|
||||
let selectedStudents = samples.length > 0 ? samples.map(s => s.studentNumber) : []
|
||||
const { complemented } = populationFilters
|
||||
|
||||
export default withRouter(connect(mapStateToProps, { getCoursePopulationDispatch: getCoursePopulation, getCoursePopulationCoursesDispatch: getCoursePopulationCourses, getCourseStatsDispatch: getCourseStats })(CourseStudents))
|
||||
if (samples.length > 0 && populationFilters.filters.length > 0) {
|
||||
const studentsForFilter = (f) => {
|
||||
if (f.type === 'CourseParticipation') {
|
||||
return Object.keys(f.studentsOfSelectedField)
|
||||
}
|
||||
return samples.filter(f.filter).map(s => s.studentNumber)
|
||||
}
|
||||
|
||||
const matchingStudents = populationFilters.filters.map(studentsForFilter)
|
||||
selectedStudents = intersection(...matchingStudents)
|
||||
|
||||
if (complemented) {
|
||||
selectedStudents = difference(samples.map(s => s.studentNumber), selectedStudents)
|
||||
}
|
||||
}
|
||||
|
||||
return ({
|
||||
studentData: coursePopulation.students,
|
||||
courses: coursePopulation.courses,
|
||||
pending: coursePopulation.pending,
|
||||
translate: getTranslate(localize),
|
||||
query: coursePopulation.query,
|
||||
courseData: courseStats.data,
|
||||
selectedStudents
|
||||
})
|
||||
}
|
||||
|
||||
export default withRouter(connect(mapStateToProps, {
|
||||
getCoursePopulationDispatch: getCoursePopulation,
|
||||
getCoursePopulationCoursesDispatch: getCoursePopulationCourses,
|
||||
getCourseStatsDispatch: getCourseStats
|
||||
})(CourseStudents))
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import React, { useState } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Segment, Icon, Button, Form, Dropdown, Popup } from 'semantic-ui-react'
|
||||
import { func, shape, string } from 'prop-types'
|
||||
|
||||
import { removePopulationFilter, setPopulationFilter } from '../../redux/populationFilters'
|
||||
import { gradeFilter } from '../../populationFilters'
|
||||
|
||||
|
||||
const GradeFilter = ({ coursecode, courseData, filter, setPopulationFilterAction, removePopulationFilterAction }) => {
|
||||
const [grade, setGrade] = useState(0)
|
||||
const handleFilter = () => {
|
||||
setPopulationFilterAction(gradeFilter({ grade, coursecode, coursename: courseData[coursecode].name }))
|
||||
}
|
||||
|
||||
const handleChange = (e, { value }) => {
|
||||
setGrade(value)
|
||||
}
|
||||
const clearFilter = () => {
|
||||
removePopulationFilterAction(filter.id)
|
||||
}
|
||||
|
||||
if (filter.notSet) {
|
||||
return (
|
||||
<Segment>
|
||||
<Form>
|
||||
<Popup
|
||||
trigger={<Icon style={{ float: 'right' }} name="info" />}
|
||||
/>
|
||||
<Form.Group inline>
|
||||
<Form.Field>
|
||||
<label>Select students that grade </label>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Dropdown
|
||||
placeholder="select"
|
||||
options={[{ key: 1, text: '1', value: 1 },
|
||||
{ key: 2, text: '2', value: 2 },
|
||||
{ key: 3, text: '3', value: 3 },
|
||||
{ key: 4, text: '4', value: 4 },
|
||||
{ key: 5, text: '5', value: 5 }]}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<label>from course {courseData[coursecode].name}</label>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Button onClick={handleFilter}>
|
||||
set filter
|
||||
</Button>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</Segment>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Segment>
|
||||
Students that have grade {filter.params.grade} from course {filter.params.coursename}
|
||||
<span style={{ float: 'right' }}>
|
||||
<Icon name="remove" onClick={clearFilter} />
|
||||
</span>
|
||||
</Segment>
|
||||
)
|
||||
}
|
||||
|
||||
GradeFilter.propTypes = {
|
||||
setPopulationFilterAction: func.isRequired,
|
||||
removePopulationFilterAction: func.isRequired,
|
||||
filter: shape({}).isRequired,
|
||||
courseData: shape({}).isRequired,
|
||||
coursecode: string.isRequired
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ courseStats }) => ({
|
||||
courseData: courseStats.data
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
setPopulationFilterAction: setPopulationFilter,
|
||||
removePopulationFilterAction: removePopulationFilter
|
||||
})(GradeFilter)
|
||||
@@ -0,0 +1,122 @@
|
||||
import React, { useState } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Segment, Header, Button, Form, Radio } from 'semantic-ui-react'
|
||||
import { func, arrayOf, bool, shape, string } from 'prop-types'
|
||||
import _ from 'lodash'
|
||||
|
||||
import CreditsLessThan from '../PopulationFilters/CreditsLessThan'
|
||||
import CreditsAtLeast from '../PopulationFilters/CreditsAtLeast'
|
||||
import GradeMeanFilter from '../PopulationFilters/GradeMeanFilter'
|
||||
import CourseParticipation from '../PopulationFilters/CourseParticipation'
|
||||
import SexFilter from '../PopulationFilters/SexFilter'
|
||||
import InfoBox from '../InfoBox'
|
||||
import infotooltips from '../../common/InfoToolTips'
|
||||
import GradeFilter from './GradeFilter'
|
||||
import {
|
||||
clearPopulationFilters, setComplementFilter
|
||||
} from '../../redux/populationFilters'
|
||||
|
||||
|
||||
const componentFor = {
|
||||
GradeFilter,
|
||||
CreditsAtLeast,
|
||||
CreditsLessThan,
|
||||
GradeMeanFilter,
|
||||
SexFilter,
|
||||
CourseParticipation
|
||||
}
|
||||
|
||||
const CourseStudentsFilters = ({ samples, filters, clearPopulationFiltersDispatch, complemented, setComplementFilterDispatch, allStudyrights, coursecode }) => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
const renderAddFilters = () => {
|
||||
const { Add } = infotooltips.PopulationStatistics.Filters
|
||||
const allFilters = _.union(Object.keys(componentFor))
|
||||
|
||||
const setFilters = _.union(
|
||||
filters.map(f => f.type),
|
||||
filters.filter(f => f.type === 'Preset').map(f => f.id),
|
||||
)
|
||||
|
||||
const unsetFilters = _.uniq(_.difference(allFilters, setFilters))
|
||||
if (unsetFilters.length === 0) {
|
||||
return null
|
||||
}
|
||||
if (!visible) {
|
||||
return (
|
||||
<Segment>
|
||||
<Header>Add filters <InfoBox content={Add} /></Header>
|
||||
<Button
|
||||
onClick={() => setVisible(true)}
|
||||
>
|
||||
add
|
||||
</Button>
|
||||
</Segment>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Segment>
|
||||
<Header>Add filters <InfoBox content={Add} /></Header>
|
||||
{unsetFilters.map(filterName => React.createElement(componentFor[filterName], { filter: { notSet: true }, key: filterName, samples, allStudyrights, coursecode }))}
|
||||
<Button onClick={() => setVisible(false)}>cancel</Button>
|
||||
</Segment>
|
||||
)
|
||||
}
|
||||
|
||||
const renderSetFilters = () => {
|
||||
const setFilters = filters.map(f => f.type)
|
||||
const { Filters } = infotooltips.PopulationStatistics.Filters
|
||||
if (setFilters.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Segment>
|
||||
<Header>Filters <InfoBox content={Filters} /></Header>
|
||||
{filters.map(filter => React.createElement(componentFor[filter.type], { filter, key: filter.id, samples, allStudyrights, coursecode }))}
|
||||
<Form>
|
||||
<Form.Group inline>
|
||||
<Form.Field>
|
||||
<label>Show excluded students only</label>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Radio
|
||||
toggle
|
||||
checked={complemented}
|
||||
onClick={setComplementFilterDispatch}
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
<Button onClick={clearPopulationFiltersDispatch}>clear all filters</Button>
|
||||
</Segment>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{renderAddFilters()}
|
||||
{renderSetFilters()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
CourseStudentsFilters.propTypes = {
|
||||
filters: arrayOf(shape([])).isRequired,
|
||||
complemented: bool.isRequired,
|
||||
samples: arrayOf(shape([])).isRequired,
|
||||
clearPopulationFiltersDispatch: func.isRequired,
|
||||
setComplementFilterDispatch: func.isRequired,
|
||||
coursecode: string.isRequired,
|
||||
allStudyrights: shape({}).isRequired
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ populationFilters, coursePopulation }) => ({
|
||||
filters: populationFilters.filters,
|
||||
complemented: populationFilters.complemented,
|
||||
allStudyrights: coursePopulation.students.studyrights
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
clearPopulationFiltersDispatch: clearPopulationFilters,
|
||||
setComplementFilterDispatch: setComplementFilter
|
||||
})(CourseStudentsFilters)
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Segment, Icon, Button, Form, Dropdown, Popup } from 'semantic-ui-react'
|
||||
import { func, shape, arrayOf } from 'prop-types'
|
||||
import _ from 'lodash'
|
||||
import { uniqBy } from 'lodash'
|
||||
|
||||
import { removePopulationFilter, setPopulationFilter } from '../../redux/populationFilters'
|
||||
import { tagFilter } from '../../populationFilters'
|
||||
@@ -15,7 +15,7 @@ const TagFilter = ({ setPopulationFilterAction, removePopulationFilterAction, fi
|
||||
const createOptions = () => {
|
||||
const tags = samples.map(s => s.tags.map(t => ({ tagname: t.tag.tagname, tag_id: t.tag.tag_id })))
|
||||
const merge = tags.reduce((a, b) => a.concat(b), [])
|
||||
const uniqueTags = _.uniqBy(merge, 'tag_id')
|
||||
const uniqueTags = uniqBy(merge, 'tag_id')
|
||||
const createdOptions = uniqueTags.map(tag => ({ key: tag.tag_id, text: tag.tagname, value: tag.tag_id }))
|
||||
setOptions(createdOptions)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { Component } from 'react'
|
||||
import { Button, Progress, Message } from 'semantic-ui-react'
|
||||
import { api } from '../../apiConnection'
|
||||
import KeyValueTable from '../Postman/KeyValueTable'
|
||||
import { updatePopulationOldestStudents } from '../../redux/populations'
|
||||
|
||||
const TASK_URL = '/tasks/topteachers'
|
||||
|
||||
@@ -54,6 +55,11 @@ class UpdateTopTeachers extends Component {
|
||||
onClick={this.checkStatus}
|
||||
loading={fetching}
|
||||
/>
|
||||
<Button
|
||||
content="Update 2000 oldest students"
|
||||
icon="refresh"
|
||||
onClick={() => updatePopulationOldestStudents(2000)}
|
||||
/>
|
||||
</Button.Group>
|
||||
{ status && <KeyValueTable data={status} /> }
|
||||
{ !!computing && <Progress autoSuccess active={computing} percent={progress} />}
|
||||
|
||||
@@ -95,6 +95,8 @@ const TagStudent = ({
|
||||
selection
|
||||
options={tagOptions}
|
||||
onChange={handleChange}
|
||||
selectOnBlur={false}
|
||||
selectOnNavigation={false}
|
||||
/>
|
||||
</List.Content>
|
||||
</List.Item>
|
||||
|
||||
@@ -273,6 +273,23 @@ export const tagFilter = (params) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const gradeFilter = (params) => {
|
||||
const { coursecode, grade, coursename } = params
|
||||
return ({
|
||||
id: uuidv4(),
|
||||
type: 'GradeFilter',
|
||||
params: {
|
||||
coursecode,
|
||||
grade,
|
||||
coursename
|
||||
},
|
||||
filter: (student) => {
|
||||
const course = student.courses.find(c => c.course.code === coursecode)
|
||||
return Number(course.grade) === grade
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const presetFilter = preset => ({
|
||||
id: preset.id,
|
||||
type: 'Preset',
|
||||
|
||||
@@ -33,6 +33,12 @@ export const updatePopulationStudents = (students) => {
|
||||
|
||||
return callController(route, prefix, students, 'post')
|
||||
}
|
||||
export const updatePopulationOldestStudents = (amount) => {
|
||||
const route = '/updatedatabase/oldest'
|
||||
const prefix = 'UPDATE_POPULATION_OLDEST_STUDENTS_'
|
||||
|
||||
return callController(route, prefix, amount, 'post')
|
||||
}
|
||||
export const clearPopulations = () => ({
|
||||
type: 'CLEAR_POPULATIONS'
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
require('dotenv').config()
|
||||
const axios = require('axios')
|
||||
const { setupCache } = require('axios-cache-adapter')
|
||||
const https = require('https')
|
||||
const fs = require('fs')
|
||||
const logger = require('../logger')
|
||||
@@ -7,6 +8,13 @@ const logger = require('../logger')
|
||||
const { OODI_ADDR, KEY_PATH, CERT_PATH } = process.env
|
||||
const base_url = OODI_ADDR
|
||||
|
||||
const cache = setupCache({
|
||||
maxAge: 15 * 60 * 1000,
|
||||
exclude: {
|
||||
paths: ['/students/'],
|
||||
},
|
||||
})
|
||||
|
||||
const agent = KEY_PATH && CERT_PATH ?
|
||||
new https.Agent({
|
||||
cert: fs.readFileSync(process.env.CERT_PATH, 'utf8'),
|
||||
@@ -18,7 +26,8 @@ const agent = KEY_PATH && CERT_PATH ?
|
||||
})
|
||||
|
||||
const instance = axios.create({
|
||||
httpsAgent: agent
|
||||
httpsAgent: agent,
|
||||
adapter: cache.adapter
|
||||
})
|
||||
|
||||
const getUrl = instance.get
|
||||
|
||||
Generated
+25
@@ -277,6 +277,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"axios-cache-adapter": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/axios-cache-adapter/-/axios-cache-adapter-2.3.3.tgz",
|
||||
"integrity": "sha512-8sDkMwrW1zXB4J2VxVyjjKocPqCzGRPrqNtKChbkIR/MCpSUVGcdCcJlO3nnsJMAIak/NCCRy8wX5uvNJ6Yj2Q==",
|
||||
"requires": {
|
||||
"axios": "^0.18.0",
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.18.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz",
|
||||
"integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10",
|
||||
"is-buffer": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"is-buffer": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
|
||||
"integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-eslint": {
|
||||
"version": "10.0.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.2.tgz",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"axios-cache-adapter": "^2.3.3",
|
||||
"dotenv": "^7.0.0",
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.24.0",
|
||||
|
||||
@@ -121,13 +121,14 @@ stan.on('connect', async () => {
|
||||
updatedCount = updatedCount + 1
|
||||
break
|
||||
case 'FETCHED':
|
||||
case 'NO_STUDENT':
|
||||
fetchedCount = fetchedCount + 1
|
||||
break
|
||||
case 'SCHEDULED':
|
||||
scheduledCount = scheduledCount + 1
|
||||
break
|
||||
default:
|
||||
throw 'unknown status in status message'
|
||||
throw 'unknown status: ' + msg.getData()
|
||||
}
|
||||
const isValidStudentId = (id) => {
|
||||
if (/^0\d{8}$/.test(id)) {
|
||||
|
||||
@@ -2,7 +2,7 @@ const express = require('express')
|
||||
const app = express()
|
||||
const port = process.env.PORT
|
||||
const bodyParser = require('body-parser')
|
||||
const { scheduleStudentsByArray } = require('./schedule_students')
|
||||
const { scheduleStudentsByArray, scheduleOldestNStudents } = require('./schedule_students')
|
||||
app.use(bodyParser.json())
|
||||
|
||||
|
||||
@@ -12,5 +12,11 @@ app.post('/update', async (req, res) => {
|
||||
const msg = await scheduleStudentsByArray(req.body)
|
||||
res.json({ message: msg })
|
||||
})
|
||||
|
||||
app.post('/update/oldest', async (req, res) => {
|
||||
const msg = await scheduleOldestNStudents(req.body)
|
||||
res.json({ message: msg })
|
||||
})
|
||||
|
||||
app.listen(port, () => console.log(`listening on port ${port}!`))
|
||||
module.exports = { app }
|
||||
@@ -57,4 +57,15 @@ const scheduleStudentsByArray = async (studentNumbers) => {
|
||||
}
|
||||
return 'scheduled'
|
||||
}
|
||||
module.exports = { scheduleActiveStudents, scheduleAllStudentsAndMeta, scheduleStudentsByArray, scheduleMeta }
|
||||
|
||||
const scheduleOldestNStudents = async (amount) => {
|
||||
try {
|
||||
const tasks = [...await Schedule.find({ type: 'student' }).limit(Number(amount)).sort({ updatedAt: 1 })]
|
||||
await publish(tasks, true)
|
||||
} catch (e) {
|
||||
return e
|
||||
}
|
||||
return 'scheduled'
|
||||
}
|
||||
|
||||
module.exports = { scheduleActiveStudents, scheduleAllStudentsAndMeta, scheduleStudentsByArray, scheduleOldestNStudents, scheduleMeta }
|
||||
Reference in New Issue
Block a user