Compare commits

...
18 Commits
Author SHA1 Message Date
Rochet2andGitHub ed265c70f9 Merge pull request #1185 from UniversityOfHelsinkiCS/trunk
Fix flaky test, Add a button, Remove compression, Record video always locally, Handle a new student status
2019-07-30 18:41:25 +03:00
Rochet2 862321da55 Compression, what is that? 2019-07-30 18:25:03 +03:00
Rochet2 5164b155aa Wait until page updates after changing filters 2019-07-30 18:15:41 +03:00
Rochet2 53b6b480b6 Locally always record a video in case of error 2019-07-30 18:12:18 +03:00
Rochet2 cf9420b758 Fix unknown status and simplify code 2019-07-30 17:25:02 +03:00
Rochet2 9cce15cf31 Add update oldest 2000 students button 2019-07-30 17:23:57 +03:00
Rochet2andGitHub cd3b6ace84 Merge pull request #1184 from UniversityOfHelsinkiCS/trunk
Updater api cache and sort data to try avoid deadlocks
2019-07-30 16:11:43 +03:00
Rochet2 4af5783dac Merge branch 'trunk' of github.com:UniversityOfHelsinkiCS/oodikone into trunk 2019-07-30 15:33:29 +03:00
Rochet2 5ec875cfc8 Fix tests for updater writer 2019-07-30 15:33:19 +03:00
esakemp ecefaf11a1 npm update 2019-07-30 15:20:09 +03:00
Rochet2 d32acd720e Merge branch 'trunk' of github.com:UniversityOfHelsinkiCS/oodikone into trunk 2019-07-30 15:16:54 +03:00
Rochet2 609fa4520d Try avoid deadlocks through sorting data 2019-07-30 15:16:40 +03:00
esakempandGitHub 9017925129 Merge pull request #1183 from UniversityOfHelsinkiCS/trunk
new amazing filter
2019-07-30 14:54:13 +03:00
esakemp bd0258b194 new amazing filter 2019-07-30 14:33:41 +03:00
Rochet2 40b7440123 Updater api cache to reduce oodi api request count 2019-07-30 14:01:06 +03:00
esakempandGitHub 9f65f9b55e Merge pull request #1182 from UniversityOfHelsinkiCS/trunk
filters for course populatoin
2019-07-30 12:59:13 +03:00
esakemp a99201dd21 Merge branch 'trunk' of https://github.com/UniversityOfHelsinkiCS/oodikone into trunk 2019-07-30 10:39:02 +03:00
esakemp 25c383957c course population filter stuff 2019-07-30 10:38:55 +03:00
24 changed files with 554 additions and 187 deletions
@@ -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)
})
+112 -96
View File
@@ -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
View File
@@ -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
}
+6 -17
View File
@@ -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
View File
@@ -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": "*"
}
+3 -2
View File
@@ -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
+25
View File
@@ -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",
+1
View File
@@ -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",
+2 -1
View File
@@ -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)) {
+7 -1
View File
@@ -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 }