refactor - Bulk management tab unit tests (#194)
* simplify histry row entries * clean up BulkManagement tab component and add docstrings/unit tests * slightly more breakup and docstrings * fix: make updateQueryParams push to history again * update paragon to get latest Hyperlink version * fix display issues from PR * make redux exposure only available in development * fix message * add oxford comma * update snapshots * v1.4.37
This commit is contained in:
289
package-lock.json
generated
289
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@edx/frontend-app-gradebook",
|
||||
"version": "1.4.34",
|
||||
"version": "1.4.36",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -3316,9 +3316,9 @@
|
||||
}
|
||||
},
|
||||
"@edx/paragon": {
|
||||
"version": "14.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-14.6.1.tgz",
|
||||
"integrity": "sha512-y80VLJZ6ZYEEw+T4/BFR+rqWPyb8Q8rlQRin/BssMNbEErsZLG+JpGU6Ff/r1n3XhvXC+B1XL4dZCb+kM6Wz2w==",
|
||||
"version": "14.16.4",
|
||||
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-14.16.4.tgz",
|
||||
"integrity": "sha512-3af3jXEyg+ojIhWOov64VZo7LWgdLuPgS5SaaraGWpTFhcp9WnKaFxicqGtzhx9lH4RqS+SC7gua5pn2xR4UzA==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.30",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.14.0",
|
||||
@@ -4242,12 +4242,11 @@
|
||||
"integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q=="
|
||||
},
|
||||
"@restart/hooks": {
|
||||
"version": "0.3.26",
|
||||
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.26.tgz",
|
||||
"integrity": "sha512-7Hwk2ZMYm+JLWcb7R9qIXk1OoUg1Z+saKWqZXlrvFwT3w6UArVNWgxYOzf+PJoK9zZejp8okPAKTctthhXLt5g==",
|
||||
"version": "0.3.27",
|
||||
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.27.tgz",
|
||||
"integrity": "sha512-s984xV/EapUIfkjlf8wz9weP2O9TNKR96C68FfMEy2bE69+H4cNv3RD4Mf97lW7Htt7PjZrYTjSC8f3SB9VCXw==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.20",
|
||||
"lodash-es": "^4.17.20"
|
||||
"dequal": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"@semantic-release/commit-analyzer": {
|
||||
@@ -5122,14 +5121,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A==",
|
||||
"requires": {
|
||||
"classnames": "*"
|
||||
}
|
||||
},
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
@@ -5263,9 +5254,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "17.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.4.tgz",
|
||||
"integrity": "sha512-onz2BqScSFMoTRdJUZUDD/7xrusM8hBA2Fktk2qgaTYPCgPvWnDEgkrOs8hhPUf2jfcIXkJ5yK6VfYormJS3Jw==",
|
||||
"version": "17.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.11.tgz",
|
||||
"integrity": "sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==",
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
@@ -5937,9 +5928,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"aria-hidden": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.2.tgz",
|
||||
"integrity": "sha512-WAMH9q3vRimVqP+B0q2eDvx7IPDoY17A2fWwj5atTA/zTYJCNcS6HJ5YErZ5FO3PUHhrV0y0yR1NA0dRNm913A==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.3.tgz",
|
||||
"integrity": "sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==",
|
||||
"requires": {
|
||||
"tslib": "^1.0.0"
|
||||
},
|
||||
@@ -7001,7 +6992,7 @@
|
||||
},
|
||||
"p-cancelable": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "http://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
|
||||
"integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
@@ -9696,14 +9687,14 @@
|
||||
"dependencies": {
|
||||
"file-type": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
|
||||
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
@@ -9860,6 +9851,11 @@
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
|
||||
"dev": true
|
||||
},
|
||||
"dequal": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz",
|
||||
"integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug=="
|
||||
},
|
||||
"des.js": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
|
||||
@@ -11825,18 +11821,11 @@
|
||||
}
|
||||
},
|
||||
"focus-lock": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.8.1.tgz",
|
||||
"integrity": "sha512-/LFZOIo82WDsyyv7h7oc0MJF9ACOvDRdx9rWPZ2pgMfNWu/z8hQDBtOchuB/0BVLmuFOZjV02YwUVzNsWx/EzA==",
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.9.1.tgz",
|
||||
"integrity": "sha512-/2Nj60Cps6yOLSO+CkVbeSKfwfns5XbX6HOedIK9PdzODP04N9c3xqOcPXayN0WsT9YjJvAnXmI0NdqNIDf5Kw==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
}
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
@@ -16817,11 +16806,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
@@ -18357,7 +18341,7 @@
|
||||
},
|
||||
"bin-links": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/bin-links/-/bin-links-1.1.8.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-KgmVfx+QqggqP9dA3iIc5pA4T1qEEEL+hOhOhNPaUm77OTrJoOXE/C05SJLNJe6m/2wUK7F1tDSou7n5TfCDzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -18484,7 +18468,7 @@
|
||||
},
|
||||
"chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -18532,7 +18516,7 @@
|
||||
},
|
||||
"cliui": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -18543,19 +18527,19 @@
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -18566,7 +18550,7 @@
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -18700,7 +18684,7 @@
|
||||
},
|
||||
"configstore": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.5.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -18848,7 +18832,7 @@
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -18906,7 +18890,7 @@
|
||||
},
|
||||
"dot-prop": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -18982,7 +18966,7 @@
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -19006,7 +18990,7 @@
|
||||
},
|
||||
"env-paths": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -19220,7 +19204,7 @@
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -19229,7 +19213,7 @@
|
||||
"dependencies": {
|
||||
"minipass": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -19349,7 +19333,7 @@
|
||||
},
|
||||
"gentle-fs": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/gentle-fs/-/gentle-fs-2.3.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-OlwBBwqCFPcjm33rF2BjW+Pr6/ll2741l+xooiwTCeaX2CA1ZuclavyMBe0/KlR21/XGsgY6hzEQZ15BdNa13Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -19382,7 +19366,7 @@
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -19406,7 +19390,7 @@
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -19456,7 +19440,7 @@
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -19505,7 +19489,7 @@
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -19538,7 +19522,7 @@
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -19572,7 +19556,7 @@
|
||||
},
|
||||
"ignore-walk": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -19655,7 +19639,7 @@
|
||||
},
|
||||
"is-ci": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -19742,7 +19726,7 @@
|
||||
},
|
||||
"is-retry-allowed": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -19851,7 +19835,7 @@
|
||||
},
|
||||
"libcipm": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/libcipm/-/libcipm-4.0.8.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-IN3hh2yDJQtZZ5paSV4fbvJg4aHxCCg5tcZID/dSVlTuUiWktsgaldVljJv6Z5OUlYspx6xQkbR0efNodnIrOA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20034,7 +20018,7 @@
|
||||
},
|
||||
"libnpx": {
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/libnpx/-/libnpx-10.2.4.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-BPc0D1cOjBeS8VIBKUu5F80s6njm0wbVt7CsGMrIcJ+SI7pi7V0uVPGpEMH9H5L8csOcclTxAXFE2VAsJXUhfA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20178,7 +20162,7 @@
|
||||
},
|
||||
"make-fetch-happen": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20197,7 +20181,7 @@
|
||||
},
|
||||
"meant": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/meant/-/meant-1.0.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-KN+1uowN/NK+sT/Lzx7WSGIj2u+3xe5n2LbwObfjOhPZiA+cCfCm6idVl0RkEfjThkw5XJ96CyRcanq6GmKtUg==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -20227,13 +20211,13 @@
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20242,7 +20226,7 @@
|
||||
"dependencies": {
|
||||
"minipass": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20272,7 +20256,7 @@
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20281,7 +20265,7 @@
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
}
|
||||
@@ -20334,7 +20318,7 @@
|
||||
},
|
||||
"node-gyp": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20353,7 +20337,7 @@
|
||||
},
|
||||
"nopt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20386,7 +20370,7 @@
|
||||
},
|
||||
"npm-audit-report": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-1.3.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-8nH/JjsFfAWMvn474HB9mpmMjrnKb1Hx/oTAdjv4PT9iZBvBxiZ+wtDUapHCJwLqYGQVPaAfs+vL5+5k9QndXw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20396,7 +20380,7 @@
|
||||
},
|
||||
"npm-bundled": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20411,7 +20395,7 @@
|
||||
},
|
||||
"npm-install-checks": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-3.0.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20420,7 +20404,7 @@
|
||||
},
|
||||
"npm-lifecycle": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20442,7 +20426,7 @@
|
||||
},
|
||||
"npm-normalize-package-bin": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -20460,7 +20444,7 @@
|
||||
},
|
||||
"npm-packlist": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20482,7 +20466,7 @@
|
||||
},
|
||||
"npm-profile": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-4.0.4.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20493,7 +20477,7 @@
|
||||
},
|
||||
"npm-registry-fetch": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.7.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20508,7 +20492,7 @@
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true
|
||||
}
|
||||
@@ -20632,7 +20616,7 @@
|
||||
},
|
||||
"pacote": {
|
||||
"version": "9.5.12",
|
||||
"resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.12.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20670,7 +20654,7 @@
|
||||
"dependencies": {
|
||||
"minipass": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20907,7 +20891,7 @@
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20928,7 +20912,7 @@
|
||||
},
|
||||
"read-cmd-shim": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20952,7 +20936,7 @@
|
||||
},
|
||||
"read-package-json": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20976,7 +20960,7 @@
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -20999,7 +20983,7 @@
|
||||
},
|
||||
"registry-auth-token": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21052,7 +21036,7 @@
|
||||
},
|
||||
"require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -21070,7 +21054,7 @@
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21165,13 +21149,13 @@
|
||||
},
|
||||
"smart-buffer": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==",
|
||||
"dev": true
|
||||
},
|
||||
"socks": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21280,7 +21264,7 @@
|
||||
},
|
||||
"spdx-license-ids": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -21409,7 +21393,7 @@
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21418,7 +21402,7 @@
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
|
||||
"dev": true
|
||||
}
|
||||
@@ -21426,7 +21410,7 @@
|
||||
},
|
||||
"stringify-package": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -21462,7 +21446,7 @@
|
||||
},
|
||||
"tar": {
|
||||
"version": "4.4.13",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21477,7 +21461,7 @@
|
||||
"dependencies": {
|
||||
"minipass": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21689,7 +21673,7 @@
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
|
||||
"dev": true
|
||||
},
|
||||
@@ -21771,7 +21755,7 @@
|
||||
},
|
||||
"widest-line": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21789,7 +21773,7 @@
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21800,19 +21784,19 @@
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21823,7 +21807,7 @@
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21875,7 +21859,7 @@
|
||||
},
|
||||
"yargs": {
|
||||
"version": "14.2.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21894,13 +21878,13 @@
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"dev": true
|
||||
},
|
||||
"find-up": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21909,13 +21893,13 @@
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21925,7 +21909,7 @@
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21934,7 +21918,7 @@
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21943,13 +21927,13 @@
|
||||
},
|
||||
"p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21960,7 +21944,7 @@
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21971,7 +21955,7 @@
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "15.0.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -21981,7 +21965,7 @@
|
||||
"dependencies": {
|
||||
"camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"dev": true
|
||||
}
|
||||
@@ -24066,37 +24050,41 @@
|
||||
}
|
||||
},
|
||||
"react-bootstrap": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.2.tgz",
|
||||
"integrity": "sha512-mGKPY5+lLd7Vtkx2VFivoRkPT4xAHazuFfIhJLTEgHlDfIUSePn7qrmpZe5gXH9zvHV0RsBaQ9cLfXjxnZrOpA==",
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.6.1.tgz",
|
||||
"integrity": "sha512-ojEPQ6OtyIMdLg0Smofk+85PKN6MLKQX3bU0Vwmok/4yNa8DQ2vCGhO2IgHJvT+ERQZ4X+gAQcdn6msAHSwLBg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.8",
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"@restart/context": "^2.1.4",
|
||||
"@restart/hooks": "^0.3.26",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/invariant": "^2.2.33",
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"@types/react": ">=16.9.35",
|
||||
"@types/react": ">=16.14.8",
|
||||
"@types/react-transition-group": "^4.4.1",
|
||||
"@types/warning": "^3.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"dom-helpers": "^5.1.2",
|
||||
"classnames": "^2.3.1",
|
||||
"dom-helpers": "^5.2.1",
|
||||
"invariant": "^2.2.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"prop-types-extra": "^1.1.0",
|
||||
"react-overlays": "^5.0.0",
|
||||
"react-overlays": "^5.0.1",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"uncontrollable": "^7.2.1",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.13.17",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.17.tgz",
|
||||
"integrity": "sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==",
|
||||
"version": "7.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz",
|
||||
"integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -24109,9 +24097,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.13.17",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.17.tgz",
|
||||
"integrity": "sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==",
|
||||
"version": "7.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz",
|
||||
"integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
@@ -24539,12 +24527,12 @@
|
||||
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
||||
},
|
||||
"react-focus-lock": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.5.0.tgz",
|
||||
"integrity": "sha512-XLxj6uTXgz0US8TmqNU2jMfnXwZG0mH2r/afQqvPEaX6nyEll5LHVcEXk2XDUQ34RVeLPkO/xK5x6c/qiuSq/A==",
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.5.1.tgz",
|
||||
"integrity": "sha512-gOToRZKVEymGEjFaTRUKgJsdYQrNosoiK7yZnXnnd8bYew4vMzk3Rxb0Q4nyrGwsFuUmgQiSAulQirA0J+v4hA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"focus-lock": "^0.8.1",
|
||||
"focus-lock": "^0.9.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-clientside-effect": "^1.2.2",
|
||||
"use-callback-ref": "^1.2.1",
|
||||
@@ -24602,9 +24590,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.13.17",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.17.tgz",
|
||||
"integrity": "sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==",
|
||||
"version": "7.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz",
|
||||
"integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
@@ -24640,9 +24628,9 @@
|
||||
}
|
||||
},
|
||||
"react-remove-scroll": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.1.tgz",
|
||||
"integrity": "sha512-K7XZySEzOHMTq7dDwcHsZA6Y7/1uX5RsWhRXVYv8rdh+y9Qz2nMwl9RX/Mwnj/j7JstCGmxyfyC0zbVGXYh3mA==",
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.2.tgz",
|
||||
"integrity": "sha512-mMSIZYQF3jS2uRJXeFDRaVGA+BGs/hIryV64YUKsHFtpgwZloOUcdu0oW8K6OU8uLHt/kM5d0lUZbdpIVwgXtQ==",
|
||||
"requires": {
|
||||
"react-remove-scroll-bar": "^2.1.0",
|
||||
"react-style-singleton": "^2.1.0",
|
||||
@@ -24781,9 +24769,9 @@
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
|
||||
"integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
|
||||
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
@@ -28790,8 +28778,7 @@
|
||||
"tslib": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
|
||||
},
|
||||
"tty-browserify": {
|
||||
"version": "0.0.0",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@edx/brand": "npm:@edx/brand-edx.org@^1.3.2",
|
||||
"@edx/frontend-component-footer": "10.1.1",
|
||||
"@edx/frontend-platform": "1.9.5",
|
||||
"@edx/paragon": "14.6.1",
|
||||
"@edx/paragon": "14.16.4",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.25",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.11.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.11.2",
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
/* eslint-disable react/sort-comp, react/button-has-type */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
Button,
|
||||
StatusAlert,
|
||||
Table,
|
||||
} from '@edx/paragon';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faDownload } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import thunkActions from 'data/thunkActions';
|
||||
import { configuration } from 'config';
|
||||
|
||||
export class BulkManagement extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.fileFormRef = React.createRef();
|
||||
this.fileInputRef = React.createRef();
|
||||
}
|
||||
|
||||
formatHistoryRow = (row) => {
|
||||
const {
|
||||
summaryOfRowsProcessed: {
|
||||
total,
|
||||
successfullyProcessed,
|
||||
failed,
|
||||
skipped,
|
||||
},
|
||||
unique_id: courseId,
|
||||
originalFilename,
|
||||
id,
|
||||
user: username,
|
||||
...rest
|
||||
} = row;
|
||||
const resultsText = [
|
||||
`${total} Students: ${successfullyProcessed} processed`,
|
||||
...(skipped > 0 ? [`${skipped} skipped`] : []),
|
||||
...(failed > 0 ? [`${failed} failed`] : []),
|
||||
].join(', ');
|
||||
const resultsSummary = (
|
||||
<a
|
||||
href={`${configuration.LMS_BASE_URL}/api/bulk_grades/course/${courseId}/?error_id=${id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FontAwesomeIcon icon={faDownload} />
|
||||
{resultsText}
|
||||
</a>
|
||||
);
|
||||
const createWrappedCell = (text) => (<span className="wrap-text-in-cell">{text}</span>);
|
||||
const filename = createWrappedCell(originalFilename);
|
||||
const user = createWrappedCell(username);
|
||||
return {
|
||||
resultsSummary,
|
||||
filename,
|
||||
user,
|
||||
...rest,
|
||||
};
|
||||
};
|
||||
|
||||
handleClickImportGrades = () => {
|
||||
const fileInput = this.fileInputRef.current;
|
||||
if (fileInput) {
|
||||
fileInput.click();
|
||||
}
|
||||
};
|
||||
|
||||
handleFileInputChange = (event) => {
|
||||
const fileInput = event.target;
|
||||
const file = fileInput.files[0];
|
||||
const form = this.fileFormRef.current;
|
||||
if (file && form) {
|
||||
const formData = new FormData(form);
|
||||
this.props.submitFileUploadFormData(formData).then(() => {
|
||||
fileInput.value = null;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h4>Use this feature by downloading a CSV for bulk management,
|
||||
overriding grades locally, and coming back here to upload.
|
||||
</h4>
|
||||
<form ref={this.fileFormRef} action={this.props.gradeExportUrl} method="post">
|
||||
<StatusAlert
|
||||
alertType="danger"
|
||||
dialog={this.props.bulkImportError}
|
||||
open={!!this.props.bulkImportError}
|
||||
dismissible={false}
|
||||
/>
|
||||
<StatusAlert
|
||||
alertType="success"
|
||||
dialog="CSV processing. File uploads may take several minutes to complete"
|
||||
open={this.props.uploadSuccess}
|
||||
dismissible={false}
|
||||
/>
|
||||
<input
|
||||
className="d-none"
|
||||
type="file"
|
||||
name="csv"
|
||||
label="Upload Grade CSV"
|
||||
onChange={this.handleFileInputChange}
|
||||
ref={this.fileInputRef}
|
||||
/>
|
||||
</form>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={this.handleClickImportGrades}
|
||||
>
|
||||
Import Grades
|
||||
</Button>
|
||||
<p>
|
||||
Results appear in the table below.<br />
|
||||
Grade processing may take a few seconds.
|
||||
</p>
|
||||
<Table
|
||||
data={this.props.bulkManagementHistory.map(this.formatHistoryRow)}
|
||||
hasFixedColumnWidths
|
||||
columns={[
|
||||
{
|
||||
key: 'filename',
|
||||
label: 'Gradebook',
|
||||
columnSortable: false,
|
||||
width: 'col-5',
|
||||
},
|
||||
{
|
||||
key: 'resultsSummary',
|
||||
label: 'Download Summary',
|
||||
columnSortable: false,
|
||||
width: 'col',
|
||||
},
|
||||
{
|
||||
key: 'user',
|
||||
label: 'Who',
|
||||
columnSortable: false,
|
||||
width: 'col-1',
|
||||
},
|
||||
{
|
||||
key: 'timeUploaded',
|
||||
label: 'When',
|
||||
columnSortable: false,
|
||||
width: 'col',
|
||||
},
|
||||
]}
|
||||
className="table-striped"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BulkManagement.defaultProps = {
|
||||
bulkImportError: '',
|
||||
bulkManagementHistory: [],
|
||||
uploadSuccess: false,
|
||||
};
|
||||
|
||||
BulkManagement.propTypes = {
|
||||
gradeExportUrl: PropTypes.string.isRequired,
|
||||
|
||||
// redux
|
||||
bulkImportError: PropTypes.string,
|
||||
bulkManagementHistory: PropTypes.arrayOf(PropTypes.shape({
|
||||
originalFilename: PropTypes.string.isRequired,
|
||||
user: PropTypes.string.isRequired,
|
||||
timeUploaded: PropTypes.string.isRequired,
|
||||
summaryOfRowsProcessed: PropTypes.shape({
|
||||
total: PropTypes.number.isRequired,
|
||||
successfullyProcessed: PropTypes.number.isRequired,
|
||||
failed: PropTypes.number.isRequired,
|
||||
skipped: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
})),
|
||||
submitFileUploadFormData: PropTypes.func.isRequired,
|
||||
uploadSuccess: PropTypes.bool,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
bulkImportError: selectors.grades.bulkImportError(state),
|
||||
bulkManagementHistory: selectors.grades.bulkManagementHistoryEntries(state),
|
||||
gradeExportUrl: selectors.root.gradeExportUrl(state),
|
||||
uploadSuccess: selectors.grades.uploadSuccess(state),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
submitFileUploadFormData: thunkActions.grades.submitFileUploadFormData,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BulkManagement);
|
||||
52
src/components/BulkManagementTab/BulkManagementAlerts.jsx
Normal file
52
src/components/BulkManagementTab/BulkManagementAlerts.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
/* eslint-disable react/sort-comp, react/button-has-type */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Alert } from '@edx/paragon';
|
||||
|
||||
import * as appConstants from 'data/constants/app';
|
||||
import selectors from 'data/selectors';
|
||||
|
||||
const { messages: { BulkManagementTab: messages } } = appConstants;
|
||||
|
||||
/**
|
||||
* <BulkManagementAlerts />
|
||||
* Alerts to display at the top of the BulkManagement tab
|
||||
*/
|
||||
export const BulkManagementAlerts = ({ bulkImportError, uploadSuccess }) => (
|
||||
<>
|
||||
<Alert
|
||||
variant="danger"
|
||||
show={!!bulkImportError}
|
||||
dismissible={false}
|
||||
>
|
||||
{bulkImportError}
|
||||
</Alert>
|
||||
<Alert
|
||||
variant="success"
|
||||
show={uploadSuccess}
|
||||
dismissible={false}
|
||||
>
|
||||
{messages.successDialog}
|
||||
</Alert>
|
||||
</>
|
||||
);
|
||||
|
||||
BulkManagementAlerts.defaultProps = {
|
||||
bulkImportError: '',
|
||||
uploadSuccess: false,
|
||||
};
|
||||
|
||||
BulkManagementAlerts.propTypes = {
|
||||
// redux
|
||||
bulkImportError: PropTypes.string,
|
||||
uploadSuccess: PropTypes.bool,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
bulkImportError: selectors.grades.bulkImportError(state),
|
||||
uploadSuccess: selectors.grades.uploadSuccess(state),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(BulkManagementAlerts);
|
||||
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Alert } from '@edx/paragon';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import * as appConstants from 'data/constants/app';
|
||||
|
||||
import { BulkManagementAlerts, mapStateToProps } from './BulkManagementAlerts';
|
||||
|
||||
jest.mock('@edx/paragon', () => ({
|
||||
Alert: () => 'Alert',
|
||||
}));
|
||||
jest.mock('data/selectors', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
grades: {
|
||||
bulkImportError: (state) => ({ bulkImportError: state }),
|
||||
uploadSuccess: (state) => ({ uploadSuccess: state }),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const errorMessage = 'Oh noooooo';
|
||||
|
||||
describe('BulkManagementAlerts', () => {
|
||||
describe('component', () => {
|
||||
let el;
|
||||
describe('no errer, no upload success', () => {
|
||||
beforeEach(() => {
|
||||
el = shallow(<BulkManagementAlerts />);
|
||||
});
|
||||
test('snapshot - bulkImportError closed, success closed', () => {
|
||||
expect(el).toMatchSnapshot();
|
||||
});
|
||||
test('closed danger alert', () => {
|
||||
expect(el.childAt(0).is(Alert)).toEqual(true);
|
||||
expect(el.childAt(0).props().show).toEqual(false);
|
||||
expect(el.childAt(0).props().variant).toEqual('danger');
|
||||
});
|
||||
test('closed success alert', () => {
|
||||
expect(el.childAt(1).is(Alert)).toEqual(true);
|
||||
expect(el.childAt(1).props().show).toEqual(false);
|
||||
expect(el.childAt(1).props().variant).toEqual('success');
|
||||
});
|
||||
});
|
||||
describe('no errer, no upload success', () => {
|
||||
beforeEach(() => {
|
||||
el = shallow(<BulkManagementAlerts uploadSuccess bulkImportError={errorMessage} />);
|
||||
});
|
||||
const assertions = [
|
||||
'danger alert open with bulkImportError',
|
||||
'success alert open with messages.successDialog',
|
||||
];
|
||||
test(`snapshot - ${assertions.join(', ')}`, () => {
|
||||
expect(el).toMatchSnapshot();
|
||||
});
|
||||
test('open danger alert with bulkImportError content', () => {
|
||||
expect(el.childAt(0).is(Alert)).toEqual(true);
|
||||
expect(el.childAt(0).children().text()).toEqual(errorMessage);
|
||||
expect(el.childAt(0).props().show).toEqual(true);
|
||||
});
|
||||
test('open success alert with messages.successDialog content', () => {
|
||||
expect(el.childAt(1).is(Alert)).toEqual(true);
|
||||
expect(el.childAt(1).children().text()).toEqual(
|
||||
appConstants.messages.BulkManagementTab.successDialog,
|
||||
);
|
||||
expect(el.childAt(1).props().show).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('mapStateToProps', () => {
|
||||
let mapped;
|
||||
const testState = { a: 'puppy', named: 'Ember' };
|
||||
beforeEach(() => {
|
||||
mapped = mapStateToProps(testState);
|
||||
});
|
||||
test('bulkImportError from grades.bulkImportError', () => {
|
||||
expect(mapped.bulkImportError).toEqual(selectors.grades.bulkImportError(testState));
|
||||
});
|
||||
test('uploadSuccess from grades.uploadSuccess', () => {
|
||||
expect(mapped.uploadSuccess).toEqual(selectors.grades.uploadSuccess(testState));
|
||||
});
|
||||
});
|
||||
});
|
||||
94
src/components/BulkManagementTab/FileUploadForm.jsx
Normal file
94
src/components/BulkManagementTab/FileUploadForm.jsx
Normal file
@@ -0,0 +1,94 @@
|
||||
/* eslint-disable react/button-has-type, import/no-named-as-default */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import { messages } from 'data/constants/app';
|
||||
import selectors from 'data/selectors';
|
||||
import thunkActions from 'data/thunkActions';
|
||||
|
||||
const { csvUploadLabel, importBtnText } = messages.BulkManagementTab;
|
||||
|
||||
/**
|
||||
* <FileUploadForm />
|
||||
* File-type input wrapped with hidden control such that when a valid file is
|
||||
* added, it is automattically uploaded.
|
||||
*/
|
||||
export class FileUploadForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.fileInputRef = React.createRef();
|
||||
this.handleClickImportGrades = this.handleClickImportGrades.bind(this);
|
||||
this.handleFileInputChange = this.handleFileInputChange.bind(this);
|
||||
}
|
||||
|
||||
get fileInput() {
|
||||
return this.fileInputRef.current;
|
||||
}
|
||||
|
||||
get formData() {
|
||||
const data = new FormData();
|
||||
data.append('csv', this.fileInput.files[0]);
|
||||
return data;
|
||||
}
|
||||
|
||||
get hasFile() {
|
||||
return this.fileInput && this.fileInput.files[0];
|
||||
}
|
||||
|
||||
handleClickImportGrades() {
|
||||
if (this.fileInput) { this.fileInput.click(); }
|
||||
}
|
||||
|
||||
handleFileInputChange() {
|
||||
return this.hasFile && (
|
||||
this.props.submitFileUploadFormData(this.formData).then(
|
||||
() => { this.fileInput.value = null; },
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Form action={this.props.gradeExportUrl} method="post">
|
||||
<FormGroup controlId="csv">
|
||||
<FormControl
|
||||
className="d-none"
|
||||
type="file"
|
||||
label={csvUploadLabel}
|
||||
onChange={this.handleFileInputChange}
|
||||
ref={this.fileInputRef}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
||||
<Button variant="primary" onClick={this.handleClickImportGrades}>
|
||||
{importBtnText}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
FileUploadForm.propTypes = {
|
||||
// redux
|
||||
gradeExportUrl: PropTypes.string.isRequired,
|
||||
submitFileUploadFormData: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
gradeExportUrl: selectors.root.gradeExportUrl(state),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
submitFileUploadFormData: thunkActions.grades.submitFileUploadFormData,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FileUploadForm);
|
||||
188
src/components/BulkManagementTab/FileUploadForm.test.jsx
Normal file
188
src/components/BulkManagementTab/FileUploadForm.test.jsx
Normal file
@@ -0,0 +1,188 @@
|
||||
/* eslint-disable import/no-named-as-default */
|
||||
import React from 'react';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import thunkActions from 'data/thunkActions';
|
||||
import * as appConstants from 'data/constants/app';
|
||||
|
||||
import { FileUploadForm, mapStateToProps, mapDispatchToProps } from './FileUploadForm';
|
||||
|
||||
const {
|
||||
messages: { BulkManagementTab: messages },
|
||||
} = appConstants;
|
||||
|
||||
jest.mock('data/selectors', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
grades: {
|
||||
bulkImportError: jest.fn(state => ({ bulkImportError: state })),
|
||||
},
|
||||
root: {
|
||||
gradeExportUrl: jest.fn(state => ({ gradeExportUrl: state })),
|
||||
},
|
||||
},
|
||||
}));
|
||||
jest.mock('data/thunkActions', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
grades: { submitFileUploadFormData: jest.fn() },
|
||||
},
|
||||
|
||||
}));
|
||||
jest.mock('./BulkManagementAlerts', () => 'BulkManagementAlerts');
|
||||
jest.mock('./ResultsSummary', () => 'ResultsSummary');
|
||||
|
||||
describe('FileUploadForm', () => {
|
||||
describe('component', () => {
|
||||
let props;
|
||||
let el;
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
gradeExportUrl: 'fakeUrl',
|
||||
submitFileUploadFormData: jest.fn(),
|
||||
};
|
||||
});
|
||||
describe('snapshot', () => {
|
||||
const snapshotSegments = [
|
||||
'export form w/ alerts and file input',
|
||||
'import btn',
|
||||
];
|
||||
test(`snapshot - loads ${snapshotSegments.join(', ')}`, () => {
|
||||
jest.mock('@edx/paragon', () => ({
|
||||
Button: () => 'Button',
|
||||
Form: () => 'Form',
|
||||
FormControl: () => 'FormControl',
|
||||
FormGroup: () => 'FormGroup',
|
||||
}));
|
||||
el = shallow(<FileUploadForm {...props} />);
|
||||
el.instance().handleFileInputChange = jest.fn().mockName('this.handleFileInputChange');
|
||||
el.instance().fileInputRef = jest.fn().mockName('this.fileInputRef');
|
||||
el.instance().handleClickImportGrades = jest.fn().mockName('this.handleClickImportGrades');
|
||||
el.instance().formatHistoryRow = jest.fn(entry => entry.originalFilename);
|
||||
expect(el.instance().render()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('render', () => {
|
||||
beforeEach(() => {
|
||||
el = mount(<FileUploadForm {...props} />);
|
||||
});
|
||||
describe('alert form', () => {
|
||||
let form;
|
||||
beforeEach(() => {
|
||||
form = el.find(Form);
|
||||
});
|
||||
test('post action points to gradeExportUrl', () => {
|
||||
expect(form.props().action).toEqual(props.gradeExportUrl);
|
||||
expect(form.props().method).toEqual('post');
|
||||
});
|
||||
describe('file input', () => {
|
||||
let formGroup;
|
||||
beforeEach(() => {
|
||||
formGroup = el.find(FormGroup);
|
||||
});
|
||||
test('group with controlId="csv"', () => {
|
||||
expect(formGroup.props().controlId).toEqual('csv');
|
||||
});
|
||||
test('file control with onChange from handleFileInputChange', () => {
|
||||
const control = el.find(FormControl);
|
||||
expect(
|
||||
control.props().onChange,
|
||||
).toEqual(el.instance().handleFileInputChange);
|
||||
});
|
||||
test('fileInputRef points to control', () => {
|
||||
expect(el.find(FormControl).getElement().ref).toBe(el.instance().fileInputRef);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('import button', () => {
|
||||
let btn;
|
||||
beforeEach(() => {
|
||||
btn = el.find(Button);
|
||||
});
|
||||
test('handleClickImportGrade on click', () => {
|
||||
expect(btn.props().onClick).toEqual(el.instance().handleClickImportGrades);
|
||||
});
|
||||
test('text from messages.importBtn', () => {
|
||||
expect(btn.children().text()).toEqual(messages.importBtnText);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('behavior', () => {
|
||||
let fileInput;
|
||||
beforeEach(() => {
|
||||
el = mount(<FileUploadForm {...props} />);
|
||||
fileInput = jest.spyOn(el.instance(), 'fileInput', 'get');
|
||||
});
|
||||
describe('handleFileInputChange', () => {
|
||||
it('does nothing (does not fail) if fileInput has not loaded', () => {
|
||||
fileInput.mockReturnValue(null);
|
||||
el.instance().handleClickImportGrades();
|
||||
});
|
||||
it('calls fileInput.click if is loaded', () => {
|
||||
const click = jest.fn();
|
||||
fileInput.mockReturnValue({ click });
|
||||
el.instance().handleClickImportGrades();
|
||||
expect(click).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('handleClickImportGrades', () => {
|
||||
it('does nothing if file input has not loaded with files', () => {
|
||||
fileInput.mockReturnValue(null);
|
||||
el.instance().handleFileInputChange();
|
||||
expect(props.submitFileUploadFormData).not.toHaveBeenCalled();
|
||||
fileInput.mockReturnValue({ files: [] });
|
||||
el.instance().handleFileInputChange();
|
||||
expect(props.submitFileUploadFormData).not.toHaveBeenCalled();
|
||||
});
|
||||
it('calls submitFileUploadFormData and then clears fileInput if has files', () => {
|
||||
fileInput.mockReturnValue({ files: ['some', 'files'], value: 'a value' });
|
||||
const formData = { fake: 'form data' };
|
||||
jest.spyOn(el.instance(), 'formData', 'get').mockReturnValue(formData);
|
||||
const submit = jest.fn(() => ({ then: (thenCB) => { thenCB(); } }));
|
||||
el.setProps({
|
||||
submitFileUploadFormData: submit,
|
||||
});
|
||||
el.instance().handleFileInputChange();
|
||||
expect(submit).toHaveBeenCalledWith(formData);
|
||||
expect(el.instance().fileInput.value).toEqual(null);
|
||||
});
|
||||
});
|
||||
describe('formData', () => {
|
||||
test('returns FormData object with csv value from fileInput.files[0]', () => {
|
||||
const file = { a: 'fake file' };
|
||||
const value = 'aValue';
|
||||
fileInput.mockReturnValue({ files: [file], value });
|
||||
const expected = new FormData();
|
||||
expected.append('csv', file);
|
||||
expect([...el.instance().formData.entries()]).toEqual([...expected.entries()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapStateToProps', () => {
|
||||
const testState = { a: 'simple', test: 'state' };
|
||||
let mapped;
|
||||
beforeEach(() => {
|
||||
mapped = mapStateToProps(testState);
|
||||
});
|
||||
test('gradeExportUrl from root.gradeExportUrl', () => {
|
||||
expect(mapped.gradeExportUrl).toEqual(selectors.root.gradeExportUrl(testState));
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapDispatchToProps', () => {
|
||||
test('submitFileUploadFormData from thunkActions.grades', () => {
|
||||
expect(
|
||||
mapDispatchToProps.submitFileUploadFormData,
|
||||
).toEqual(thunkActions.grades.submitFileUploadFormData);
|
||||
});
|
||||
});
|
||||
});
|
||||
68
src/components/BulkManagementTab/HistoryTable.jsx
Normal file
68
src/components/BulkManagementTab/HistoryTable.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
/* eslint-disable react/button-has-type, import/no-named-as-default */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Table } from '@edx/paragon';
|
||||
|
||||
import { bulkManagementColumns, messages } from 'data/constants/app';
|
||||
import selectors from 'data/selectors';
|
||||
import ResultsSummary from './ResultsSummary';
|
||||
|
||||
export const mapHistoryRows = ({
|
||||
resultsSummary,
|
||||
originalFilename,
|
||||
user,
|
||||
...rest
|
||||
}) => ({
|
||||
resultsSummary: (<ResultsSummary {...resultsSummary} />),
|
||||
filename: (<span className="wrap-text-in-cell">{originalFilename}</span>),
|
||||
user: (<span className="wrap-text-in-cell">{user}</span>),
|
||||
...rest,
|
||||
});
|
||||
|
||||
const { hints } = messages.BulkManagementTab;
|
||||
|
||||
/**
|
||||
* <HistoryTable />
|
||||
* Table with history of bulk management uploads, including a results summary which
|
||||
* displays total, skipped, and failed uploads
|
||||
*/
|
||||
export const HistoryTable = ({ bulkManagementHistory }) => (
|
||||
<>
|
||||
<p>
|
||||
{hints[0]}
|
||||
<br />
|
||||
{hints[1]}
|
||||
</p>
|
||||
|
||||
<Table
|
||||
data={bulkManagementHistory.map(mapHistoryRows)}
|
||||
hasFixedColumnWidths
|
||||
columns={bulkManagementColumns}
|
||||
className="table-striped"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
HistoryTable.defaultProps = {
|
||||
bulkManagementHistory: [],
|
||||
};
|
||||
HistoryTable.propTypes = {
|
||||
// redux
|
||||
bulkManagementHistory: PropTypes.arrayOf(PropTypes.shape({
|
||||
originalFilename: PropTypes.string.isRequired,
|
||||
user: PropTypes.string.isRequired,
|
||||
timeUploaded: PropTypes.string.isRequired,
|
||||
resultsSummary: PropTypes.shape({
|
||||
rowId: PropTypes.number.isRequired,
|
||||
courseId: PropTypes.string.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
}),
|
||||
})),
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
bulkManagementHistory: selectors.grades.bulkManagementHistoryEntries(state),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(HistoryTable);
|
||||
115
src/components/BulkManagementTab/HistoryTable.test.jsx
Normal file
115
src/components/BulkManagementTab/HistoryTable.test.jsx
Normal file
@@ -0,0 +1,115 @@
|
||||
/* eslint-disable import/no-named-as-default */
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Table } from '@edx/paragon';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import { bulkManagementColumns, messages } from 'data/constants/app';
|
||||
|
||||
import ResultsSummary from './ResultsSummary';
|
||||
import { HistoryTable, mapStateToProps } from './HistoryTable';
|
||||
|
||||
jest.mock('@edx/paragon', () => ({
|
||||
Table: () => 'Table',
|
||||
}));
|
||||
jest.mock('data/selectors', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
grades: {
|
||||
bulkManagementHistoryEntries: jest.fn(state => ({ historyEntries: state })),
|
||||
},
|
||||
},
|
||||
}));
|
||||
jest.mock('./ResultsSummary', () => 'ResultsSummary');
|
||||
|
||||
describe('HistoryTable', () => {
|
||||
describe('component', () => {
|
||||
const entry1 = {
|
||||
originalFilename: 'blue.png',
|
||||
user: 'Eifel',
|
||||
timeUploaded: '65',
|
||||
resultsSummary: {
|
||||
rowId: 12,
|
||||
courseId: 'Da Bu Dee',
|
||||
text: 'Da ba daa',
|
||||
},
|
||||
};
|
||||
const entry2 = {
|
||||
originalFilename: 'allStar.jpg',
|
||||
user: 'Smashmouth',
|
||||
timeUploaded: '2000s?',
|
||||
resultsSummary: {
|
||||
courseId: 'rockstar',
|
||||
rowId: 2,
|
||||
text: 'all that glitters is gold',
|
||||
},
|
||||
};
|
||||
const props = {
|
||||
bulkManagementHistory: [entry1, entry2],
|
||||
};
|
||||
let el;
|
||||
describe('snapshot', () => {
|
||||
beforeEach(() => {
|
||||
el = shallow(<HistoryTable {...props} />);
|
||||
});
|
||||
const snapshotSegments = [
|
||||
'hints display',
|
||||
'formatted table',
|
||||
];
|
||||
test(`snapshot - loads ${snapshotSegments.join(', ')}`, () => {
|
||||
expect(el).toMatchSnapshot();
|
||||
});
|
||||
test('hints with break in between', () => {
|
||||
const hints = el.find('p');
|
||||
expect(hints.childAt(0).text()).toEqual(messages.BulkManagementTab.hints[0]);
|
||||
expect(hints.childAt(1).is('br')).toEqual(true);
|
||||
expect(hints.childAt(2).text()).toEqual(messages.BulkManagementTab.hints[1]);
|
||||
});
|
||||
describe('history table', () => {
|
||||
let table;
|
||||
beforeEach(() => {
|
||||
table = el.find(Table);
|
||||
});
|
||||
describe('data (from bulkManagementHistory.map(this.formatHistoryRow)', () => {
|
||||
const fieldAssertions = [
|
||||
'maps resultsSummay to ResultsSummary',
|
||||
'wraps filename and user',
|
||||
'forwards the rest',
|
||||
];
|
||||
test(`snapshot: ${fieldAssertions.join(', ')}`, () => {
|
||||
expect(table.props().data).toMatchSnapshot();
|
||||
});
|
||||
test(fieldAssertions.join(', '), () => {
|
||||
const rows = table.props().data;
|
||||
expect(rows[0].resultsSummary).toEqual(<ResultsSummary {...entry1.resultsSummary} />);
|
||||
expect(rows[0].user).toEqual(<span className="wrap-text-in-cell">{entry1.user}</span>);
|
||||
expect(
|
||||
rows[0].filename,
|
||||
).toEqual(<span className="wrap-text-in-cell">{entry1.originalFilename}</span>);
|
||||
expect(rows[1].resultsSummary).toEqual(<ResultsSummary {...entry2.resultsSummary} />);
|
||||
expect(rows[1].user).toEqual(<span className="wrap-text-in-cell">{entry2.user}</span>);
|
||||
expect(
|
||||
rows[1].filename,
|
||||
).toEqual(<span className="wrap-text-in-cell">{entry2.originalFilename}</span>);
|
||||
});
|
||||
});
|
||||
test('columns from bulkManagementColumns', () => {
|
||||
expect(table.props().columns).toEqual(bulkManagementColumns);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapStateToProps', () => {
|
||||
const testState = { a: 'simple', test: 'state' };
|
||||
let mapped;
|
||||
beforeEach(() => {
|
||||
mapped = mapStateToProps(testState);
|
||||
});
|
||||
test('bulkManagementHistory from grades.bulkManagementHistoryEntries', () => {
|
||||
expect(
|
||||
mapped.bulkManagementHistory,
|
||||
).toEqual(selectors.grades.bulkManagementHistoryEntries(testState));
|
||||
});
|
||||
});
|
||||
});
|
||||
40
src/components/BulkManagementTab/ResultsSummary.jsx
Normal file
40
src/components/BulkManagementTab/ResultsSummary.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable react/sort-comp, react/button-has-type */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Hyperlink, Icon } from '@edx/paragon';
|
||||
import { Download } from '@edx/paragon/icons';
|
||||
|
||||
import { bulkGradesUrlByCourseAndRow } from 'data/constants/api';
|
||||
|
||||
/**
|
||||
* <ResultsSummary {...{ courseId, rowId, text }} />
|
||||
* displays a result summary cell for a single bulk management upgrade history entry.
|
||||
* @param {string} courseId - course identifier
|
||||
* @param {number} rowId - row/error identifier
|
||||
* @param {string} text - summary string
|
||||
*/
|
||||
const ResultsSummary = ({
|
||||
courseId,
|
||||
rowId,
|
||||
text,
|
||||
}) => (
|
||||
<Hyperlink
|
||||
href={bulkGradesUrlByCourseAndRow(courseId, rowId)}
|
||||
destination="www.edx.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
showLaunchIcon={false}
|
||||
>
|
||||
<Icon src={Download} className="d-inline-block" />
|
||||
{text}
|
||||
</Hyperlink>
|
||||
);
|
||||
|
||||
ResultsSummary.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
rowId: PropTypes.number.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ResultsSummary;
|
||||
52
src/components/BulkManagementTab/ResultsSummary.test.jsx
Normal file
52
src/components/BulkManagementTab/ResultsSummary.test.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { Icon } from '@edx/paragon';
|
||||
import { Download } from '@edx/paragon/icons';
|
||||
|
||||
import * as api from 'data/constants/api';
|
||||
import ResultsSummary from './ResultsSummary';
|
||||
|
||||
jest.mock('@edx/paragon', () => ({
|
||||
Hyperlink: () => 'Hyperlink',
|
||||
Icon: () => 'Icon',
|
||||
}));
|
||||
jest.mock('@edx/paragon/icons', () => ({
|
||||
Download: 'DownloadIcon',
|
||||
}));
|
||||
jest.mock('data/constants/api', () => ({
|
||||
bulkGradesUrlByCourseAndRow: jest.fn((courseId, rowId) => ({ url: { courseId, rowId } })),
|
||||
}));
|
||||
|
||||
describe('ResultsSummary component', () => {
|
||||
const props = {
|
||||
courseId: 'classy',
|
||||
rowId: 42,
|
||||
text: 'texty',
|
||||
};
|
||||
let el;
|
||||
const assertions = [
|
||||
'safe hyperlink with bulkGradesUrl with course and row id',
|
||||
'download icon',
|
||||
'results text',
|
||||
];
|
||||
beforeEach(() => {
|
||||
el = shallow(<ResultsSummary {...props} />);
|
||||
});
|
||||
test(`snapshot - ${assertions.join(', ')}`, () => {
|
||||
expect(el).toMatchSnapshot();
|
||||
});
|
||||
test('Hyperlink has target="_blank" and rel="noopener noreferrer"', () => {
|
||||
expect(el.props().target).toEqual('_blank');
|
||||
expect(el.props().rel).toEqual('noopener noreferrer');
|
||||
});
|
||||
test('Hyperlink has href to bulkGradesUrl', () => {
|
||||
expect(el.props().href).toEqual(api.bulkGradesUrlByCourseAndRow(props.courseId, props.rowId));
|
||||
});
|
||||
test('displays Download Icon and text', () => {
|
||||
const icon = el.childAt(0);
|
||||
expect(icon.is(Icon)).toEqual(true);
|
||||
expect(icon.props().src).toEqual(Download);
|
||||
expect(el.childAt(1).text()).toEqual(props.text);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`BulkManagementAlerts component no errer, no upload success snapshot - bulkImportError closed, success closed 1`] = `
|
||||
<Fragment>
|
||||
<Alert
|
||||
dismissible={false}
|
||||
show={false}
|
||||
variant="danger"
|
||||
/>
|
||||
<Alert
|
||||
dismissible={false}
|
||||
show={false}
|
||||
variant="success"
|
||||
>
|
||||
CSV processing. File uploads may take several minutes to complete.
|
||||
</Alert>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`BulkManagementAlerts component no errer, no upload success snapshot - danger alert open with bulkImportError, success alert open with messages.successDialog 1`] = `
|
||||
<Fragment>
|
||||
<Alert
|
||||
dismissible={false}
|
||||
show={true}
|
||||
variant="danger"
|
||||
>
|
||||
Oh noooooo
|
||||
</Alert>
|
||||
<Alert
|
||||
dismissible={false}
|
||||
show={true}
|
||||
variant="success"
|
||||
>
|
||||
CSV processing. File uploads may take several minutes to complete.
|
||||
</Alert>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -0,0 +1,35 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FileUploadForm component snapshot snapshot - loads export form w/ alerts and file input, import btn 1`] = `
|
||||
<React.Fragment>
|
||||
<Form
|
||||
action="fakeUrl"
|
||||
inline={false}
|
||||
method="post"
|
||||
>
|
||||
<FormGroup
|
||||
as="div"
|
||||
controlId="csv"
|
||||
isInvalid={false}
|
||||
isValid={false}
|
||||
>
|
||||
<ForwardRef
|
||||
as="input"
|
||||
className="d-none"
|
||||
label="Upload Grade CSV"
|
||||
onChange={[MockFunction this.handleFileInputChange]}
|
||||
plaintext={false}
|
||||
type="file"
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
<ForwardRef
|
||||
active={false}
|
||||
disabled={false}
|
||||
onClick={[MockFunction this.handleClickImportGrades]}
|
||||
variant="primary"
|
||||
>
|
||||
Import Grades
|
||||
</ForwardRef>
|
||||
</React.Fragment>
|
||||
`;
|
||||
@@ -0,0 +1,124 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HistoryTable component snapshot history table data (from bulkManagementHistory.map(this.formatHistoryRow) snapshot: maps resultsSummay to ResultsSummary, wraps filename and user, forwards the rest 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"filename": <span
|
||||
className="wrap-text-in-cell"
|
||||
>
|
||||
blue.png
|
||||
</span>,
|
||||
"resultsSummary": <ResultsSummary
|
||||
courseId="Da Bu Dee"
|
||||
rowId={12}
|
||||
text="Da ba daa"
|
||||
/>,
|
||||
"timeUploaded": "65",
|
||||
"user": <span
|
||||
className="wrap-text-in-cell"
|
||||
>
|
||||
Eifel
|
||||
</span>,
|
||||
},
|
||||
Object {
|
||||
"filename": <span
|
||||
className="wrap-text-in-cell"
|
||||
>
|
||||
allStar.jpg
|
||||
</span>,
|
||||
"resultsSummary": <ResultsSummary
|
||||
courseId="rockstar"
|
||||
rowId={2}
|
||||
text="all that glitters is gold"
|
||||
/>,
|
||||
"timeUploaded": "2000s?",
|
||||
"user": <span
|
||||
className="wrap-text-in-cell"
|
||||
>
|
||||
Smashmouth
|
||||
</span>,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`HistoryTable component snapshot snapshot - loads hints display, formatted table 1`] = `
|
||||
<Fragment>
|
||||
<p>
|
||||
Results appear in the table below.
|
||||
<br />
|
||||
Grade processing may take a few seconds.
|
||||
</p>
|
||||
<Table
|
||||
className="table-striped"
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"columnSortable": false,
|
||||
"key": "filename",
|
||||
"label": "Gradebook",
|
||||
"width": "col-5",
|
||||
},
|
||||
Object {
|
||||
"columnSortable": false,
|
||||
"key": "resultsSummary",
|
||||
"label": "Download Summary",
|
||||
"width": "col",
|
||||
},
|
||||
Object {
|
||||
"columnSortable": false,
|
||||
"key": "user",
|
||||
"label": "Who",
|
||||
"width": "col-1",
|
||||
},
|
||||
Object {
|
||||
"columnSortable": false,
|
||||
"key": "timeUploaded",
|
||||
"label": "When",
|
||||
"width": "col",
|
||||
},
|
||||
]
|
||||
}
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"filename": <span
|
||||
className="wrap-text-in-cell"
|
||||
>
|
||||
blue.png
|
||||
</span>,
|
||||
"resultsSummary": <ResultsSummary
|
||||
courseId="Da Bu Dee"
|
||||
rowId={12}
|
||||
text="Da ba daa"
|
||||
/>,
|
||||
"timeUploaded": "65",
|
||||
"user": <span
|
||||
className="wrap-text-in-cell"
|
||||
>
|
||||
Eifel
|
||||
</span>,
|
||||
},
|
||||
Object {
|
||||
"filename": <span
|
||||
className="wrap-text-in-cell"
|
||||
>
|
||||
allStar.jpg
|
||||
</span>,
|
||||
"resultsSummary": <ResultsSummary
|
||||
courseId="rockstar"
|
||||
rowId={2}
|
||||
text="all that glitters is gold"
|
||||
/>,
|
||||
"timeUploaded": "2000s?",
|
||||
"user": <span
|
||||
className="wrap-text-in-cell"
|
||||
>
|
||||
Smashmouth
|
||||
</span>,
|
||||
},
|
||||
]
|
||||
}
|
||||
hasFixedColumnWidths={true}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -0,0 +1,24 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ResultsSummary component snapshot - safe hyperlink with bulkGradesUrl with course and row id, download icon, results text 1`] = `
|
||||
<Hyperlink
|
||||
destination="www.edx.org"
|
||||
href={
|
||||
Object {
|
||||
"url": Object {
|
||||
"courseId": "classy",
|
||||
"rowId": 42,
|
||||
},
|
||||
}
|
||||
}
|
||||
rel="noopener noreferrer"
|
||||
showLaunchIcon={false}
|
||||
target="_blank"
|
||||
>
|
||||
<Icon
|
||||
className="d-inline-block"
|
||||
src="DownloadIcon"
|
||||
/>
|
||||
texty
|
||||
</Hyperlink>
|
||||
`;
|
||||
12
src/components/BulkManagementTab/__snapshots__/test.jsx.snap
Normal file
12
src/components/BulkManagementTab/__snapshots__/test.jsx.snap
Normal file
@@ -0,0 +1,12 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`BulkManagementTab component snapshot snapshot - loads heading from messages.BulkManagementTab.heading, <BulkManagementAlerts />, <FileUploadForm />, <HistoryTable /> 1`] = `
|
||||
<div>
|
||||
<h4>
|
||||
Use this feature by downloading a CSV for bulk management, overriding grades locally, and coming back here to upload.
|
||||
</h4>
|
||||
<BulkManagementAlerts />
|
||||
<FileUploadForm />
|
||||
<HistoryTable />
|
||||
</div>
|
||||
`;
|
||||
22
src/components/BulkManagementTab/index.jsx
Normal file
22
src/components/BulkManagementTab/index.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
/* eslint-disable react/button-has-type, import/no-named-as-default */
|
||||
import React from 'react';
|
||||
|
||||
import { messages } from 'data/constants/app';
|
||||
import BulkManagementAlerts from './BulkManagementAlerts';
|
||||
import FileUploadForm from './FileUploadForm';
|
||||
import HistoryTable from './HistoryTable';
|
||||
|
||||
/**
|
||||
* <BulkManagementTab />
|
||||
* top-level view for managing uploads of bulk management override csvs.
|
||||
*/
|
||||
export const BulkManagementTab = () => (
|
||||
<div>
|
||||
<h4>{messages.BulkManagementTab.heading}</h4>
|
||||
<BulkManagementAlerts />
|
||||
<FileUploadForm />
|
||||
<HistoryTable />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default BulkManagementTab;
|
||||
43
src/components/BulkManagementTab/test.jsx
Normal file
43
src/components/BulkManagementTab/test.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
/* eslint-disable import/no-named-as-default */
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { messages } from 'data/constants/app';
|
||||
import { BulkManagementTab } from '.';
|
||||
import BulkManagementAlerts from './BulkManagementAlerts';
|
||||
import FileUploadForm from './FileUploadForm';
|
||||
import HistoryTable from './HistoryTable';
|
||||
|
||||
jest.mock('./BulkManagementAlerts', () => 'BulkManagementAlerts');
|
||||
jest.mock('./FileUploadForm', () => 'FileUploadForm');
|
||||
jest.mock('./HistoryTable', () => 'HistoryTable');
|
||||
|
||||
describe('BulkManagementTab', () => {
|
||||
describe('component', () => {
|
||||
let el;
|
||||
beforeEach(() => {
|
||||
el = shallow(<BulkManagementTab />);
|
||||
});
|
||||
describe('snapshot', () => {
|
||||
const snapshotSegments = [
|
||||
'heading from messages.BulkManagementTab.heading',
|
||||
'<BulkManagementAlerts />',
|
||||
'<FileUploadForm />',
|
||||
'<HistoryTable />',
|
||||
];
|
||||
test(`snapshot - loads ${snapshotSegments.join(', ')}`, () => {
|
||||
expect(el).toMatchSnapshot();
|
||||
});
|
||||
test('heading - h4 loaded from messages', () => {
|
||||
const heading = el.find('h4');
|
||||
expect(heading.text()).toEqual(messages.BulkManagementTab.heading);
|
||||
});
|
||||
test('heading, then alerts, then upload form, then table', () => {
|
||||
expect(el.childAt(0).is('h4')).toEqual(true);
|
||||
expect(el.childAt(1).is(BulkManagementAlerts)).toEqual(true);
|
||||
expect(el.childAt(2).is(FileUploadForm)).toEqual(true);
|
||||
expect(el.childAt(3).is(HistoryTable)).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,7 @@ exports[`AssignmentGradeFilter Component snapshots buttons and groups disabled i
|
||||
<div
|
||||
className="grade-filter-action"
|
||||
>
|
||||
<Button
|
||||
<ForwardRef
|
||||
active={false}
|
||||
disabled={true}
|
||||
name="assignmentGradeMinMax"
|
||||
@@ -30,7 +30,7 @@ exports[`AssignmentGradeFilter Component snapshots buttons and groups disabled i
|
||||
variant="outline-secondary"
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
</ForwardRef>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -56,7 +56,7 @@ exports[`AssignmentGradeFilter Component snapshots smoke test 1`] = `
|
||||
<div
|
||||
className="grade-filter-action"
|
||||
>
|
||||
<Button
|
||||
<ForwardRef
|
||||
active={false}
|
||||
disabled={false}
|
||||
name="assignmentGradeMinMax"
|
||||
@@ -65,7 +65,7 @@ exports[`AssignmentGradeFilter Component snapshots smoke test 1`] = `
|
||||
variant="outline-secondary"
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
</ForwardRef>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -14,7 +14,7 @@ import GradebookHeader from 'components/GradebookHeader';
|
||||
import GradesTab from 'components/GradesTab';
|
||||
import GradebookFilters from 'components/GradebookFilters';
|
||||
import GradebookFiltersHeader from 'components/GradebookFiltersHeader';
|
||||
import BulkManagement from 'components/BulkManagement';
|
||||
import BulkManagementTab from 'components/BulkManagementTab';
|
||||
|
||||
export class GradebookPage extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -36,6 +36,7 @@ export class GradebookPage extends React.Component {
|
||||
delete parsed[key];
|
||||
}
|
||||
});
|
||||
this.props.history.push(`?${queryString.stringify(parsed)}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -52,7 +53,7 @@ export class GradebookPage extends React.Component {
|
||||
</Tab>
|
||||
{this.props.showBulkManagement && (
|
||||
<Tab eventKey="bulk_management" title="Bulk Management">
|
||||
<BulkManagement />
|
||||
<BulkManagementTab />
|
||||
</Tab>
|
||||
)}
|
||||
</Tabs>
|
||||
@@ -63,12 +64,16 @@ export class GradebookPage extends React.Component {
|
||||
}
|
||||
GradebookPage.defaultProps = {
|
||||
location: { search: '' },
|
||||
showBulkManagement: false,
|
||||
};
|
||||
GradebookPage.propTypes = {
|
||||
history: PropTypes.shape({
|
||||
push: PropTypes.func,
|
||||
}).isRequired,
|
||||
location: PropTypes.shape({ search: PropTypes.string }),
|
||||
courseId: PropTypes.string.isRequired,
|
||||
initializeApp: PropTypes.func.isRequired,
|
||||
showBulkManagement: PropTypes.bool.isRequired,
|
||||
showBulkManagement: PropTypes.bool,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state, ownProps) => ({
|
||||
|
||||
14
src/data/constants/api.js
Normal file
14
src/data/constants/api.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { configuration } from 'config';
|
||||
|
||||
export const baseUrl = `${configuration.LMS_BASE_URL}/api`;
|
||||
|
||||
/**
|
||||
* bulkGradesUrlByCourseAndRow(courseId, rowId)
|
||||
* returns the bulkGrades url with the given courseId and rowId.
|
||||
* @param {string} courseId - course identifier
|
||||
* @param {string} rowId - row/error identifier
|
||||
* @return {string} - bulk grades fetch url
|
||||
*/
|
||||
export const bulkGradesUrlByCourseAndRow = (courseId, rowId) => (
|
||||
`${baseUrl}/bulkGrades/course/${courseId}/?error_id=${rowId}`
|
||||
);
|
||||
@@ -18,3 +18,51 @@ export const localFilterKeys = StrictDict({
|
||||
courseGradeMax: 'courseGradeMax',
|
||||
courseGradeMin: 'courseGradeMin',
|
||||
});
|
||||
|
||||
/**
|
||||
* column configuration for bulk management tab's data table
|
||||
*/
|
||||
export const bulkManagementColumns = [
|
||||
{
|
||||
key: 'filename',
|
||||
label: 'Gradebook',
|
||||
columnSortable: false,
|
||||
width: 'col-5',
|
||||
},
|
||||
{
|
||||
key: 'resultsSummary',
|
||||
label: 'Download Summary',
|
||||
columnSortable: false,
|
||||
width: 'col',
|
||||
},
|
||||
{
|
||||
key: 'user',
|
||||
label: 'Who',
|
||||
columnSortable: false,
|
||||
width: 'col-1',
|
||||
},
|
||||
{
|
||||
key: 'timeUploaded',
|
||||
label: 'When',
|
||||
columnSortable: false,
|
||||
width: 'col',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Display strings for various app components.
|
||||
* Note: this is a temporary storage location for these strings, before we put them in
|
||||
* messages files for translation.
|
||||
*/
|
||||
export const messages = StrictDict({
|
||||
BulkManagementTab: StrictDict({
|
||||
csvUploadLabel: 'Upload Grade CSV',
|
||||
heading: 'Use this feature by downloading a CSV for bulk management, overriding grades locally, and coming back here to upload.',
|
||||
importBtnText: 'Import Grades',
|
||||
successDialog: 'CSV processing. File uploads may take several minutes to complete.',
|
||||
hints: [
|
||||
'Results appear in the table below.',
|
||||
'Grade processing may take a few seconds.',
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -6,16 +6,23 @@ import { formatDateForDisplay } from 'data/actions/utils';
|
||||
import simpleSelectorFactory from '../utils';
|
||||
import * as module from './grades';
|
||||
|
||||
/**
|
||||
* getRowsProcessed(historyEntryData)
|
||||
* @param {object} historyEntryData - the data param from a bulk management history entry
|
||||
* @return {string} - user-facing summary of total/skipped/failed rows.
|
||||
*/
|
||||
export const getRowsProcessed = ({
|
||||
processed_rows: processed,
|
||||
saved_rows: saved,
|
||||
total_rows: total,
|
||||
}) => ({
|
||||
total,
|
||||
successfullyProcessed: saved,
|
||||
failed: processed - saved,
|
||||
skipped: total - processed,
|
||||
});
|
||||
}) => {
|
||||
const failed = processed - saved;
|
||||
const skipped = total - processed;
|
||||
const summaryEntries = [`${total} Students: ${saved} processed`];
|
||||
if (skipped > 0) { summaryEntries.push(`${skipped} skipped`); }
|
||||
if (failed > 0) { summaryEntries.push(`${failed} failed`); }
|
||||
return summaryEntries.join(', ');
|
||||
};
|
||||
|
||||
/**
|
||||
* formatGradeOverrideForDisplay(historyArray)
|
||||
@@ -112,17 +119,23 @@ export const headingMapper = (category, label = 'All') => {
|
||||
* Takes a raw bulkManagementHistory entry and formats it for consumption
|
||||
* @param {object} rawEntry - raw history entry to transform
|
||||
* @return {object} - transformed history entry
|
||||
* ({ timeUploaded, originalFilename, summaryOfRowsProcessed, ... })
|
||||
* ({ timeUploaded, originalFilename, resultsSummary, ... })
|
||||
*/
|
||||
export const transformHistoryEntry = ({
|
||||
modified,
|
||||
original_filename: originalFilename,
|
||||
data,
|
||||
unique_id: courseId,
|
||||
id,
|
||||
...rest
|
||||
}) => ({
|
||||
timeUploaded: formatDateForDisplay(new Date(modified)),
|
||||
originalFilename,
|
||||
summaryOfRowsProcessed: module.getRowsProcessed(data),
|
||||
resultsSummary: {
|
||||
rowId: id,
|
||||
courseId,
|
||||
text: module.getRowsProcessed(data),
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
|
||||
|
||||
@@ -41,16 +41,46 @@ const genericResultsRows = [
|
||||
describe('grades selectors', () => {
|
||||
// Transformers
|
||||
describe('getRowsProcessed', () => {
|
||||
const data = {
|
||||
processed_rows: 20,
|
||||
saved_rows: 10,
|
||||
total_rows: 50,
|
||||
};
|
||||
expect(selectors.getRowsProcessed(data)).toEqual({
|
||||
total: data.total_rows,
|
||||
successfullyProcessed: data.saved_rows,
|
||||
failed: data.processed_rows - data.saved_rows,
|
||||
skipped: data.total_rows - data.processed_rows,
|
||||
test('returns appropriate breakdown of processed rows given some saved and skipped', () => {
|
||||
const data = {
|
||||
processed_rows: 20,
|
||||
saved_rows: 10,
|
||||
total_rows: 50,
|
||||
};
|
||||
const failed = data.processed_rows - data.saved_rows;
|
||||
const skipped = data.total_rows - data.processed_rows;
|
||||
const expected = [
|
||||
`${data.total_rows} Students: ${data.saved_rows} processed`,
|
||||
`${skipped} skipped`,
|
||||
`${failed} failed`,
|
||||
].join(', ');
|
||||
expect(selectors.getRowsProcessed(data)).toEqual(expected);
|
||||
});
|
||||
test('excludes skipped if all rows were processed', () => {
|
||||
const data = {
|
||||
processed_rows: 50,
|
||||
saved_rows: 20,
|
||||
total_rows: 50,
|
||||
};
|
||||
const failed = data.processed_rows - data.saved_rows;
|
||||
const expected = [
|
||||
`${data.total_rows} Students: ${data.saved_rows} processed`,
|
||||
`${failed} failed`,
|
||||
].join(', ');
|
||||
expect(selectors.getRowsProcessed(data)).toEqual(expected);
|
||||
});
|
||||
test('excludes failed if all processed rows were saved', () => {
|
||||
const data = {
|
||||
processed_rows: 20,
|
||||
saved_rows: 20,
|
||||
total_rows: 50,
|
||||
};
|
||||
const skipped = data.total_rows - data.processed_rows;
|
||||
const expected = [
|
||||
`${data.total_rows} Students: ${data.saved_rows} processed`,
|
||||
`${skipped} skipped`,
|
||||
].join(', ');
|
||||
expect(selectors.getRowsProcessed(data)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -147,6 +177,8 @@ describe('grades selectors', () => {
|
||||
const rawEntry = {
|
||||
modified: 'Jan 10 2021',
|
||||
original_filename: 'fileName',
|
||||
unique_id: 'I exist',
|
||||
id: 'a row/entry id, I guess',
|
||||
data: { some: 'data' },
|
||||
also: 'some',
|
||||
other: 'fields',
|
||||
@@ -166,7 +198,11 @@ describe('grades selectors', () => {
|
||||
expect(output.originalFilename).toEqual(rawEntry.original_filename);
|
||||
});
|
||||
it('summarizes processed rows', () => {
|
||||
expect(output.summaryOfRowsProcessed).toEqual(selectors.getRowsProcessed(rawEntry.data));
|
||||
expect(output.resultsSummary).toEqual({
|
||||
text: selectors.getRowsProcessed(rawEntry.data),
|
||||
courseId: rawEntry.unique_id,
|
||||
rowId: rawEntry.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -100,10 +100,14 @@ const store = createStore(
|
||||
composeWithDevTools(applyMiddleware(...middleware)),
|
||||
);
|
||||
|
||||
// TODO debug only
|
||||
window.store = store;
|
||||
window.actions = actions;
|
||||
window.selectors = selectors;
|
||||
/**
|
||||
* Dev tools for redux work
|
||||
*/
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
window.store = store;
|
||||
window.actions = actions;
|
||||
window.selectors = selectors;
|
||||
}
|
||||
|
||||
export { trackingCategory };
|
||||
export default store;
|
||||
|
||||
Reference in New Issue
Block a user