Compare commits

...

8 Commits

Author SHA1 Message Date
Nathan Sprenkle
650e9321b1 Simplify Bulk Management Config (#200)
* refactor: simplify bulk management enabling

Formerly, a course had to have bulk management enabled and have a master's
track. This painted us into a corner where we had to create a
workaround for enabling bulk management on non-master's track courses.
Instead, this relies only on an enabled flag from edx-platform (based on
a course waffle flag) which simplifies the enabling code here.

* feat: remove unneeded bulk management allow-list
2021-08-04 11:04:55 -04:00
Ben Warzeski
e8a8cca483 fix: add excludedCourseRoles to bulk-grades fetch (#209) 2021-08-04 09:46:51 -04:00
Ben Warzeski
f5d2a34660 Fix cohort url (#208)
* fix cohorts fetch url

* v1.4.45
2021-08-03 15:37:12 -04:00
Ben Warzeski
97d3a29a7f fix: add track back into lms api service calls. (#207)
* fix: add track back into lms api service calls.

* fix tests

* v1.4.44
2021-07-30 11:59:01 -04:00
Leangseu Kim
5b8f67e8d2 fix: update csv import error messages
update csv error according discussed

Ticket[au-66]

version bump
2021-07-26 12:59:42 -04:00
Ben Warzeski
c6e33307ba refactor: add transifex support to user-facing messages (#203)
* clean up and test segment integration

* add transifex config

* move user-facing messages into messages files and translate in usage

* lint cleanup

* fix introduced typos

* remove dead code

* remove should-be-ignored temp translation files

* make HistoryHeader use node-type to support translations

* fix apostrophe

* fix snapshot

* v1.4.42
2021-07-22 10:45:18 -04:00
Ben Warzeski
a4df8f7238 refactor: update test coverage (#202)
* clean up and test segment integration

* add tests for action utils

* add tests for store aggregator and utils

* clean up un-used paths in thunkAction testUtils

* clean up filter reducer coverage

* add filter reducer tests for filterMenu actions

* clean up grades selectors coverage

* separate App and add unit tests

* ignore external files in coverage analysis

* remove old/unused code from StrictDict and clean up tests

* clean up FileUploadForm coverage

* more cleanup for StrictDict tests

* clean up GradesTab test coverage

* clean up GradesTab coverage

* ignore reducer-mapping for unit-test coverage

* clean up AssignmentFilter test coverage

* add index-level test coverage

* temp remove snapshots

* re-add App snapshot

* v1.4.41
2021-07-02 12:32:18 -04:00
Ben Warzeski
15b76edb5d refactor: lms service testing (#199)
* v1.4.40

* ignore accepted import eslint errors

* clean up LmsApiService into smaller, tested modules in lms service

* set default format before initial fetches

* fix bulk grades export and grade filtering

* fix clearing assignment grade filter badge

* re-connect grade format control
2021-06-30 11:50:07 -04:00
144 changed files with 3534 additions and 1326 deletions

1
.env
View File

@@ -30,4 +30,3 @@ ENTERPRISE_MARKETING_URL=''
ENTERPRISE_MARKETING_UTM_SOURCE=''
ENTERPRISE_MARKETING_UTM_CAMPAIGN=''
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM=''
BULK_MANAGEMENT_SPECIAL_ACCESS_COURSE_IDS=''

View File

@@ -36,4 +36,3 @@ ENTERPRISE_MARKETING_URL='http://example.com'
ENTERPRISE_MARKETING_UTM_SOURCE='example.com'
ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral'
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
BULK_MANAGEMENT_SPECIAL_ACCESS_COURSE_IDS=''

View File

@@ -1,6 +1,13 @@
const { createConfig } = require('@edx/frontend-build');
const config = createConfig('eslint');
const config = createConfig('eslint', {
rules: {
'import/no-named-as-default': 'off',
'import/no-named-as-default-member': 'off',
'import/no-self-import': 'off',
'spaced-comment': ['error', 'always', { 'block': { 'exceptions': ['*'] } }],
},
});
config.settings = {
"import/resolver": {

4
.gitignore vendored
View File

@@ -17,3 +17,7 @@ dist/
### Development environments ###
.idea
.vscode
### transifex ###
src/i18n/transifex_input.json
temp

8
.tx/config Normal file
View File

@@ -0,0 +1,8 @@
[main]
host = https://www.transifex.com
[edx-platform.frontend-app-gradebook]
file_filter = src/i18n/messages/<lang>.json
source_file = src/i18n/transifex_input.json
source_lang = en
type = KEYVALUEJSON

View File

@@ -2,8 +2,64 @@ npm-install-%: ## install specified % npm package
npm install $* --save-dev
git add package.json
validate-no-uncommitted-package-lock-changes:
git diff --exit-code package-lock.json
transifex_resource = frontend-app-gradebook
transifex_langs = "ar,fr,es_419,zh_CN"
test:
npm run test
transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/translation/en/strings/
tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/source/
# This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-react-intl
NPM_TESTS=build i18n_extract lint test is-es5
.PHONY: test
test: $(addprefix test.npm.,$(NPM_TESTS)) ## validate ci suite
.PHONY: test.npm.*
test.npm.%: validate-no-uncommitted-package-lock-changes
test -d node_modules || $(MAKE) requirements
npm run $(*)
.PHONY: requirements
requirements: ## install ci requirements
npm ci
i18n.extract:
# Pulling display strings from .jsx files into .json files...
rm -rf $(transifex_temp)
npm run-script i18n_extract
i18n.concat:
# Gathering JSON messages into one file...
$(transifex_utils) $(transifex_temp) $(transifex_input)
extract_translations: | requirements i18n.extract i18n.concat
# Despite the name, we actually need this target to detect changes in the incoming translated message files as well.
detect_changed_source_translations:
# Checking for changed translations...
git diff --exit-code $(i18n)
# Pushes translations to Transifex. You must run make extract_translations first.
push_translations:
# Pushing strings to Transifex...
tx push -s
# Fetching hashes from Transifex...
./node_modules/reactifex/bash_scripts/get_hashed_strings.sh $(tx_url1)
# Writing out comments to file...
$(transifex_utils) $(transifex_temp) --comments
# Pushing comments to Transifex...
./node_modules/reactifex/bash_scripts/put_comments.sh $(tx_url2)
# Pulls translations from Transifex.
pull_translations:
tx pull -f --mode reviewed --language=$(transifex_langs)
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:
# Checking for package-lock.json changes...
git diff --exit-code package-lock.json

View File

@@ -68,7 +68,7 @@ Confirm the following workflows:
- [ ] Clicking "Save Grade" applies the override, shows the successful "grade has been edited" banner and updates score in grades table (may take a few seconds).
- [ ] Opening back up the "Edit Grades" modal shows the change as an entry in the override history table.
- [ ] *Masters only*: "Bulk Management" allows overriding grades in bulk.
- [ ] *Master's (or selectively-enabled) only*: "Bulk Management" allows overriding grades in bulk.
- Open a non-masters-track course.
- [ ] Verify that the "Bulk Management" tab does not appear.
- [ ] Verify that the "Bulk Management" button does not appear.

View File

@@ -14,7 +14,7 @@ Suggested resources:
- [Adding Exercises and Tools](https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/grading/index.html)
- [Set the Assignment Type and Due Date for a Subsection](https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/developing_course/course_subsections.html#set-the-assignment-type-and-due-date-for-a-subsection)
## Enable Gradebook and feature toggles for course
## Enable Gradebook for course
See README.md #Quickstart for more detailed instructions.
@@ -25,7 +25,13 @@ As an admin user, visit Django Admin (`{lms-url}/admin`) to modify features.
- [ ] Set name to `grades.assume_zero_grade_if_absent`, select "Active", and click "Save"
- In Waffle_Utils > Waffle flag course overrides:
- [ ] Add a new flag called `grades.writeable_gradebook`, select "Force On", and enable it for your course
- [ ] Add a new flag called `grades.bulk_management`, select "Force On", and enable it for your course
## Enable Bulk Management
Bulk Management is an added feature to allow modifying grades in bulk via CSV upload. Bulk Management is default enabled for Master's track courses but can be selectively enabled for other courses with a waffle flag following the steps below.
- In Waffle_Utils > Waffle flag course overrides:
- [ ] Add a new flag called `grades.bulk_management`, select "Force On", and enable it for your course.
## Create a Master's track for testing Master's-only features

View File

@@ -8,4 +8,8 @@ module.exports = createConfig('jest', {
snapshotSerializers: [
'enzyme-to-json/serializer',
],
coveragePathIgnorePatterns: [
'src/segment.js',
'src/postcss.config.js',
],
});

874
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "@edx/frontend-app-gradebook",
"version": "1.4.36",
"version": "1.4.46",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -18156,7 +18156,7 @@
"dependencies": {
"JSONStream": {
"version": "1.3.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
"dev": true,
"requires": {
@@ -18166,13 +18166,13 @@
},
"abbrev": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"agent-base": {
"version": "4.3.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
"dev": true,
"requires": {
@@ -18181,7 +18181,7 @@
},
"agentkeepalive": {
"version": "3.5.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==",
"dev": true,
"requires": {
@@ -18190,7 +18190,7 @@
},
"ajv": {
"version": "5.5.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
"dev": true,
"requires": {
@@ -18202,7 +18202,7 @@
},
"ansi-align": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
"dev": true,
"requires": {
@@ -18211,13 +18211,13 @@
},
"ansi-regex": {
"version": "2.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
@@ -18226,31 +18226,31 @@
},
"ansicolors": {
"version": "0.3.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=",
"dev": true
},
"ansistyles": {
"version": "0.1.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=",
"dev": true
},
"aproba": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
"dev": true
},
"archy": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=",
"dev": true
},
"are-we-there-yet": {
"version": "1.1.4",
"resolved": false,
"resolved": "",
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
"dev": true,
"requires": {
@@ -18260,7 +18260,7 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": false,
"resolved": "",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@@ -18275,7 +18275,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@@ -18286,13 +18286,13 @@
},
"asap": {
"version": "2.0.6",
"resolved": false,
"resolved": "",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
"dev": true
},
"asn1": {
"version": "0.2.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
"dev": true,
"requires": {
@@ -18301,37 +18301,37 @@
},
"assert-plus": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"dev": true
},
"asynckit": {
"version": "0.4.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"aws-sign2": {
"version": "0.7.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
"dev": true
},
"aws4": {
"version": "1.8.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
},
"balanced-match": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"dev": true,
"optional": true,
@@ -18341,7 +18341,7 @@
},
"bin-links": {
"version": "1.1.8",
"resolved": false,
"resolved": "",
"integrity": "sha512-KgmVfx+QqggqP9dA3iIc5pA4T1qEEEL+hOhOhNPaUm77OTrJoOXE/C05SJLNJe6m/2wUK7F1tDSou7n5TfCDzQ==",
"dev": true,
"requires": {
@@ -18355,13 +18355,13 @@
},
"bluebird": {
"version": "3.5.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==",
"dev": true
},
"boxen": {
"version": "1.3.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==",
"dev": true,
"requires": {
@@ -18376,7 +18376,7 @@
},
"brace-expansion": {
"version": "1.1.11",
"resolved": false,
"resolved": "",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
@@ -18386,31 +18386,31 @@
},
"buffer-from": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==",
"dev": true
},
"builtins": {
"version": "1.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=",
"dev": true
},
"byline": {
"version": "5.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=",
"dev": true
},
"byte-size": {
"version": "5.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==",
"dev": true
},
"cacache": {
"version": "12.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==",
"dev": true,
"requires": {
@@ -18433,31 +18433,31 @@
},
"call-limit": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ==",
"dev": true
},
"camelcase": {
"version": "4.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
"dev": true
},
"capture-stack-trace": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=",
"dev": true
},
"caseless": {
"version": "0.12.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
"dev": true
},
"chalk": {
"version": "2.4.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"requires": {
@@ -18468,19 +18468,19 @@
},
"chownr": {
"version": "1.1.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"dev": true
},
"ci-info": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"dev": true
},
"cidr-regex": {
"version": "2.0.10",
"resolved": false,
"resolved": "",
"integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==",
"dev": true,
"requires": {
@@ -18489,13 +18489,13 @@
},
"cli-boxes": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=",
"dev": true
},
"cli-columns": {
"version": "3.1.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=",
"dev": true,
"requires": {
@@ -18505,7 +18505,7 @@
},
"cli-table3": {
"version": "0.5.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==",
"dev": true,
"requires": {
@@ -18516,7 +18516,7 @@
},
"cliui": {
"version": "5.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true,
"requires": {
@@ -18527,19 +18527,19 @@
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
@@ -18550,7 +18550,7 @@
},
"strip-ansi": {
"version": "5.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
@@ -18561,13 +18561,13 @@
},
"clone": {
"version": "1.0.4",
"resolved": false,
"resolved": "",
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=",
"dev": true
},
"cmd-shim": {
"version": "3.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==",
"dev": true,
"requires": {
@@ -18577,19 +18577,19 @@
},
"co": {
"version": "4.6.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
"dev": true
},
"code-point-at": {
"version": "1.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
"color-convert": {
"version": "1.9.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"dev": true,
"requires": {
@@ -18598,20 +18598,20 @@
},
"color-name": {
"version": "1.1.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"colors": {
"version": "1.3.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==",
"dev": true,
"optional": true
},
"columnify": {
"version": "1.5.4",
"resolved": false,
"resolved": "",
"integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=",
"dev": true,
"requires": {
@@ -18621,7 +18621,7 @@
},
"combined-stream": {
"version": "1.0.6",
"resolved": false,
"resolved": "",
"integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
"dev": true,
"requires": {
@@ -18630,13 +18630,13 @@
},
"concat-map": {
"version": "0.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"concat-stream": {
"version": "1.6.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"dev": true,
"requires": {
@@ -18648,7 +18648,7 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": false,
"resolved": "",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@@ -18663,7 +18663,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@@ -18674,7 +18674,7 @@
},
"config-chain": {
"version": "1.1.12",
"resolved": false,
"resolved": "",
"integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
"dev": true,
"requires": {
@@ -18684,7 +18684,7 @@
},
"configstore": {
"version": "3.1.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==",
"dev": true,
"requires": {
@@ -18698,13 +18698,13 @@
},
"console-control-strings": {
"version": "1.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
},
"copy-concurrently": {
"version": "1.0.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
"dev": true,
"requires": {
@@ -18718,13 +18718,13 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true
},
"iferr": {
"version": "0.1.5",
"resolved": false,
"resolved": "",
"integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
"dev": true
}
@@ -18732,13 +18732,13 @@
},
"core-util-is": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
"create-error-class": {
"version": "3.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
"dev": true,
"requires": {
@@ -18747,7 +18747,7 @@
},
"cross-spawn": {
"version": "5.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
"dev": true,
"requires": {
@@ -18758,7 +18758,7 @@
"dependencies": {
"lru-cache": {
"version": "4.1.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"dev": true,
"requires": {
@@ -18768,7 +18768,7 @@
},
"yallist": {
"version": "2.1.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
}
@@ -18776,19 +18776,19 @@
},
"crypto-random-string": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
"dev": true
},
"cyclist": {
"version": "0.2.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=",
"dev": true
},
"dashdash": {
"version": "1.14.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"dev": true,
"requires": {
@@ -18797,7 +18797,7 @@
},
"debug": {
"version": "3.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
@@ -18806,7 +18806,7 @@
"dependencies": {
"ms": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
@@ -18814,31 +18814,31 @@
},
"debuglog": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=",
"dev": true
},
"decamelize": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true
},
"deep-extend": {
"version": "0.6.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"dev": true
},
"defaults": {
"version": "1.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
"dev": true,
"requires": {
@@ -18847,7 +18847,7 @@
},
"define-properties": {
"version": "1.1.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"dev": true,
"requires": {
@@ -18856,31 +18856,31 @@
},
"delayed-stream": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
"delegates": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"dev": true
},
"detect-indent": {
"version": "5.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=",
"dev": true
},
"detect-newline": {
"version": "2.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
"dev": true
},
"dezalgo": {
"version": "1.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=",
"dev": true,
"requires": {
@@ -18890,7 +18890,7 @@
},
"dot-prop": {
"version": "4.2.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==",
"dev": true,
"requires": {
@@ -18899,19 +18899,19 @@
},
"dotenv": {
"version": "5.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==",
"dev": true
},
"duplexer3": {
"version": "0.1.4",
"resolved": false,
"resolved": "",
"integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
"dev": true
},
"duplexify": {
"version": "3.6.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==",
"dev": true,
"requires": {
@@ -18923,7 +18923,7 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": false,
"resolved": "",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@@ -18938,7 +18938,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@@ -18949,7 +18949,7 @@
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
"dev": true,
"optional": true,
@@ -18960,19 +18960,19 @@
},
"editor": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=",
"dev": true
},
"emoji-regex": {
"version": "7.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"encoding": {
"version": "0.1.12",
"resolved": false,
"resolved": "",
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
"dev": true,
"requires": {
@@ -18981,7 +18981,7 @@
},
"end-of-stream": {
"version": "1.4.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
"dev": true,
"requires": {
@@ -18990,19 +18990,19 @@
},
"env-paths": {
"version": "2.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==",
"dev": true
},
"err-code": {
"version": "1.1.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=",
"dev": true
},
"errno": {
"version": "0.1.7",
"resolved": false,
"resolved": "",
"integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
"dev": true,
"requires": {
@@ -19011,7 +19011,7 @@
},
"es-abstract": {
"version": "1.12.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==",
"dev": true,
"requires": {
@@ -19024,7 +19024,7 @@
},
"es-to-primitive": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
"dev": true,
"requires": {
@@ -19035,13 +19035,13 @@
},
"es6-promise": {
"version": "4.2.8",
"resolved": false,
"resolved": "",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
"dev": true
},
"es6-promisify": {
"version": "5.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
"dev": true,
"requires": {
@@ -19050,13 +19050,13 @@
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": false,
"resolved": "",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"execa": {
"version": "0.7.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
"dev": true,
"requires": {
@@ -19071,7 +19071,7 @@
"dependencies": {
"get-stream": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true
}
@@ -19079,43 +19079,43 @@
},
"extend": {
"version": "3.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
},
"extsprintf": {
"version": "1.3.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
"dev": true
},
"fast-deep-equal": {
"version": "1.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=",
"dev": true
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
"dev": true
},
"figgy-pudding": {
"version": "3.5.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
"dev": true
},
"find-npm-prefix": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==",
"dev": true
},
"flush-write-stream": {
"version": "1.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==",
"dev": true,
"requires": {
@@ -19125,7 +19125,7 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": false,
"resolved": "",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@@ -19140,7 +19140,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@@ -19151,13 +19151,13 @@
},
"forever-agent": {
"version": "0.6.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
"dev": true
},
"form-data": {
"version": "2.3.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
"dev": true,
"requires": {
@@ -19168,7 +19168,7 @@
},
"from2": {
"version": "2.3.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
"dev": true,
"requires": {
@@ -19178,7 +19178,7 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": false,
"resolved": "",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@@ -19193,7 +19193,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@@ -19204,7 +19204,7 @@
},
"fs-minipass": {
"version": "1.2.7",
"resolved": false,
"resolved": "",
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
"dev": true,
"requires": {
@@ -19213,7 +19213,7 @@
"dependencies": {
"minipass": {
"version": "2.9.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"dev": true,
"requires": {
@@ -19225,7 +19225,7 @@
},
"fs-vacuum": {
"version": "1.2.10",
"resolved": false,
"resolved": "",
"integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=",
"dev": true,
"requires": {
@@ -19236,7 +19236,7 @@
},
"fs-write-stream-atomic": {
"version": "1.0.10",
"resolved": false,
"resolved": "",
"integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
"dev": true,
"requires": {
@@ -19248,13 +19248,13 @@
"dependencies": {
"iferr": {
"version": "0.1.5",
"resolved": false,
"resolved": "",
"integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
"dev": true
},
"readable-stream": {
"version": "2.3.6",
"resolved": false,
"resolved": "",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@@ -19269,7 +19269,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@@ -19280,19 +19280,19 @@
},
"fs.realpath": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"function-bind": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"gauge": {
"version": "2.7.4",
"resolved": false,
"resolved": "",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"dev": true,
"requires": {
@@ -19308,13 +19308,13 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true
},
"string-width": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@@ -19327,13 +19327,13 @@
},
"genfun": {
"version": "5.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==",
"dev": true
},
"gentle-fs": {
"version": "2.3.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-OlwBBwqCFPcjm33rF2BjW+Pr6/ll2741l+xooiwTCeaX2CA1ZuclavyMBe0/KlR21/XGsgY6hzEQZ15BdNa13Q==",
"dev": true,
"requires": {
@@ -19352,13 +19352,13 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true
},
"iferr": {
"version": "0.1.5",
"resolved": false,
"resolved": "",
"integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
"dev": true
}
@@ -19366,13 +19366,13 @@
},
"get-caller-file": {
"version": "2.0.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"get-stream": {
"version": "4.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"dev": true,
"requires": {
@@ -19381,7 +19381,7 @@
},
"getpass": {
"version": "0.1.7",
"resolved": false,
"resolved": "",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"dev": true,
"requires": {
@@ -19390,7 +19390,7 @@
},
"glob": {
"version": "7.1.6",
"resolved": false,
"resolved": "",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
@@ -19404,7 +19404,7 @@
},
"global-dirs": {
"version": "0.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
"dev": true,
"requires": {
@@ -19413,7 +19413,7 @@
},
"got": {
"version": "6.7.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
"dev": true,
"requires": {
@@ -19432,7 +19432,7 @@
"dependencies": {
"get-stream": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true
}
@@ -19440,19 +19440,19 @@
},
"graceful-fs": {
"version": "4.2.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
"har-schema": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
"dev": true
},
"har-validator": {
"version": "5.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==",
"dev": true,
"requires": {
@@ -19462,7 +19462,7 @@
},
"has": {
"version": "1.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
@@ -19471,37 +19471,37 @@
},
"has-flag": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"has-symbols": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
"dev": true
},
"has-unicode": {
"version": "2.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"dev": true
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": false,
"resolved": "",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"dev": true
},
"http-cache-semantics": {
"version": "3.8.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==",
"dev": true
},
"http-proxy-agent": {
"version": "2.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
"dev": true,
"requires": {
@@ -19511,7 +19511,7 @@
},
"http-signature": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"dev": true,
"requires": {
@@ -19522,7 +19522,7 @@
},
"https-proxy-agent": {
"version": "2.2.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
"dev": true,
"requires": {
@@ -19532,7 +19532,7 @@
},
"humanize-ms": {
"version": "1.2.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
"dev": true,
"requires": {
@@ -19541,7 +19541,7 @@
},
"iconv-lite": {
"version": "0.4.23",
"resolved": false,
"resolved": "",
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
"dev": true,
"requires": {
@@ -19550,13 +19550,13 @@
},
"iferr": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg==",
"dev": true
},
"ignore-walk": {
"version": "3.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
"dev": true,
"requires": {
@@ -19565,25 +19565,25 @@
},
"import-lazy": {
"version": "2.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=",
"dev": true
},
"imurmurhash": {
"version": "0.1.4",
"resolved": false,
"resolved": "",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
"infer-owner": {
"version": "1.0.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": false,
"resolved": "",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
@@ -19593,19 +19593,19 @@
},
"inherits": {
"version": "2.0.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"ini": {
"version": "1.3.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true
},
"init-package-json": {
"version": "1.10.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==",
"dev": true,
"requires": {
@@ -19621,25 +19621,25 @@
},
"ip": {
"version": "1.1.5",
"resolved": false,
"resolved": "",
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
"dev": true
},
"ip-regex": {
"version": "2.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
"dev": true
},
"is-callable": {
"version": "1.1.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
"dev": true
},
"is-ci": {
"version": "1.2.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
"dev": true,
"requires": {
@@ -19648,7 +19648,7 @@
"dependencies": {
"ci-info": {
"version": "1.6.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
"dev": true
}
@@ -19656,7 +19656,7 @@
},
"is-cidr": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-8Xnnbjsb0x462VoYiGlhEi+drY8SFwrHiSYuzc/CEwco55vkehTaxAyIjEdpi3EMvLPPJAJi9FlzP+h+03gp0Q==",
"dev": true,
"requires": {
@@ -19665,13 +19665,13 @@
},
"is-date-object": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
"dev": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
@@ -19680,7 +19680,7 @@
},
"is-installed-globally": {
"version": "0.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
"dev": true,
"requires": {
@@ -19690,19 +19690,19 @@
},
"is-npm": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=",
"dev": true
},
"is-obj": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
"dev": true
},
"is-path-inside": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
"dev": true,
"requires": {
@@ -19711,13 +19711,13 @@
},
"is-redirect": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=",
"dev": true
},
"is-regex": {
"version": "1.0.4",
"resolved": false,
"resolved": "",
"integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
"dev": true,
"requires": {
@@ -19726,19 +19726,19 @@
},
"is-retry-allowed": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==",
"dev": true
},
"is-stream": {
"version": "1.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
"dev": true
},
"is-symbol": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
"dev": true,
"requires": {
@@ -19747,68 +19747,68 @@
},
"is-typedarray": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true
},
"isarray": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"isstream": {
"version": "0.1.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
"dev": true
},
"jsbn": {
"version": "0.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"dev": true,
"optional": true
},
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
"dev": true
},
"json-schema": {
"version": "0.2.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
"dev": true
},
"json-schema-traverse": {
"version": "0.3.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
"dev": true
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"dev": true
},
"jsonparse": {
"version": "1.3.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
"dev": true
},
"jsprim": {
"version": "1.4.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
"dev": true,
"requires": {
@@ -19820,7 +19820,7 @@
},
"latest-version": {
"version": "3.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=",
"dev": true,
"requires": {
@@ -19829,13 +19829,13 @@
},
"lazy-property": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=",
"dev": true
},
"libcipm": {
"version": "4.0.8",
"resolved": false,
"resolved": "",
"integrity": "sha512-IN3hh2yDJQtZZ5paSV4fbvJg4aHxCCg5tcZID/dSVlTuUiWktsgaldVljJv6Z5OUlYspx6xQkbR0efNodnIrOA==",
"dev": true,
"requires": {
@@ -19858,7 +19858,7 @@
},
"libnpm": {
"version": "3.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==",
"dev": true,
"requires": {
@@ -19886,7 +19886,7 @@
},
"libnpmaccess": {
"version": "3.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==",
"dev": true,
"requires": {
@@ -19898,7 +19898,7 @@
},
"libnpmconfig": {
"version": "1.2.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==",
"dev": true,
"requires": {
@@ -19909,7 +19909,7 @@
"dependencies": {
"find-up": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
@@ -19918,7 +19918,7 @@
},
"locate-path": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
@@ -19928,7 +19928,7 @@
},
"p-limit": {
"version": "2.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
"dev": true,
"requires": {
@@ -19937,7 +19937,7 @@
},
"p-locate": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
@@ -19946,7 +19946,7 @@
},
"p-try": {
"version": "2.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
}
@@ -19954,7 +19954,7 @@
},
"libnpmhook": {
"version": "5.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==",
"dev": true,
"requires": {
@@ -19966,7 +19966,7 @@
},
"libnpmorg": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==",
"dev": true,
"requires": {
@@ -19978,7 +19978,7 @@
},
"libnpmpublish": {
"version": "1.1.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==",
"dev": true,
"requires": {
@@ -19995,7 +19995,7 @@
},
"libnpmsearch": {
"version": "2.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==",
"dev": true,
"requires": {
@@ -20006,7 +20006,7 @@
},
"libnpmteam": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==",
"dev": true,
"requires": {
@@ -20018,7 +20018,7 @@
},
"libnpx": {
"version": "10.2.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-BPc0D1cOjBeS8VIBKUu5F80s6njm0wbVt7CsGMrIcJ+SI7pi7V0uVPGpEMH9H5L8csOcclTxAXFE2VAsJXUhfA==",
"dev": true,
"requires": {
@@ -20034,7 +20034,7 @@
},
"lock-verify": {
"version": "2.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==",
"dev": true,
"requires": {
@@ -20044,7 +20044,7 @@
},
"lockfile": {
"version": "1.0.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==",
"dev": true,
"requires": {
@@ -20053,13 +20053,13 @@
},
"lodash._baseindexof": {
"version": "3.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=",
"dev": true
},
"lodash._baseuniq": {
"version": "4.6.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=",
"dev": true,
"requires": {
@@ -20069,19 +20069,19 @@
},
"lodash._bindcallback": {
"version": "3.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=",
"dev": true
},
"lodash._cacheindexof": {
"version": "3.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=",
"dev": true
},
"lodash._createcache": {
"version": "3.1.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=",
"dev": true,
"requires": {
@@ -20090,61 +20090,61 @@
},
"lodash._createset": {
"version": "4.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=",
"dev": true
},
"lodash._getnative": {
"version": "3.9.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
"dev": true
},
"lodash._root": {
"version": "3.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=",
"dev": true
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
"dev": true
},
"lodash.restparam": {
"version": "3.6.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=",
"dev": true
},
"lodash.union": {
"version": "4.6.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=",
"dev": true
},
"lodash.uniq": {
"version": "4.5.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
"dev": true
},
"lodash.without": {
"version": "4.4.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=",
"dev": true
},
"lowercase-keys": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
"dev": true
},
"lru-cache": {
"version": "5.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"requires": {
@@ -20153,7 +20153,7 @@
},
"make-dir": {
"version": "1.3.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
"dev": true,
"requires": {
@@ -20162,7 +20162,7 @@
},
"make-fetch-happen": {
"version": "5.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==",
"dev": true,
"requires": {
@@ -20181,19 +20181,19 @@
},
"meant": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-KN+1uowN/NK+sT/Lzx7WSGIj2u+3xe5n2LbwObfjOhPZiA+cCfCm6idVl0RkEfjThkw5XJ96CyRcanq6GmKtUg==",
"dev": true
},
"mime-db": {
"version": "1.35.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==",
"dev": true
},
"mime-types": {
"version": "2.1.19",
"resolved": false,
"resolved": "",
"integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==",
"dev": true,
"requires": {
@@ -20202,7 +20202,7 @@
},
"minimatch": {
"version": "3.0.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
@@ -20211,13 +20211,13 @@
},
"minimist": {
"version": "1.2.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"minizlib": {
"version": "1.3.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
"dev": true,
"requires": {
@@ -20226,7 +20226,7 @@
"dependencies": {
"minipass": {
"version": "2.9.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"dev": true,
"requires": {
@@ -20238,7 +20238,7 @@
},
"mississippi": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
"dev": true,
"requires": {
@@ -20256,7 +20256,7 @@
},
"mkdirp": {
"version": "0.5.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
@@ -20265,7 +20265,7 @@
"dependencies": {
"minimist": {
"version": "1.2.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
}
@@ -20273,7 +20273,7 @@
},
"move-concurrently": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
"dev": true,
"requires": {
@@ -20287,7 +20287,7 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true
}
@@ -20295,19 +20295,19 @@
},
"ms": {
"version": "2.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
},
"mute-stream": {
"version": "0.0.7",
"resolved": false,
"resolved": "",
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
"dev": true
},
"node-fetch-npm": {
"version": "2.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==",
"dev": true,
"requires": {
@@ -20318,7 +20318,7 @@
},
"node-gyp": {
"version": "5.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==",
"dev": true,
"requires": {
@@ -20337,7 +20337,7 @@
},
"nopt": {
"version": "4.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
"dev": true,
"requires": {
@@ -20347,7 +20347,7 @@
},
"normalize-package-data": {
"version": "2.5.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"dev": true,
"requires": {
@@ -20359,7 +20359,7 @@
"dependencies": {
"resolve": {
"version": "1.10.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
"dev": true,
"requires": {
@@ -20370,7 +20370,7 @@
},
"npm-audit-report": {
"version": "1.3.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-8nH/JjsFfAWMvn474HB9mpmMjrnKb1Hx/oTAdjv4PT9iZBvBxiZ+wtDUapHCJwLqYGQVPaAfs+vL5+5k9QndXw==",
"dev": true,
"requires": {
@@ -20380,7 +20380,7 @@
},
"npm-bundled": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
"dev": true,
"requires": {
@@ -20389,13 +20389,13 @@
},
"npm-cache-filename": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=",
"dev": true
},
"npm-install-checks": {
"version": "3.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==",
"dev": true,
"requires": {
@@ -20404,7 +20404,7 @@
},
"npm-lifecycle": {
"version": "3.1.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==",
"dev": true,
"requires": {
@@ -20420,19 +20420,19 @@
},
"npm-logical-tree": {
"version": "1.2.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==",
"dev": true
},
"npm-normalize-package-bin": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
"dev": true
},
"npm-package-arg": {
"version": "6.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==",
"dev": true,
"requires": {
@@ -20444,7 +20444,7 @@
},
"npm-packlist": {
"version": "1.4.8",
"resolved": false,
"resolved": "",
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
"dev": true,
"requires": {
@@ -20455,7 +20455,7 @@
},
"npm-pick-manifest": {
"version": "3.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==",
"dev": true,
"requires": {
@@ -20466,7 +20466,7 @@
},
"npm-profile": {
"version": "4.0.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==",
"dev": true,
"requires": {
@@ -20477,7 +20477,7 @@
},
"npm-registry-fetch": {
"version": "4.0.7",
"resolved": false,
"resolved": "",
"integrity": "sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==",
"dev": true,
"requires": {
@@ -20492,7 +20492,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.2.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
}
@@ -20500,7 +20500,7 @@
},
"npm-run-path": {
"version": "2.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
"dev": true,
"requires": {
@@ -20509,13 +20509,13 @@
},
"npm-user-validate": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-jOyg9c6gTU6TUZ73LQVXp1Ei6VE=",
"dev": true
},
"npmlog": {
"version": "4.1.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"dev": true,
"requires": {
@@ -20527,31 +20527,31 @@
},
"number-is-nan": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
},
"oauth-sign": {
"version": "0.9.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"dev": true
},
"object-assign": {
"version": "4.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
},
"object-keys": {
"version": "1.0.12",
"resolved": false,
"resolved": "",
"integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==",
"dev": true
},
"object.getownpropertydescriptors": {
"version": "2.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
"dev": true,
"requires": {
@@ -20561,7 +20561,7 @@
},
"once": {
"version": "1.4.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
@@ -20570,25 +20570,25 @@
},
"opener": {
"version": "1.5.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==",
"dev": true
},
"os-homedir": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"dev": true
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
"osenv": {
"version": "0.1.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"dev": true,
"requires": {
@@ -20598,13 +20598,13 @@
},
"p-finally": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"dev": true
},
"package-json": {
"version": "4.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
"dev": true,
"requires": {
@@ -20616,7 +20616,7 @@
},
"pacote": {
"version": "9.5.12",
"resolved": false,
"resolved": "",
"integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==",
"dev": true,
"requires": {
@@ -20654,7 +20654,7 @@
"dependencies": {
"minipass": {
"version": "2.9.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"dev": true,
"requires": {
@@ -20666,7 +20666,7 @@
},
"parallel-transform": {
"version": "1.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
"dev": true,
"requires": {
@@ -20677,7 +20677,7 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": false,
"resolved": "",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@@ -20692,7 +20692,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@@ -20703,67 +20703,67 @@
},
"path-exists": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-is-inside": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
"dev": true
},
"path-key": {
"version": "2.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": false,
"resolved": "",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"performance-now": {
"version": "2.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true
},
"pify": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
"prepend-http": {
"version": "1.0.4",
"resolved": false,
"resolved": "",
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
"dev": true
},
"process-nextick-args": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
"dev": true
},
"promise-inflight": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
"dev": true
},
"promise-retry": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=",
"dev": true,
"requires": {
@@ -20773,7 +20773,7 @@
"dependencies": {
"retry": {
"version": "0.10.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=",
"dev": true
}
@@ -20781,7 +20781,7 @@
},
"promzard": {
"version": "0.3.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=",
"dev": true,
"requires": {
@@ -20790,13 +20790,13 @@
},
"proto-list": {
"version": "1.2.4",
"resolved": false,
"resolved": "",
"integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=",
"dev": true
},
"protoduck": {
"version": "5.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==",
"dev": true,
"requires": {
@@ -20805,25 +20805,25 @@
},
"prr": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
"dev": true
},
"pseudomap": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
"dev": true
},
"psl": {
"version": "1.1.29",
"resolved": false,
"resolved": "",
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==",
"dev": true
},
"pump": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dev": true,
"requires": {
@@ -20833,7 +20833,7 @@
},
"pumpify": {
"version": "1.5.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
"dev": true,
"requires": {
@@ -20844,7 +20844,7 @@
"dependencies": {
"pump": {
"version": "2.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
"dev": true,
"requires": {
@@ -20856,25 +20856,25 @@
},
"punycode": {
"version": "1.4.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true
},
"qrcode-terminal": {
"version": "0.12.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==",
"dev": true
},
"qs": {
"version": "6.5.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"dev": true
},
"query-string": {
"version": "6.8.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==",
"dev": true,
"requires": {
@@ -20885,13 +20885,13 @@
},
"qw": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=",
"dev": true
},
"rc": {
"version": "1.2.8",
"resolved": false,
"resolved": "",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"dev": true,
"requires": {
@@ -20903,7 +20903,7 @@
},
"read": {
"version": "1.0.7",
"resolved": false,
"resolved": "",
"integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=",
"dev": true,
"requires": {
@@ -20912,7 +20912,7 @@
},
"read-cmd-shim": {
"version": "1.0.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==",
"dev": true,
"requires": {
@@ -20921,7 +20921,7 @@
},
"read-installed": {
"version": "4.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=",
"dev": true,
"requires": {
@@ -20936,7 +20936,7 @@
},
"read-package-json": {
"version": "2.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==",
"dev": true,
"requires": {
@@ -20949,7 +20949,7 @@
},
"read-package-tree": {
"version": "5.3.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==",
"dev": true,
"requires": {
@@ -20960,7 +20960,7 @@
},
"readable-stream": {
"version": "3.6.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
@@ -20971,7 +20971,7 @@
},
"readdir-scoped-modules": {
"version": "1.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
"dev": true,
"requires": {
@@ -20983,7 +20983,7 @@
},
"registry-auth-token": {
"version": "3.4.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
"dev": true,
"requires": {
@@ -20993,7 +20993,7 @@
},
"registry-url": {
"version": "3.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
"dev": true,
"requires": {
@@ -21002,7 +21002,7 @@
},
"request": {
"version": "2.88.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"dev": true,
"requires": {
@@ -21030,31 +21030,31 @@
},
"require-directory": {
"version": "2.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
"require-main-filename": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"resolve-from": {
"version": "4.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"retry": {
"version": "0.12.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
"dev": true
},
"rimraf": {
"version": "2.7.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"requires": {
@@ -21063,7 +21063,7 @@
},
"run-queue": {
"version": "1.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
"dev": true,
"requires": {
@@ -21072,7 +21072,7 @@
"dependencies": {
"aproba": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true
}
@@ -21080,25 +21080,25 @@
},
"safe-buffer": {
"version": "5.1.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"semver": {
"version": "5.7.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"semver-diff": {
"version": "2.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
"dev": true,
"requires": {
@@ -21107,13 +21107,13 @@
},
"set-blocking": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"sha": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==",
"dev": true,
"requires": {
@@ -21122,7 +21122,7 @@
},
"shebang-command": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
"dev": true,
"requires": {
@@ -21131,31 +21131,31 @@
},
"shebang-regex": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
"signal-exit": {
"version": "3.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
},
"slide": {
"version": "1.1.6",
"resolved": false,
"resolved": "",
"integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=",
"dev": true
},
"smart-buffer": {
"version": "4.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==",
"dev": true
},
"socks": {
"version": "2.3.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==",
"dev": true,
"requires": {
@@ -21165,7 +21165,7 @@
},
"socks-proxy-agent": {
"version": "4.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==",
"dev": true,
"requires": {
@@ -21175,7 +21175,7 @@
"dependencies": {
"agent-base": {
"version": "4.2.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
"dev": true,
"requires": {
@@ -21186,13 +21186,13 @@
},
"sorted-object": {
"version": "2.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=",
"dev": true
},
"sorted-union-stream": {
"version": "2.1.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=",
"dev": true,
"requires": {
@@ -21202,7 +21202,7 @@
"dependencies": {
"from2": {
"version": "1.3.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=",
"dev": true,
"requires": {
@@ -21212,13 +21212,13 @@
},
"isarray": {
"version": "0.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
"dev": true
},
"readable-stream": {
"version": "1.1.14",
"resolved": false,
"resolved": "",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"dev": true,
"requires": {
@@ -21230,7 +21230,7 @@
},
"string_decoder": {
"version": "0.10.31",
"resolved": false,
"resolved": "",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
"dev": true
}
@@ -21238,7 +21238,7 @@
},
"spdx-correct": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==",
"dev": true,
"requires": {
@@ -21248,13 +21248,13 @@
},
"spdx-exceptions": {
"version": "2.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==",
"dev": true
},
"spdx-expression-parse": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
"dev": true,
"requires": {
@@ -21264,19 +21264,19 @@
},
"spdx-license-ids": {
"version": "3.0.5",
"resolved": false,
"resolved": "",
"integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
"dev": true
},
"split-on-first": {
"version": "1.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
"dev": true
},
"sshpk": {
"version": "1.14.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=",
"dev": true,
"requires": {
@@ -21293,7 +21293,7 @@
},
"ssri": {
"version": "6.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
"dev": true,
"requires": {
@@ -21302,7 +21302,7 @@
},
"stream-each": {
"version": "1.2.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==",
"dev": true,
"requires": {
@@ -21312,7 +21312,7 @@
},
"stream-iterate": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=",
"dev": true,
"requires": {
@@ -21322,7 +21322,7 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": false,
"resolved": "",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@@ -21337,7 +21337,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@@ -21348,19 +21348,19 @@
},
"stream-shift": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
"dev": true
},
"strict-uri-encode": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=",
"dev": true
},
"string-width": {
"version": "2.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": {
@@ -21370,19 +21370,19 @@
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"strip-ansi": {
"version": "4.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
@@ -21393,7 +21393,7 @@
},
"string_decoder": {
"version": "1.3.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"requires": {
@@ -21402,7 +21402,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
"dev": true
}
@@ -21410,13 +21410,13 @@
},
"stringify-package": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==",
"dev": true
},
"strip-ansi": {
"version": "3.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
@@ -21425,19 +21425,19 @@
},
"strip-eof": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
"dev": true
},
"strip-json-comments": {
"version": "2.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"dev": true
},
"supports-color": {
"version": "5.4.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"dev": true,
"requires": {
@@ -21446,7 +21446,7 @@
},
"tar": {
"version": "4.4.13",
"resolved": false,
"resolved": "",
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
"dev": true,
"requires": {
@@ -21461,7 +21461,7 @@
"dependencies": {
"minipass": {
"version": "2.9.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"dev": true,
"requires": {
@@ -21473,7 +21473,7 @@
},
"term-size": {
"version": "1.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=",
"dev": true,
"requires": {
@@ -21482,19 +21482,19 @@
},
"text-table": {
"version": "0.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"through": {
"version": "2.3.8",
"resolved": false,
"resolved": "",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
"through2": {
"version": "2.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
"dev": true,
"requires": {
@@ -21504,7 +21504,7 @@
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": false,
"resolved": "",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@@ -21519,7 +21519,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@@ -21530,19 +21530,19 @@
},
"timed-out": {
"version": "4.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=",
"dev": true
},
"tiny-relative-date": {
"version": "1.3.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==",
"dev": true
},
"tough-cookie": {
"version": "2.4.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"dev": true,
"requires": {
@@ -21552,7 +21552,7 @@
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"dev": true,
"requires": {
@@ -21561,32 +21561,32 @@
},
"tweetnacl": {
"version": "0.14.5",
"resolved": false,
"resolved": "",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true,
"optional": true
},
"typedarray": {
"version": "0.0.6",
"resolved": false,
"resolved": "",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true
},
"uid-number": {
"version": "0.0.6",
"resolved": false,
"resolved": "",
"integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=",
"dev": true
},
"umask": {
"version": "1.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=",
"dev": true
},
"unique-filename": {
"version": "1.1.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
"dev": true,
"requires": {
@@ -21595,7 +21595,7 @@
},
"unique-slug": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=",
"dev": true,
"requires": {
@@ -21604,7 +21604,7 @@
},
"unique-string": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
"dev": true,
"requires": {
@@ -21613,19 +21613,19 @@
},
"unpipe": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
"dev": true
},
"unzip-response": {
"version": "2.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=",
"dev": true
},
"update-notifier": {
"version": "2.5.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==",
"dev": true,
"requires": {
@@ -21643,7 +21643,7 @@
},
"url-parse-lax": {
"version": "1.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
"dev": true,
"requires": {
@@ -21652,19 +21652,19 @@
},
"util-deprecate": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
},
"util-extend": {
"version": "1.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=",
"dev": true
},
"util-promisify": {
"version": "2.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=",
"dev": true,
"requires": {
@@ -21673,13 +21673,13 @@
},
"uuid": {
"version": "3.3.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
"dev": true
},
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": false,
"resolved": "",
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"dev": true,
"requires": {
@@ -21689,7 +21689,7 @@
},
"validate-npm-package-name": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
"dev": true,
"requires": {
@@ -21698,7 +21698,7 @@
},
"verror": {
"version": "1.10.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"dev": true,
"requires": {
@@ -21709,7 +21709,7 @@
},
"wcwidth": {
"version": "1.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
"dev": true,
"requires": {
@@ -21718,7 +21718,7 @@
},
"which": {
"version": "1.3.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"requires": {
@@ -21727,13 +21727,13 @@
},
"which-module": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"wide-align": {
"version": "1.1.2",
"resolved": false,
"resolved": "",
"integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
"dev": true,
"requires": {
@@ -21742,7 +21742,7 @@
"dependencies": {
"string-width": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
@@ -21755,7 +21755,7 @@
},
"widest-line": {
"version": "2.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
"dev": true,
"requires": {
@@ -21764,7 +21764,7 @@
},
"worker-farm": {
"version": "1.7.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
"dev": true,
"requires": {
@@ -21773,7 +21773,7 @@
},
"wrap-ansi": {
"version": "5.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"requires": {
@@ -21784,19 +21784,19 @@
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
@@ -21807,7 +21807,7 @@
},
"strip-ansi": {
"version": "5.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
@@ -21818,13 +21818,13 @@
},
"wrappy": {
"version": "1.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"write-file-atomic": {
"version": "2.4.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
"dev": true,
"requires": {
@@ -21835,31 +21835,31 @@
},
"xdg-basedir": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=",
"dev": true
},
"xtend": {
"version": "4.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
"dev": true
},
"y18n": {
"version": "4.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
"yallist": {
"version": "3.0.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
"dev": true
},
"yargs": {
"version": "14.2.3",
"resolved": false,
"resolved": "",
"integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==",
"dev": true,
"requires": {
@@ -21878,13 +21878,13 @@
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"find-up": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
@@ -21893,13 +21893,13 @@
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"locate-path": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
@@ -21909,7 +21909,7 @@
},
"p-limit": {
"version": "2.3.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
@@ -21918,7 +21918,7 @@
},
"p-locate": {
"version": "3.0.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
@@ -21927,13 +21927,13 @@
},
"p-try": {
"version": "2.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
@@ -21944,7 +21944,7 @@
},
"strip-ansi": {
"version": "5.2.0",
"resolved": false,
"resolved": "",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
@@ -21955,7 +21955,7 @@
},
"yargs-parser": {
"version": "15.0.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
"dev": true,
"requires": {
@@ -21965,7 +21965,7 @@
"dependencies": {
"camelcase": {
"version": "5.3.1",
"resolved": false,
"resolved": "",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
}
@@ -24779,6 +24779,12 @@
"prop-types": "^15.6.2"
}
},
"reactifex": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/reactifex/-/reactifex-1.1.1.tgz",
"integrity": "sha512-HH2N/b5tRxh7ypIgCRsiBl/CTxRkTEPf9DhIstaM6hne4WiwM5/bBbWuvVlRZc/i3FdqZED3pZ//6n4mtxma4w==",
"dev": true
},
"read-pkg": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@edx/frontend-app-gradebook",
"version": "1.4.39",
"version": "1.4.46",
"description": "edx editable gradebook-ui to manipulate grade overrides on subsections",
"repository": {
"type": "git",
@@ -10,6 +10,7 @@
"build": "fedx-scripts webpack",
"coveralls": "cat ./coverage/lcov.info | coveralls",
"is-es5": "es-check es5 ./dist/*.js",
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"lint": "fedx-scripts eslint --ext .jsx,.js src/",
"lint-fix": "fedx-scripts eslint --fix --ext .jsx,.js src/",
"prepush": "npm run lint",
@@ -75,6 +76,7 @@
"jest": "24.9.0",
"react-dev-utils": "^5.0.3",
"react-test-renderer": "^16.10.1",
"reactifex": "1.1.1",
"redux-mock-store": "^1.5.3",
"semantic-release": "^17.2.3",
"travis-deploy-once": "^5.0.11"

36
src/App.jsx Executable file
View File

@@ -0,0 +1,36 @@
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Provider } from 'react-redux';
import { IntlProvider } from 'react-intl';
import Footer from '@edx/frontend-component-footer';
import { routePath } from 'data/constants/app';
import store from 'data/store';
import GradebookPage from 'containers/GradebookPage';
import EdxHeader from 'components/EdxHeader';
import './App.scss';
const App = () => (
<IntlProvider locale="en">
<Provider store={store}>
<Router>
<div>
<EdxHeader />
<main>
<Switch>
<Route
exact
path={routePath}
component={GradebookPage}
/>
</Switch>
</main>
<Footer logo={process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG} />
</div>
</Router>
</Provider>
</IntlProvider>
);
export default App;

86
src/App.test.jsx Normal file
View File

@@ -0,0 +1,86 @@
import React from 'react';
import { shallow } from 'enzyme';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Provider } from 'react-redux';
import { IntlProvider } from 'react-intl';
import Footer from '@edx/frontend-component-footer';
import { routePath } from 'data/constants/app';
import store from 'data/store';
import GradebookPage from 'containers/GradebookPage';
import EdxHeader from 'components/EdxHeader';
import App from './App';
jest.mock('react-router-dom', () => ({
BrowserRouter: () => 'BrowserRouter',
Route: () => 'Route',
Switch: () => 'Switch',
}));
jest.mock('react-redux', () => ({
Provider: () => 'Provider',
}));
jest.mock('react-intl', () => ({
IntlProvider: () => 'IntlProvider',
}));
jest.mock('data/constants/app', () => ({
routePath: '/:courseId',
}));
jest.mock('@edx/frontend-component-footer', () => 'Footer');
jest.mock('data/store', () => 'testStore');
jest.mock('containers/GradebookPage', () => 'GradebookPage');
jest.mock('components/EdxHeader', () => 'EdxHeader');
const logo = 'fakeLogo.png';
let el;
let router;
describe('App router component', () => {
test('snapshot', () => {
expect(shallow(<App />)).toMatchSnapshot();
});
describe('component', () => {
beforeEach(() => {
process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG = logo;
el = shallow(<App />);
router = el.childAt(0).childAt(0);
});
describe('IntlProvider', () => {
test('outer-wrapper component', () => {
expect(el.type()).toBe(IntlProvider);
});
test('"en" locale', () => {
expect(el.props().locale).toEqual('en');
});
});
describe('Provider, inside IntlProvider', () => {
test('first child, passed the redux store props', () => {
expect(el.childAt(0).type()).toBe(Provider);
expect(el.childAt(0).props().store).toEqual(store);
});
});
describe('Router', () => {
test('first child of Provider', () => {
expect(router.type()).toBe(Router);
});
test('EdxHeader is above/outside-of the routing', () => {
expect(router.childAt(0).childAt(0).type()).toBe(EdxHeader);
expect(router.childAt(0).childAt(1).type()).toBe('main');
});
test('Routing - GradebookPage is only route', () => {
expect(router.find('main')).toEqual(shallow(
<main>
<Switch>
<Route exact path={routePath} component={GradebookPage} />
</Switch>
</main>,
));
});
});
test('Footer logo drawn from env variable', () => {
expect(router.find(Footer).props().logo).toEqual(logo);
});
});
});

View File

@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App router component snapshot 1`] = `
<IntlProvider
locale="en"
>
<Provider
store="testStore"
>
<BrowserRouter>
<div>
<EdxHeader />
<main>
<Switch>
<Route
component="GradebookPage"
exact={true}
path="/:courseId"
/>
</Switch>
</main>
<Footer />
</div>
</BrowserRouter>
</Provider>
</IntlProvider>
`;

View File

@@ -1,20 +1,22 @@
/* eslint-disable react/sort-comp, react/button-has-type */
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
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;
import messages from './messages';
/**
* <BulkManagementAlerts />
* Alerts to display at the top of the BulkManagement tab
*/
export const BulkManagementAlerts = ({ bulkImportError, uploadSuccess }) => (
export const BulkManagementAlerts = ({
bulkImportError,
uploadSuccess,
}) => (
<>
<Alert
variant="danger"
@@ -28,7 +30,7 @@ export const BulkManagementAlerts = ({ bulkImportError, uploadSuccess }) => (
show={uploadSuccess}
dismissible={false}
>
{messages.successDialog}
<FormattedMessage {...messages.successDialog} />
</Alert>
</>
);

View File

@@ -1,12 +1,17 @@
import React from 'react';
import { shallow } from 'enzyme';
import { Alert } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import * as appConstants from 'data/constants/app';
import messages from './messages';
import { BulkManagementAlerts, mapStateToProps } from './BulkManagementAlerts';
jest.mock('@edx/frontend-platform/i18n', () => ({
defineMessages: m => m,
FormattedMessage: () => 'FormattedMessage',
}));
jest.mock('@edx/paragon', () => ({
Alert: () => 'Alert',
}));
@@ -61,8 +66,8 @@ describe('BulkManagementAlerts', () => {
});
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).children().getElement()).toEqual(
<FormattedMessage {...messages.successDialog} />,
);
expect(el.childAt(1).props().show).toEqual(true);
});

View File

@@ -3,6 +3,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import {
Button,
Form,
@@ -10,11 +12,9 @@ import {
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;
import messages from './messages';
/**
* <FileUploadForm />
@@ -56,14 +56,15 @@ export class FileUploadForm extends React.Component {
}
render() {
const { gradeExportUrl } = this.props;
return (
<>
<Form action={this.props.gradeExportUrl} method="post">
<Form action={gradeExportUrl} method="post">
<FormGroup controlId="csv">
<FormControl
className="d-none"
type="file"
label={csvUploadLabel}
label={<FormattedMessage {...messages.csvUploadLabel} />}
onChange={this.handleFileInputChange}
ref={this.fileInputRef}
/>
@@ -71,7 +72,7 @@ export class FileUploadForm extends React.Component {
</Form>
<Button variant="primary" onClick={this.handleClickImportGrades}>
{importBtnText}
<FormattedMessage {...messages.importBtnText} />
</Button>
</>
);

View File

@@ -1,23 +1,25 @@
/* eslint-disable import/no-named-as-default */
import React from 'react';
import { shallow, mount } from 'enzyme';
import { shallow } from 'enzyme';
import TestRenderer from 'react-test-renderer';
import {
Button,
Form,
FormControl,
FormGroup,
} from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
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;
import messages from './messages';
jest.mock('@edx/frontend-platform/i18n', () => ({
defineMessages: m => m,
FormattedMessage: () => 'FormattedMessage',
}));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
@@ -39,10 +41,16 @@ jest.mock('data/thunkActions', () => ({
jest.mock('./BulkManagementAlerts', () => 'BulkManagementAlerts');
jest.mock('./ResultsSummary', () => 'ResultsSummary');
const mockRef = { click: jest.fn(), files: [] };
describe('FileUploadForm', () => {
beforeEach(() => {
mockRef.click.mockClear();
});
describe('component', () => {
let props;
let el;
let inst;
beforeEach(() => {
props = {
gradeExportUrl: 'fakeUrl',
@@ -71,87 +79,105 @@ describe('FileUploadForm', () => {
});
describe('render', () => {
beforeEach(() => {
el = mount(<FileUploadForm {...props} />);
el = TestRenderer.create(
<FileUploadForm {...props} />,
{ createNodeMock: () => mockRef },
);
inst = el.root;
});
describe('alert form', () => {
let form;
beforeEach(() => {
form = el.find(Form);
form = inst.findByType(Form);
});
test('post action points to gradeExportUrl', () => {
expect(form.props().action).toEqual(props.gradeExportUrl);
expect(form.props().method).toEqual('post');
expect(form.props.action).toEqual(props.gradeExportUrl);
expect(form.props.method).toEqual('post');
});
describe('file input', () => {
let formGroup;
beforeEach(() => {
formGroup = el.find(FormGroup);
formGroup = inst.findByType(FormGroup);
});
test('group with controlId="csv"', () => {
expect(formGroup.props().controlId).toEqual('csv');
expect(formGroup.props.controlId).toEqual('csv');
});
test('file control with onChange from handleFileInputChange', () => {
const control = el.find(FormControl);
const control = inst.findByType(FormControl);
expect(
control.props().onChange,
).toEqual(el.instance().handleFileInputChange);
control.props.onChange,
).toEqual(el.getInstance().handleFileInputChange);
});
test('fileInputRef points to control', () => {
expect(el.find(FormControl).getElement().ref).toBe(el.instance().fileInputRef);
expect(
// eslint-disable-next-line no-underscore-dangle
inst.findByType(FormControl)._fiber.ref,
).toEqual(el.getInstance().fileInputRef);
});
});
});
describe('import button', () => {
let btn;
beforeEach(() => {
btn = el.find(Button);
btn = inst.findByType(Button);
});
test('handleClickImportGrade on click', () => {
expect(btn.props().onClick).toEqual(el.instance().handleClickImportGrades);
expect(btn.props.onClick).toEqual(el.getInstance().handleClickImportGrades);
});
test('text from messages.importBtn', () => {
expect(btn.children().text()).toEqual(messages.importBtnText);
const messageEl = btn.findByType(FormattedMessage);
expect(messageEl.props).toEqual(messages.importBtnText);
});
});
});
describe('fileInput helper', () => {
test('links to fileInputRef.current', () => {
el = TestRenderer.create(
<FileUploadForm {...props} />,
{ createNodeMock: () => mockRef },
);
expect(el.getInstance().fileInput).not.toEqual(undefined);
expect(el.getInstance().fileInput).toEqual(el.getInstance().fileInputRef.current);
});
});
describe('behavior', () => {
let fileInput;
beforeEach(() => {
el = mount(<FileUploadForm {...props} />);
fileInput = jest.spyOn(el.instance(), 'fileInput', 'get');
el = TestRenderer.create(
<FileUploadForm {...props} />,
{ createNodeMock: () => mockRef },
);
fileInput = jest.spyOn(el.getInstance(), 'fileInput', 'get');
});
describe('handleFileInputChange', () => {
it('does nothing (does not fail) if fileInput has not loaded', () => {
fileInput.mockReturnValue(null);
el.instance().handleClickImportGrades();
el.getInstance().handleClickImportGrades();
expect(mockRef.click).not.toHaveBeenCalled();
});
it('calls fileInput.click if is loaded', () => {
const click = jest.fn();
fileInput.mockReturnValue({ click });
el.instance().handleClickImportGrades();
expect(click).toHaveBeenCalled();
el.getInstance().handleClickImportGrades();
expect(mockRef.click).toHaveBeenCalled();
});
});
describe('handleClickImportGrades', () => {
it('does nothing if file input has not loaded with files', () => {
fileInput.mockReturnValue(null);
el.instance().handleFileInputChange();
el.getInstance().handleFileInputChange();
expect(props.submitFileUploadFormData).not.toHaveBeenCalled();
fileInput.mockReturnValue({ files: [] });
el.instance().handleFileInputChange();
el.getInstance().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);
jest.spyOn(el.getInstance(), 'formData', 'get').mockReturnValue(formData);
const submit = jest.fn(() => ({ then: (thenCB) => { thenCB(); } }));
el.setProps({
submitFileUploadFormData: submit,
});
el.instance().handleFileInputChange();
el.update(<FileUploadForm {...props} submitFileUploadFormData={submit} />);
el.getInstance().handleFileInputChange();
expect(submit).toHaveBeenCalledWith(formData);
expect(el.instance().fileInput.value).toEqual(null);
expect(el.getInstance().fileInput.value).toEqual(null);
});
});
describe('formData', () => {
@@ -161,7 +187,7 @@ describe('FileUploadForm', () => {
fileInput.mockReturnValue({ files: [file], value });
const expected = new FormData();
expected.append('csv', file);
expect([...el.instance().formData.entries()]).toEqual([...expected.entries()]);
expect([...el.getInstance().formData.entries()]).toEqual([...expected.entries()]);
});
});
});

View File

@@ -3,11 +3,14 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Table } from '@edx/paragon';
import { bulkManagementColumns, messages } from 'data/constants/app';
import { bulkManagementColumns } from 'data/constants/app';
import selectors from 'data/selectors';
import ResultsSummary from './ResultsSummary';
import messages from './messages';
export const mapHistoryRows = ({
resultsSummary,
@@ -21,19 +24,19 @@ export const mapHistoryRows = ({
...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 }) => (
export const HistoryTable = ({
bulkManagementHistory,
}) => (
<>
<p>
{hints[0]}
<FormattedMessage {...messages.hint1} />
<br />
{hints[1]}
<FormattedMessage {...messages.hint2} />
</p>
<Table
@@ -55,7 +58,6 @@ HistoryTable.propTypes = {
timeUploaded: PropTypes.string.isRequired,
resultsSummary: PropTypes.shape({
rowId: PropTypes.number.isRequired,
courseId: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
}),
})),

View File

@@ -2,13 +2,19 @@
import React from 'react';
import { shallow } from 'enzyme';
import { Table } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import { bulkManagementColumns, messages } from 'data/constants/app';
import { bulkManagementColumns } from 'data/constants/app';
import ResultsSummary from './ResultsSummary';
import { HistoryTable, mapStateToProps } from './HistoryTable';
import messages from './messages';
jest.mock('@edx/frontend-platform/i18n', () => ({
defineMessages: m => m,
FormattedMessage: () => 'FormattedMessage',
}));
jest.mock('@edx/paragon', () => ({
Table: () => 'Table',
}));
@@ -61,9 +67,9 @@ describe('HistoryTable', () => {
});
test('hints with break in between', () => {
const hints = el.find('p');
expect(hints.childAt(0).text()).toEqual(messages.BulkManagementTab.hints[0]);
expect(hints.childAt(0).getElement()).toEqual(<FormattedMessage {...messages.hint1} />);
expect(hints.childAt(1).is('br')).toEqual(true);
expect(hints.childAt(2).text()).toEqual(messages.BulkManagementTab.hints[1]);
expect(hints.childAt(2).getElement()).toEqual(<FormattedMessage {...messages.hint2} />);
});
describe('history table', () => {
let table;

View File

@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import { Hyperlink, Icon } from '@edx/paragon';
import { Download } from '@edx/paragon/icons';
import { bulkGradesUrlByCourseAndRow } from 'data/constants/api';
import lms from 'data/services/lms';
/**
* <ResultsSummary {...{ courseId, rowId, text }} />
@@ -15,12 +15,11 @@ import { bulkGradesUrlByCourseAndRow } from 'data/constants/api';
* @param {string} text - summary string
*/
const ResultsSummary = ({
courseId,
rowId,
text,
}) => (
<Hyperlink
href={bulkGradesUrlByCourseAndRow(courseId, rowId)}
href={lms.urls.bulkGradesUrlByRow(rowId)}
destination="www.edx.org"
target="_blank"
rel="noopener noreferrer"
@@ -32,7 +31,6 @@ const ResultsSummary = ({
);
ResultsSummary.propTypes = {
courseId: PropTypes.string.isRequired,
rowId: PropTypes.number.isRequired,
text: PropTypes.string.isRequired,
};

View File

@@ -4,7 +4,7 @@ import { shallow } from 'enzyme';
import { Icon } from '@edx/paragon';
import { Download } from '@edx/paragon/icons';
import * as api from 'data/constants/api';
import lms from 'data/services/lms';
import ResultsSummary from './ResultsSummary';
jest.mock('@edx/paragon', () => ({
@@ -14,13 +14,14 @@ jest.mock('@edx/paragon', () => ({
jest.mock('@edx/paragon/icons', () => ({
Download: 'DownloadIcon',
}));
jest.mock('data/constants/api', () => ({
bulkGradesUrlByCourseAndRow: jest.fn((courseId, rowId) => ({ url: { courseId, rowId } })),
jest.mock('data/services/lms', () => ({
urls: {
bulkGradesUrlByRow: jest.fn((rowId) => ({ url: { rowId } })),
},
}));
describe('ResultsSummary component', () => {
const props = {
courseId: 'classy',
rowId: 42,
text: 'texty',
};
@@ -41,7 +42,7 @@ describe('ResultsSummary component', () => {
expect(el.props().rel).toEqual('noopener noreferrer');
});
test('Hyperlink has href to bulkGradesUrl', () => {
expect(el.props().href).toEqual(api.bulkGradesUrlByCourseAndRow(props.courseId, props.rowId));
expect(el.props().href).toEqual(lms.urls.bulkGradesUrlByRow(props.rowId));
});
test('displays Download Icon and text', () => {
const icon = el.childAt(0);

View File

@@ -12,7 +12,11 @@ exports[`BulkManagementAlerts component no errer, no upload success snapshot - b
show={false}
variant="success"
>
CSV processing. File uploads may take several minutes to complete.
<FormattedMessage
defaultMessage="CSV processing. File uploads may take several minutes to complete."
description="Success Dialog message in BulkManagement Tab File Upload Form"
id="gradebook.BulkManagementTab.successDialog"
/>
</Alert>
</Fragment>
`;
@@ -31,7 +35,11 @@ exports[`BulkManagementAlerts component no errer, no upload success snapshot - d
show={true}
variant="success"
>
CSV processing. File uploads may take several minutes to complete.
<FormattedMessage
defaultMessage="CSV processing. File uploads may take several minutes to complete."
description="Success Dialog message in BulkManagement Tab File Upload Form"
id="gradebook.BulkManagementTab.successDialog"
/>
</Alert>
</Fragment>
`;

View File

@@ -16,7 +16,13 @@ exports[`FileUploadForm component snapshot snapshot - loads export form w/ alert
<ForwardRef
as="input"
className="d-none"
label="Upload Grade CSV"
label={
<FormattedMessage
defaultMessage="Upload Grade CSV"
description="Button in BulkManagementTab Alerts"
id="gradebook.BulkManagementTab.csvUploadLabel"
/>
}
onChange={[MockFunction this.handleFileInputChange]}
plaintext={false}
type="file"
@@ -29,7 +35,11 @@ exports[`FileUploadForm component snapshot snapshot - loads export form w/ alert
onClick={[MockFunction this.handleClickImportGrades]}
variant="primary"
>
Import Grades
<FormattedMessage
defaultMessage="Import Grades"
description="Button in BulkManagement Tab File Upload Form"
id="gradebook.BulkManagementTab.importBtnText"
/>
</ForwardRef>
</React.Fragment>
`;

View File

@@ -44,9 +44,17 @@ Array [
exports[`HistoryTable component snapshot snapshot - loads hints display, formatted table 1`] = `
<Fragment>
<p>
Results appear in the table below.
<FormattedMessage
defaultMessage="Results appear in the table below."
description="Hint text on BulkManagement Tab History Table"
id="gradebook.BulkManagementTab.hint1"
/>
<br />
Grade processing may take a few seconds.
<FormattedMessage
defaultMessage="Grade processing may take a few seconds."
description="Hint text on BulkManagement Tab History Table"
id="gradebook.BulkManagementTab.hint2"
/>
</p>
<Table
className="table-striped"

View File

@@ -6,7 +6,6 @@ exports[`ResultsSummary component snapshot - safe hyperlink with bulkGradesUrl w
href={
Object {
"url": Object {
"courseId": "classy",
"rowId": 42,
},
}

View File

@@ -3,7 +3,11 @@
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.
<FormattedMessage
defaultMessage="Use this feature by downloading a CSV for bulk management, overriding grades locally, and coming back here to upload."
description="Heading text for BulkManagement Tab"
id="gradebook.BulkManagementTab.heading"
/>
</h4>
<BulkManagementAlerts />
<FileUploadForm />

View File

@@ -1,7 +1,8 @@
/* eslint-disable react/button-has-type, import/no-named-as-default */
import React from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { messages } from 'data/constants/app';
import messages from './messages';
import BulkManagementAlerts from './BulkManagementAlerts';
import FileUploadForm from './FileUploadForm';
import HistoryTable from './HistoryTable';
@@ -12,7 +13,7 @@ import HistoryTable from './HistoryTable';
*/
export const BulkManagementTab = () => (
<div>
<h4>{messages.BulkManagementTab.heading}</h4>
<h4><FormattedMessage {...(messages.heading)} /></h4>
<BulkManagementAlerts />
<FileUploadForm />
<HistoryTable />

View File

@@ -1,12 +1,13 @@
/* eslint-disable import/no-named-as-default */
import React from 'react';
import { shallow } from 'enzyme';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { messages } from 'data/constants/app';
import { BulkManagementTab } from '.';
import BulkManagementAlerts from './BulkManagementAlerts';
import FileUploadForm from './FileUploadForm';
import HistoryTable from './HistoryTable';
import messages from './messages';
jest.mock('./BulkManagementAlerts', () => 'BulkManagementAlerts');
jest.mock('./FileUploadForm', () => 'FileUploadForm');
@@ -30,7 +31,11 @@ describe('BulkManagementTab', () => {
});
test('heading - h4 loaded from messages', () => {
const heading = el.find('h4');
expect(heading.text()).toEqual(messages.BulkManagementTab.heading);
expect(heading.getElement()).toEqual((
<h4>
<FormattedMessage {...messages.heading} />
</h4>
));
});
test('heading, then alerts, then upload form, then table', () => {
expect(el.childAt(0).is('h4')).toEqual(true);

View File

@@ -0,0 +1,36 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
csvUploadLabel: {
id: 'gradebook.BulkManagementTab.csvUploadLabel',
defaultMessage: 'Upload Grade CSV',
description: 'Button in BulkManagementTab Alerts',
},
heading: {
id: 'gradebook.BulkManagementTab.heading',
defaultMessage: 'Use this feature by downloading a CSV for bulk management, overriding grades locally, and coming back here to upload.',
description: 'Heading text for BulkManagement Tab',
},
hint1: {
id: 'gradebook.BulkManagementTab.hint1',
defaultMessage: 'Results appear in the table below.',
description: 'Hint text on BulkManagement Tab History Table',
},
hint2: {
id: 'gradebook.BulkManagementTab.hint2',
defaultMessage: 'Grade processing may take a few seconds.',
description: 'Hint text on BulkManagement Tab History Table',
},
importBtnText: {
id: 'gradebook.BulkManagementTab.importBtnText',
defaultMessage: 'Import Grades',
description: 'Button in BulkManagement Tab File Upload Form',
},
successDialog: {
id: 'gradebook.BulkManagementTab.successDialog',
defaultMessage: 'CSV processing. File uploads may take several minutes to complete.',
description: 'Success Dialog message in BulkManagement Tab File Upload Form',
},
});
export default messages;

View File

@@ -7,7 +7,13 @@ exports[`AssignmentFilter Component snapshots basic snapshot 1`] = `
<SelectGroup
disabled={false}
id="assignment"
label="Assignment"
label={
<FormattedMessage
defaultMessage="Assignment"
description="Assignment filter select label in Gradebook Filters"
id="gradebook.GradebookFilters.assignmentFilterLabel"
/>
}
onChange={[MockFunction handleChange]}
options={
Array [

View File

@@ -3,10 +3,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import actions from 'data/actions';
import thunkActions from 'data/thunkActions';
import messages from '../messages';
import SelectGroup from '../SelectGroup';
const { fetchGradesIfAssignmentGradeFiltersSet } = thunkActions.grades;
@@ -46,7 +49,7 @@ export class AssignmentFilter extends React.Component {
<div className="student-filters">
<SelectGroup
id="assignment"
label="Assignment"
label={<FormattedMessage {...messages.assignment} />}
value={this.props.selectedAssignment}
onChange={this.handleChange}
disabled={this.props.assignmentFilterOptions.length === 0}
@@ -64,7 +67,6 @@ AssignmentFilter.defaultProps = {
AssignmentFilter.propTypes = {
updateQueryParams: PropTypes.func.isRequired,
// redux
assignmentFilterOptions: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string,

View File

@@ -69,7 +69,6 @@ describe('AssignmentFilter', () => {
el = mount(<AssignmentFilter {...props} />);
el.instance().handleChange(event);
});
it('calls props.updateAssignmentFilter with selection', () => {
expect(props.updateAssignmentFilter).toHaveBeenCalledWith({
label: newAssgn,
@@ -87,6 +86,30 @@ describe('AssignmentFilter', () => {
const method = props.fetchGradesIfAssignmentGradeFiltersSet;
expect(method).toHaveBeenCalledWith();
});
describe('no selected option', () => {
const value = 'fake';
beforeEach(() => {
el = mount(<AssignmentFilter {...props} />);
el.instance().handleChange({ target: { value } });
});
it('calls props.updateAssignmentFilter with selection', () => {
expect(props.updateAssignmentFilter).toHaveBeenCalledWith({
label: value,
type: undefined,
id: undefined,
});
});
it('calls props.updateQueryParams with selected assignment id',
() => {
expect(props.updateQueryParams).toHaveBeenCalledWith({
assignment: undefined,
});
});
it('calls props.fetchGradesIfAssignmentGradeFiltersSet', () => {
const method = props.fetchGradesIfAssignmentGradeFiltersSet;
expect(method).toHaveBeenCalledWith();
});
});
});
});
describe('snapshots', () => {

View File

@@ -7,14 +7,26 @@ exports[`AssignmentGradeFilter Component snapshots buttons and groups disabled i
<PercentGroup
disabled={true}
id="assignmentGradeMin"
label="Min Grade"
label={
<FormattedMessage
defaultMessage="Min Grade"
description="Min-grade filter select label in Gradebook Filters"
id="gradebook.GradebookFilters.minGradeFilterLabel"
/>
}
onChange={[MockFunction handleSetMin]}
value="2"
/>
<PercentGroup
disabled={true}
id="assignmentGradeMax"
label="Max Grade"
label={
<FormattedMessage
defaultMessage="Max Grade"
description="Max-grade filter select label in Gradebook Filters"
id="gradebook.GradebookFilters.maxGradeFilterLabel"
/>
}
onChange={[MockFunction handleSetMax]}
value="98"
/>
@@ -42,14 +54,26 @@ exports[`AssignmentGradeFilter Component snapshots smoke test 1`] = `
<PercentGroup
disabled={false}
id="assignmentGradeMin"
label="Min Grade"
label={
<FormattedMessage
defaultMessage="Min Grade"
description="Min-grade filter select label in Gradebook Filters"
id="gradebook.GradebookFilters.minGradeFilterLabel"
/>
}
onChange={[MockFunction handleSetMin]}
value="2"
/>
<PercentGroup
disabled={false}
id="assignmentGradeMax"
label="Max Grade"
label={
<FormattedMessage
defaultMessage="Max Grade"
description="Max-grade filter select label in Gradebook Filters"
id="gradebook.GradebookFilters.maxGradeFilterLabel"
/>
}
onChange={[MockFunction handleSetMax]}
value="98"
/>

View File

@@ -3,12 +3,14 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import selectors from 'data/selectors';
import actions from 'data/actions';
import thunkActions from 'data/thunkActions';
import messages from '../messages';
import PercentGroup from '../PercentGroup';
export class AssignmentGradeFilter extends React.Component {
@@ -34,19 +36,21 @@ export class AssignmentGradeFilter extends React.Component {
}
render() {
const { assignmentGradeMin, assignmentGradeMax } = this.props.localAssignmentLimits;
const {
localAssignmentLimits: { assignmentGradeMax, assignmentGradeMin },
} = this.props;
return (
<div className="grade-filter-inputs">
<PercentGroup
id="assignmentGradeMin"
label="Min Grade"
label={<FormattedMessage {...messages.minGrade} />}
value={assignmentGradeMin}
disabled={!this.props.selectedAssignment}
onChange={this.handleSetMin}
/>
<PercentGroup
id="assignmentGradeMax"
label="Max Grade"
label={<FormattedMessage {...messages.maxGrade} />}
value={assignmentGradeMax}
disabled={!this.props.selectedAssignment}
onChange={this.handleSetMax}

View File

@@ -7,7 +7,13 @@ exports[`AssignmentTypeFilter Component snapshots SelectGroup disabled if no ass
<SelectGroup
disabled={true}
id="assignment-types"
label="Assignment Types"
label={
<FormattedMessage
defaultMessage="Assignment Types"
description="Assignment Types filter select label in Gradebook Filters"
id="gradebook.GradebookFilters.assignmentTypesLabel"
/>
}
onChange={[MockFunction handleChange]}
options={
Array [
@@ -40,7 +46,13 @@ exports[`AssignmentTypeFilter Component snapshots smoke test 1`] = `
<SelectGroup
disabled={false}
id="assignment-types"
label="Assignment Types"
label={
<FormattedMessage
defaultMessage="Assignment Types"
description="Assignment Types filter select label in Gradebook Filters"
id="gradebook.GradebookFilters.assignmentTypesLabel"
/>
}
onChange={[MockFunction handleChange]}
options={
Array [

View File

@@ -3,9 +3,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import actions from 'data/actions';
import SelectGroup from '../SelectGroup';
import messages from '../messages';
export class AssignmentTypeFilter extends React.Component {
constructor(props) {
@@ -34,7 +38,7 @@ export class AssignmentTypeFilter extends React.Component {
<div className="student-filters">
<SelectGroup
id="assignment-types"
label="Assignment Types"
label={<FormattedMessage {...messages.assignmentTypes} />}
value={this.props.selectedAssignmentType}
onChange={this.handleChange}
disabled={this.props.assignmentFilterOptions.length === 0}

View File

@@ -7,13 +7,25 @@ exports[`CourseGradeFilter Component snapshots basic snapshot 1`] = `
>
<PercentGroup
id="minimum-grade"
label="Min Grade"
label={
<FormattedMessage
defaultMessage="Min Grade"
description="Min-grade filter select label in Gradebook Filters"
id="gradebook.GradebookFilters.minGradeFilterLabel"
/>
}
onChange={[MockFunction handleUpdateMin]}
value="5"
/>
<PercentGroup
id="maximum-grade"
label="Max Grade"
label={
<FormattedMessage
defaultMessage="Max Grade"
description="Max-grade filter select label in Gradebook Filters"
id="gradebook.GradebookFilters.maxGradeFilterLabel"
/>
}
onChange={[MockFunction handleUpdateMax]}
value="92"
/>

View File

@@ -2,13 +2,15 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
Button,
} from '@edx/paragon';
import { Button } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import actions from 'data/actions';
import thunkActions from 'data/thunkActions';
import messages from '../messages';
import PercentGroup from '../PercentGroup';
export class CourseGradeFilter extends React.Component {
@@ -41,19 +43,21 @@ export class CourseGradeFilter extends React.Component {
}
render() {
const { courseGradeMin, courseGradeMax } = this.props.localCourseLimits;
const {
localCourseLimits: { courseGradeMin, courseGradeMax },
} = this.props;
return (
<>
<div className="grade-filter-inputs">
<PercentGroup
id="minimum-grade"
label="Min Grade"
label={<FormattedMessage {...messages.minGrade} />}
value={courseGradeMin}
onChange={this.handleUpdateMin}
/>
<PercentGroup
id="maximum-grade"
label="Max Grade"
label={<FormattedMessage {...messages.maxGrade} />}
value={courseGradeMax}
onChange={this.handleUpdateMax}
/>

View File

@@ -30,7 +30,7 @@ PercentGroup.defaultProps = {
};
PercentGroup.propTypes = {
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
label: PropTypes.node.isRequired,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool,

View File

@@ -23,7 +23,7 @@ const SelectGroup = ({
);
SelectGroup.propTypes = {
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
label: PropTypes.node.isRequired,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool,

View File

@@ -3,10 +3,13 @@ import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import actions from 'data/actions';
import selectors from 'data/selectors';
import thunkActions from 'data/thunkActions';
import messages from '../messages';
import SelectGroup from '../SelectGroup';
export const optionFactory = ({ data, defaultOption, key }) => [
@@ -28,7 +31,7 @@ export class StudentGroupsFilter extends React.Component {
mapCohortsEntries() {
return optionFactory({
data: this.props.cohorts,
defaultOption: 'Cohort-All',
defaultOption: this.translate(messages.cohortAll),
key: 'id',
});
}
@@ -36,7 +39,7 @@ export class StudentGroupsFilter extends React.Component {
mapTracksEntries() {
return optionFactory({
data: this.props.tracks,
defaultOption: 'Track-All',
defaultOption: this.translate(messages.trackAll),
key: 'slug',
});
}
@@ -65,19 +68,23 @@ export class StudentGroupsFilter extends React.Component {
this.props.fetchGrades();
}
translate(message) {
return this.props.intl.formatMessage(message);
}
render() {
return (
<>
<SelectGroup
id="Tracks"
label="Tracks"
label={this.translate(messages.tracks)}
value={this.props.selectedTrackEntry.name}
onChange={this.updateTracks}
options={this.mapTracksEntries()}
/>
<SelectGroup
id="Cohorts"
label="Cohorts"
label={this.translate(messages.cohorts)}
value={this.props.selectedCohortEntry.name}
disabled={this.props.cohorts.length === 0}
onChange={this.updateCohorts}
@@ -100,6 +107,9 @@ StudentGroupsFilter.defaultProps = {
StudentGroupsFilter.propTypes = {
updateQueryParams: PropTypes.func.isRequired,
// injected
intl: intlShape.isRequired,
// redux
cohorts: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
@@ -139,4 +149,4 @@ export const mapDispatchToProps = {
updateTrack: actions.filters.update.track,
};
export default connect(mapStateToProps, mapDispatchToProps)(StudentGroupsFilter);
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(StudentGroupsFilter));

View File

@@ -63,6 +63,7 @@ describe('StudentGroupsFilter', () => {
beforeEach(() => {
props = {
...props,
intl: { formatMessage: (msg) => msg.defaultMessage },
cohortsByName: {
[props.cohorts[0].name]: props.cohorts[0],
[props.cohorts[1].name]: props.cohorts[1],

View File

@@ -22,7 +22,13 @@ exports[`GradebookFilters Component snapshots basic snapshot 1`] = `
<Collapsible
className="filter-group mb-3"
defaultOpen={true}
title="Assignments"
title={
<FormattedMessage
defaultMessage="Assignments"
description="Assignment filter group label in Gradebook Filters"
id="gradebook.GradebookFilters.assignmentsFilterLabel"
/>
}
>
<div>
<Connect(AssignmentTypeFilter)
@@ -39,7 +45,13 @@ exports[`GradebookFilters Component snapshots basic snapshot 1`] = `
<Collapsible
className="filter-group mb-3"
defaultOpen={true}
title="Overall Grade"
title={
<FormattedMessage
defaultMessage="Overall Grade"
description="Overall Grade filter group label in Gradebook Filters"
id="gradebook.GradebookFilters.overallGradeFilterLabel"
/>
}
>
<Connect(CourseGradeFilter)
updateQueryParams={[MockFunction]}
@@ -48,22 +60,38 @@ exports[`GradebookFilters Component snapshots basic snapshot 1`] = `
<Collapsible
className="filter-group mb-3"
defaultOpen={true}
title="Student Groups"
title={
<FormattedMessage
defaultMessage="Student Groups"
description="Student Groups filter group label in Gradebook Filters"
id="gradebook.GradebookFilters.studentGroupsFilterLabel"
/>
}
>
<Connect(StudentGroupsFilter)
<InjectIntl(ShimmedIntlComponent)
updateQueryParams={[MockFunction]}
/>
</Collapsible>
<Collapsible
className="filter-group mb-3"
defaultOpen={true}
title="Include Course Team Members"
title={
<FormattedMessage
defaultMessage="Include Course Team Members"
description="Include Course Team Members filter label in Gradebook Filters"
id="gradebook.GradebookFilters.includeCourseTeamMembersFilterLabel"
/>
}
>
<Checkbox
checked={true}
onChange={[MockFunction handleIncludeTeamMembersChange]}
>
Include Course Team Members
<FormattedMessage
defaultMessage="Include Course Team Members"
description="Include Course Team Members filter label in Gradebook Filters"
id="gradebook.GradebookFilters.includeCourseTeamMembersFilterLabel"
/>
</Checkbox>
</Collapsible>
</React.Fragment>

View File

@@ -10,11 +10,13 @@ import {
Form,
} from '@edx/paragon';
import { Close } from '@edx/paragon/icons';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import actions from 'data/actions';
import selectors from 'data/selectors';
import thunkActions from 'data/thunkActions';
import messages from './messages';
import AssignmentTypeFilter from './AssignmentTypeFilter';
import AssignmentFilter from './AssignmentFilter';
import AssignmentGradeFilter from './AssignmentGradeFilter';
@@ -39,13 +41,18 @@ export class GradebookFilters extends React.Component {
}
collapsibleGroup = (title, content) => (
<Collapsible title={title} defaultOpen className="filter-group mb-3">
<Collapsible
title={<FormattedMessage {...title} />}
defaultOpen
className="filter-group mb-3"
>
{content}
</Collapsible>
);
render() {
const {
intl,
updateQueryParams,
} = this.props;
return (
@@ -57,12 +64,12 @@ export class GradebookFilters extends React.Component {
onClick={this.props.closeMenu}
iconAs={Icon}
src={Close}
alt="Close Filters"
aria-label="Close Filters"
alt={intl.formatMessage(messages.closeFilters)}
aria-label={intl.formatMessage(messages.closeFilters)}
/>
</div>
{this.collapsibleGroup('Assignments', (
{this.collapsibleGroup(messages.assignments, (
<div>
<AssignmentTypeFilter updateQueryParams={updateQueryParams} />
<AssignmentFilter updateQueryParams={updateQueryParams} />
@@ -70,20 +77,20 @@ export class GradebookFilters extends React.Component {
</div>
))}
{this.collapsibleGroup('Overall Grade', (
{this.collapsibleGroup(messages.overallGrade, (
<CourseGradeFilter updateQueryParams={updateQueryParams} />
))}
{this.collapsibleGroup('Student Groups', (
{this.collapsibleGroup(messages.studentGroups, (
<StudentGroupsFilter updateQueryParams={updateQueryParams} />
))}
{this.collapsibleGroup('Include Course Team Members', (
{this.collapsibleGroup(messages.includeCourseTeamMembers, (
<Form.Checkbox
checked={this.state.includeCourseRoleMembers}
onChange={this.handleIncludeTeamMembersChange}
>
Include Course Team Members
<FormattedMessage {...messages.includeCourseTeamMembers} />
</Form.Checkbox>
))}
</>
@@ -95,6 +102,8 @@ GradebookFilters.defaultProps = {
};
GradebookFilters.propTypes = {
updateQueryParams: PropTypes.func.isRequired,
// injected
intl: intlShape.isRequired,
// redux
closeMenu: PropTypes.func.isRequired,
fetchGrades: PropTypes.func.isRequired,
@@ -112,4 +121,4 @@ export const mapDispatchToProps = {
updateIncludeCourseRoleMembers: actions.filters.update.includeCourseRoleMembers,
};
export default connect(mapStateToProps, mapDispatchToProps)(GradebookFilters);
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(GradebookFilters));

View File

@@ -0,0 +1,71 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
assignments: {
id: 'gradebook.GradebookFilters.assignmentsFilterLabel',
defaultMessage: 'Assignments',
description: 'Assignment filter group label in Gradebook Filters',
},
overallGrade: {
id: 'gradebook.GradebookFilters.overallGradeFilterLabel',
defaultMessage: 'Overall Grade',
description: 'Overall Grade filter group label in Gradebook Filters',
},
studentGroups: {
id: 'gradebook.GradebookFilters.studentGroupsFilterLabel',
defaultMessage: 'Student Groups',
description: 'Student Groups filter group label in Gradebook Filters',
},
includeCourseTeamMembers: {
id: 'gradebook.GradebookFilters.includeCourseTeamMembersFilterLabel',
defaultMessage: 'Include Course Team Members',
description: 'Include Course Team Members filter label in Gradebook Filters',
},
assignment: {
id: 'gradebook.GradebookFilters.assignmentFilterLabel',
defaultMessage: 'Assignment',
description: 'Assignment filter select label in Gradebook Filters',
},
assignmentTypes: {
id: 'gradebook.GradebookFilters.assignmentTypesLabel',
defaultMessage: 'Assignment Types',
description: 'Assignment Types filter select label in Gradebook Filters',
},
maxGrade: {
id: 'gradebook.GradebookFilters.maxGradeFilterLabel',
defaultMessage: 'Max Grade',
description: 'Max-grade filter select label in Gradebook Filters',
},
minGrade: {
id: 'gradebook.GradebookFilters.minGradeFilterLabel',
defaultMessage: 'Min Grade',
description: 'Min-grade filter select label in Gradebook Filters',
},
cohorts: {
id: 'gradebook.GradebookFilters.cohorts',
defaultMessage: 'Cohorts',
description: 'Cohorts filter select label in Gradebook Filters',
},
cohortAll: {
id: 'gradebook.GradebookFilters.cohortsAll',
defaultMessage: 'Cohort-All',
description: 'Cohorts filter select default in Gradebook Filters',
},
tracks: {
id: 'gradebook.GradebookFilters.tracks',
defaultMessage: 'Tracks',
description: 'Tracks filter select label in Gradebook Filters',
},
trackAll: {
id: 'gradebook.GradebookFilters.trackAll',
defaultMessage: 'Track-All',
description: 'Tracks filter select default in Gradebook Filters',
},
closeFilters: {
id: 'gradebook.GradebookFilters.closeFilters',
defaultMessage: 'Close Filters',
description: 'Button label for Close button in Gradebook Filters',
},
});
export default messages;

View File

@@ -46,6 +46,7 @@ describe('GradebookFilters', () => {
beforeEach(() => {
props = {
...props,
intl: { formatMessage: (msg) => msg.defaultMessage },
closeMenu: jest.fn().mockName('this.props.closeMenu'),
fetchGrades: jest.fn(),
updateIncludeCourseRoleMembers: jest.fn(),

View File

@@ -13,20 +13,31 @@ exports[`GradebookHeader component snapshots default values (grades frozen, cann
>
&lt;&lt;
</span>
Back to Dashboard
<FormattedMessage
defaultMessage="Back to Dashboard"
description="Button text to take user back to LMS dashboard in Gradebook Header"
id="gradebook.GradebookHeader.backButton"
/>
</a>
<h1>
Gradebook
<FormattedMessage
defaultMessage="Gradebook"
description="Top-level app title in Gradebook Header component"
id="gradebook.GradebookHeader.appLabel"
/>
</h1>
<h3>
fakeID
</h3>
<div
className="alert alert-warning"
role="alert"
>
You are not authorized to view the gradebook for this course.
<FormattedMessage
defaultMessage="You are not authorized to view the gradebook for this course."
description="Warning message in Gradebook Header when user is not allowed to view the app"
id="gradebook.GradebookHeader.unauthorizedWarning"
/>
</div>
</div>
`;
@@ -44,20 +55,31 @@ exports[`GradebookHeader component snapshots grades frozen, can view. grades fro
>
&lt;&lt;
</span>
Back to Dashboard
<FormattedMessage
defaultMessage="Back to Dashboard"
description="Button text to take user back to LMS dashboard in Gradebook Header"
id="gradebook.GradebookHeader.backButton"
/>
</a>
<h1>
Gradebook
<FormattedMessage
defaultMessage="Gradebook"
description="Top-level app title in Gradebook Header component"
id="gradebook.GradebookHeader.appLabel"
/>
</h1>
<h3>
fakeID
</h3>
<div
className="alert alert-warning"
role="alert"
>
The grades for this course are now frozen. Editing of grades is no longer allowed.
<FormattedMessage
defaultMessage="The grades for this course are now frozen. Editing of grades is no longer allowed."
description="Warning message in Gradebook Header for frozen messages"
id="gradebook.GradebookHeader.frozenWarning"
/>
</div>
</div>
`;
@@ -75,26 +97,41 @@ exports[`GradebookHeader component snapshots grades frozen, cannot view unauthor
>
&lt;&lt;
</span>
Back to Dashboard
<FormattedMessage
defaultMessage="Back to Dashboard"
description="Button text to take user back to LMS dashboard in Gradebook Header"
id="gradebook.GradebookHeader.backButton"
/>
</a>
<h1>
Gradebook
<FormattedMessage
defaultMessage="Gradebook"
description="Top-level app title in Gradebook Header component"
id="gradebook.GradebookHeader.appLabel"
/>
</h1>
<h3>
fakeID
</h3>
<div
className="alert alert-warning"
role="alert"
>
The grades for this course are now frozen. Editing of grades is no longer allowed.
<FormattedMessage
defaultMessage="The grades for this course are now frozen. Editing of grades is no longer allowed."
description="Warning message in Gradebook Header for frozen messages"
id="gradebook.GradebookHeader.frozenWarning"
/>
</div>
<div
className="alert alert-warning"
role="alert"
>
You are not authorized to view the gradebook for this course.
<FormattedMessage
defaultMessage="You are not authorized to view the gradebook for this course."
description="Warning message in Gradebook Header when user is not allowed to view the app"
id="gradebook.GradebookHeader.unauthorizedWarning"
/>
</div>
</div>
`;

View File

@@ -2,9 +2,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { configuration } from 'config';
import selectors from 'data/selectors';
import messages from './messages';
export class GradebookHeader extends React.Component {
lmsInstructorDashboardUrl = courseId => (
`${configuration.LMS_BASE_URL}/courses/${courseId}/instructor`
@@ -17,19 +21,22 @@ export class GradebookHeader extends React.Component {
href={this.lmsInstructorDashboardUrl(this.props.courseId)}
className="mb-3"
>
<span aria-hidden="true">{'<< '}</span> Back to Dashboard
<span aria-hidden="true">{'<< '}</span>
<FormattedMessage {...messages.backToDashboard} />
</a>
<h1>Gradebook</h1>
<h3> {this.props.courseId}</h3>
<h1>
<FormattedMessage {...messages.gradebook} />
</h1>
<h3>{this.props.courseId}</h3>
{this.props.areGradesFrozen
&& (
<div className="alert alert-warning" role="alert">
The grades for this course are now frozen. Editing of grades is no longer allowed.
<FormattedMessage {...messages.frozenWarning} />
</div>
)}
{(this.props.canUserViewGradebook === false) && (
<div className="alert alert-warning" role="alert">
You are not authorized to view the gradebook for this course.
<FormattedMessage {...messages.unauthorizedWarning} />
</div>
)}
</div>

View File

@@ -0,0 +1,26 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
backToDashboard: {
id: 'gradebook.GradebookHeader.backButton',
defaultMessage: 'Back to Dashboard',
description: 'Button text to take user back to LMS dashboard in Gradebook Header',
},
gradebook: {
id: 'gradebook.GradebookHeader.appLabel',
defaultMessage: 'Gradebook',
description: 'Top-level app title in Gradebook Header component',
},
frozenWarning: {
id: 'gradebook.GradebookHeader.frozenWarning',
defaultMessage: 'The grades for this course are now frozen. Editing of grades is no longer allowed.',
description: 'Warning message in Gradebook Header for frozen messages',
},
unauthorizedWarning: {
id: 'gradebook.GradebookHeader.unauthorizedWarning',
defaultMessage: 'You are not authorized to view the gradebook for this course.',
description: 'Warning message in Gradebook Header when user is not allowed to view the app',
},
});
export default messages;

View File

@@ -4,6 +4,11 @@ import { shallow } from 'enzyme';
import selectors from 'data/selectors';
import { GradebookHeader, mapStateToProps } from '.';
jest.mock('@edx/frontend-platform/i18n', () => ({
defineMessages: messages => messages,
FormattedMessage: 'FormattedMessage',
}));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {

View File

@@ -4,11 +4,14 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { StatefulButton, Icon } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { StrictDict } from 'utils';
import actions from 'data/actions';
import selectors from 'data/selectors';
import messages from './messages';
export const basicButtonProps = () => ({
variant: 'outline-primary',
icons: {
@@ -63,11 +66,11 @@ export class BulkManagementControls extends React.Component {
return this.props.showBulkManagement && (
<div>
<StatefulButton
{...this.buttonProps('Bulk Management')}
{...this.buttonProps(<FormattedMessage {...messages.bulkManagement} />)}
onClick={this.handleClickExportGrades}
/>
<StatefulButton
{...this.buttonProps('Interventions')}
{...this.buttonProps(<FormattedMessage {...messages.interventions} />)}
onClick={this.handleClickDownloadInterventions}
/>
</div>

View File

@@ -19,7 +19,7 @@ HistoryHeader.defaultProps = {
};
HistoryHeader.propTypes = {
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
label: PropTypes.node.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};

View File

@@ -2,7 +2,11 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import messages from './messages';
import HistoryHeader from './HistoryHeader';
/**
@@ -18,22 +22,22 @@ export const ModalHeaders = ({
<div>
<HistoryHeader
id="assignment"
label="Assignment"
label={<FormattedMessage {...messages.assignmentHeader} />}
value={modalState.assignmentName}
/>
<HistoryHeader
id="student"
label="Student"
label={<FormattedMessage {...messages.studentHeader} />}
value={modalState.updateUserName}
/>
<HistoryHeader
id="original-grade"
label="Original Grade"
label={<FormattedMessage {...messages.originalGradeHeader} />}
value={originalGrade}
/>
<HistoryHeader
id="current-grade"
label="Current Grade"
label={<FormattedMessage {...messages.currentGradeHeader} />}
value={currentGrade}
/>
</div>

View File

@@ -6,19 +6,35 @@ exports[`OverrideTable Component snapshots basic snapshot shows a row for each e
Array [
Object {
"key": "date",
"label": "Date",
"label": <FormattedMessage
defaultMessage="Date"
description="Edit Modal Override Table Date column header"
id="gradebook.GradesTab.EditModal.Overrides.dateHeader"
/>,
},
Object {
"key": "grader",
"label": "Grader",
"label": <FormattedMessage
defaultMessage="Grader"
description="Edit Modal Override Table Grader column header"
id="gradebook.GradesTab.EditModal.Overrides.graderHeader"
/>,
},
Object {
"key": "reason",
"label": "Reason",
"label": <FormattedMessage
defaultMessage="Reason"
description="Edit Modal Override Table Reason column header"
id="gradebook.GradesTab.EditModal.Overrides.reasonHeader"
/>,
},
Object {
"key": "adjustedGrade",
"label": "Adjusted grade",
"label": <FormattedMessage
defaultMessage="Adjusted grade"
description="Edit Modal Override Table Adjusted grade column header"
id="gradebook.GradesTab.EditModal.Overrides.adjustedGradeHeader"
/>,
},
]
}

View File

@@ -4,18 +4,15 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Table } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { gradeOverrideHistoryColumns as columns } from 'data/constants/app';
import selectors from 'data/selectors';
import messages from './messages';
import ReasonInput from './ReasonInput';
import AdjustedGradeInput from './AdjustedGradeInput';
const GRADE_OVERRIDE_HISTORY_COLUMNS = [
{ label: 'Date', key: 'date' },
{ label: 'Grader', key: 'grader' },
{ label: 'Reason', key: 'reason' },
{ label: 'Adjusted grade', key: 'adjustedGrade' },
];
/**
* <OverrideTable />
* Table containing previous grade override entries, and an "edit" row
@@ -31,7 +28,15 @@ export const OverrideTable = ({
}
return (
<Table
columns={GRADE_OVERRIDE_HISTORY_COLUMNS}
columns={[
{ label: <FormattedMessage {...messages.dateHeader} />, key: columns.date },
{ label: <FormattedMessage {...messages.graderHeader} />, key: columns.grader },
{ label: <FormattedMessage {...messages.reasonHeader} />, key: columns.reason },
{
label: <FormattedMessage {...messages.adjustedGradeHeader} />,
key: columns.adjustedGrade,
},
]}
data={[
...gradeOverrides,
{

View File

@@ -0,0 +1,26 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
adjustedGradeHeader: {
id: 'gradebook.GradesTab.EditModal.Overrides.adjustedGradeHeader',
defaultMessage: 'Adjusted grade',
description: 'Edit Modal Override Table Adjusted grade column header',
},
dateHeader: {
id: 'gradebook.GradesTab.EditModal.Overrides.dateHeader',
defaultMessage: 'Date',
description: 'Edit Modal Override Table Date column header',
},
graderHeader: {
id: 'gradebook.GradesTab.EditModal.Overrides.graderHeader',
defaultMessage: 'Grader',
description: 'Edit Modal Override Table Grader column header',
},
reasonHeader: {
id: 'gradebook.GradesTab.EditModal.Overrides.reasonHeader',
defaultMessage: 'Reason',
description: 'Edit Modal Override Table Reason column header',
},
});
export default messages;

View File

@@ -4,22 +4,46 @@ exports[`ModalHeaders Component snapshots gradeOverrideHistoryError is and empty
<div>
<HistoryHeader
id="assignment"
label="Assignment"
label={
<FormattedMessage
defaultMessage="Assignment"
description="Edit Modal Assignment header"
id="gradebook.GradesTab.EditModal.headers.assignment"
/>
}
value="Qwerty"
/>
<HistoryHeader
id="student"
label="Student"
label={
<FormattedMessage
defaultMessage="Student"
description="Edit Modal Student header"
id="gradebook.GradesTab.EditModal.headers.student"
/>
}
value="Uiop"
/>
<HistoryHeader
id="original-grade"
label="Original Grade"
label={
<FormattedMessage
defaultMessage="Original Grade"
description="Edit Modal Original Grade header"
id="gradebook.GradesTab.EditModal.headers.originalGrade"
/>
}
value={20}
/>
<HistoryHeader
id="current-grade"
label="Current Grade"
label={
<FormattedMessage
defaultMessage="Current Grade"
description="Edit Modal Current Grade header"
id="gradebook.GradesTab.EditModal.headers.currentGrade"
/>
}
value={2}
/>
</div>
@@ -29,22 +53,46 @@ exports[`ModalHeaders Component snapshots gradeOverrideHistoryError is empty and
<div>
<HistoryHeader
id="assignment"
label="Assignment"
label={
<FormattedMessage
defaultMessage="Assignment"
description="Edit Modal Assignment header"
id="gradebook.GradesTab.EditModal.headers.assignment"
/>
}
value="Qwerty"
/>
<HistoryHeader
id="student"
label="Student"
label={
<FormattedMessage
defaultMessage="Student"
description="Edit Modal Student header"
id="gradebook.GradesTab.EditModal.headers.student"
/>
}
value="Uiop"
/>
<HistoryHeader
id="original-grade"
label="Original Grade"
label={
<FormattedMessage
defaultMessage="Original Grade"
description="Edit Modal Original Grade header"
id="gradebook.GradesTab.EditModal.headers.originalGrade"
/>
}
value={20}
/>
<HistoryHeader
id="current-grade"
label="Current Grade"
label={
<FormattedMessage
defaultMessage="Current Grade"
description="Edit Modal Current Grade header"
id="gradebook.GradesTab.EditModal.headers.currentGrade"
/>
}
value={2}
/>
</div>

View File

@@ -13,10 +13,18 @@ exports[`EditMoal Component snapshots gradeOverrideHistoryError is and empty and
/>
<OverrideTable />
<div>
Showing most recent actions (max 5). To see more, please contact support.
<FormattedMessage
defaultMessage="Showing most recent actions (max 5). To see more, please contact support"
description="Edit Modal visibility hint message"
id="gradebook.GradesTab.EditModal.contactSupport"
/>
</div>
<div>
Note: Once you save, your changes will be visible to students.
<FormattedMessage
defaultMessage="Note: Once you save, your changes will be visible to students."
description="Edit Modal saved changes effect hint"
id="gradebook.GradesTab.EditModal.saveVisibility"
/>
</div>
</div>
}
@@ -26,14 +34,30 @@ exports[`EditMoal Component snapshots gradeOverrideHistoryError is and empty and
onClick={[MockFunction this.handleAdjustedGradeClick]}
variant="primary"
>
Save Grade
<FormattedMessage
defaultMessage="Save Grades"
description="Edit Modal Save button label"
id="gradebook.GradesTab.EditModal.saveGrade"
/>
</Button>,
]
}
closeText="Cancel"
closeText={
<FormattedMessage
defaultMessage="Cancel"
description="Edit Modal close button text"
id="gradebook.GradesTab.EditModal.closeText"
/>
}
onClose={[MockFunction this.closeAssignmentModal]}
open={true}
title="Edit Grades"
title={
<FormattedMessage
defaultMessage="Edit Grades"
description="Edit Modal title"
id="gradebook.GradesTab.EditModal.title"
/>
}
/>
`;
@@ -50,10 +74,18 @@ exports[`EditMoal Component snapshots gradeOverrideHistoryError is empty and ope
/>
<OverrideTable />
<div>
Showing most recent actions (max 5). To see more, please contact support.
<FormattedMessage
defaultMessage="Showing most recent actions (max 5). To see more, please contact support"
description="Edit Modal visibility hint message"
id="gradebook.GradesTab.EditModal.contactSupport"
/>
</div>
<div>
Note: Once you save, your changes will be visible to students.
<FormattedMessage
defaultMessage="Note: Once you save, your changes will be visible to students."
description="Edit Modal saved changes effect hint"
id="gradebook.GradesTab.EditModal.saveVisibility"
/>
</div>
</div>
}
@@ -63,13 +95,29 @@ exports[`EditMoal Component snapshots gradeOverrideHistoryError is empty and ope
onClick={[MockFunction this.handleAdjustedGradeClick]}
variant="primary"
>
Save Grade
<FormattedMessage
defaultMessage="Save Grades"
description="Edit Modal Save button label"
id="gradebook.GradesTab.EditModal.saveGrade"
/>
</Button>,
]
}
closeText="Cancel"
closeText={
<FormattedMessage
defaultMessage="Cancel"
description="Edit Modal close button text"
id="gradebook.GradesTab.EditModal.closeText"
/>
}
onClose={[MockFunction this.closeAssignmentModal]}
open={false}
title="Edit Grades"
title={
<FormattedMessage
defaultMessage="Edit Grades"
description="Edit Modal title"
id="gradebook.GradesTab.EditModal.title"
/>
}
/>
`;

View File

@@ -8,11 +8,13 @@ import {
Modal,
StatusAlert,
} from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import actions from 'data/actions';
import thunkActions from 'data/thunkActions';
import messages from './messages';
import OverrideTable from './OverrideTable';
import ModalHeaders from './ModalHeaders';
@@ -46,8 +48,8 @@ export class EditModal extends React.Component {
return (
<Modal
open={this.props.open}
title="Edit Grades"
closeText="Cancel"
title={<FormattedMessage {...messages.title} />}
closeText={<FormattedMessage {...messages.closeText} />}
body={(
<div>
<ModalHeaders />
@@ -58,15 +60,13 @@ export class EditModal extends React.Component {
dismissible={false}
/>
<OverrideTable />
<div>Showing most recent actions (max 5). To see more, please contact
support.
</div>
<div>Note: Once you save, your changes will be visible to students.</div>
<div><FormattedMessage {...messages.visibility} /></div>
<div><FormattedMessage {...messages.saveVisibility} /></div>
</div>
)}
buttons={[
<Button variant="primary" onClick={this.handleAdjustedGradeClick}>
Save Grade
<FormattedMessage {...messages.saveGrade} />
</Button>,
]}
onClose={this.closeAssignmentModal}

View File

@@ -0,0 +1,51 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
assignmentHeader: {
id: 'gradebook.GradesTab.EditModal.headers.assignment',
defaultMessage: 'Assignment',
description: 'Edit Modal Assignment header',
},
currentGradeHeader: {
id: 'gradebook.GradesTab.EditModal.headers.currentGrade',
defaultMessage: 'Current Grade',
description: 'Edit Modal Current Grade header',
},
originalGradeHeader: {
id: 'gradebook.GradesTab.EditModal.headers.originalGrade',
defaultMessage: 'Original Grade',
description: 'Edit Modal Original Grade header',
},
studentHeader: {
id: 'gradebook.GradesTab.EditModal.headers.student',
defaultMessage: 'Student',
description: 'Edit Modal Student header',
},
title: {
id: 'gradebook.GradesTab.EditModal.title',
defaultMessage: 'Edit Grades',
description: 'Edit Modal title',
},
closeText: {
id: 'gradebook.GradesTab.EditModal.closeText',
defaultMessage: 'Cancel',
description: 'Edit Modal close button text',
},
visibility: {
id: 'gradebook.GradesTab.EditModal.contactSupport',
defaultMessage: 'Showing most recent actions (max 5). To see more, please contact support',
description: 'Edit Modal visibility hint message',
},
saveVisibility: {
id: 'gradebook.GradesTab.EditModal.saveVisibility',
defaultMessage: 'Note: Once you save, your changes will be visible to students.',
description: 'Edit Modal saved changes effect hint',
},
saveGrade: {
id: 'gradebook.GradesTab.EditModal.saveGrade',
defaultMessage: 'Save Grades',
description: 'Edit Modal Save button label',
},
});
export default messages;

View File

@@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Button } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
@@ -15,7 +16,6 @@ import selectors from 'data/selectors';
* @param {string} filterName - api filter name (for redux connector)
*/
export const FilterBadge = ({
handleClose,
config: {
displayName,
isDefault,
@@ -23,11 +23,15 @@ export const FilterBadge = ({
value,
connectedFilters,
},
handleClose,
}) => !isDefault && (
<div>
<span className="badge badge-info">
<span>
{displayName}{!hideValue && `: ${value}`}
<FormattedMessage {...displayName} />
</span>
<span>
{!hideValue ? `: ${value}` : ''}
</span>
<Button
className="btn-info"
@@ -48,7 +52,9 @@ FilterBadge.propTypes = {
// redux
config: PropTypes.shape({
connectedFilters: PropTypes.arrayOf(PropTypes.string),
displayName: PropTypes.string.isRequired,
displayName: PropTypes.shape({
defaultMessage: PropTypes.string,
}).isRequired,
isDefault: PropTypes.bool.isRequired,
hideValue: PropTypes.bool,
value: PropTypes.oneOfType([

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { shallow } from 'enzyme';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import selectors from 'data/selectors';
@@ -20,7 +21,9 @@ jest.mock('data/selectors', () => ({
describe('FilterBadge', () => {
describe('component', () => {
const config = {
displayName: 'a common name',
displayName: {
defaultMessage: 'a common name',
},
isDefault: false,
hideValue: false,
value: 'a common value',
@@ -58,7 +61,11 @@ describe('FilterBadge', () => {
expect(el).toMatchSnapshot();
});
it('shows displayName but not value in span', () => {
expect(el.find('span.badge').childAt(0).text()).toEqual(config.displayName);
expect(el.find('span.badge').childAt(0).getElement()).toEqual(
<span>
<FormattedMessage {...config.displayName} />
</span>,
);
});
it('calls a handleClose event for connected filters on button click', () => {
expect(el.find(Button).props().onClick).toEqual(handleClose(config.connectedFilters));
@@ -72,8 +79,15 @@ describe('FilterBadge', () => {
expect(el).toMatchSnapshot();
});
it('shows displayName and value in span', () => {
expect(el.find('span.badge').childAt(0).text()).toEqual(
`${config.displayName}: ${config.value}`,
expect(el.find('span.badge').childAt(0).getElement()).toEqual(
<span>
<FormattedMessage {...config.displayName} />
</span>,
);
expect(el.find('span.badge').childAt(1).getElement()).toEqual(
<span>
{`: ${config.value}`}
</span>,
);
});
it('calls a handleClose event for connected filters on button click', () => {

View File

@@ -8,7 +8,11 @@ exports[`FilterBadge component with non-default value (active) if hideValue is f
className="badge badge-info"
>
<span>
a common name
<FormattedMessage
defaultMessage="a common name"
/>
</span>
<span>
: a common value
</span>
<Button
@@ -40,8 +44,11 @@ exports[`FilterBadge component with non-default value (active) if hideValue is t
className="badge badge-info"
>
<span>
a common name
<FormattedMessage
defaultMessage="a common name"
/>
</span>
<span />
<Button
aria-label="close"
className="btn-info"

View File

@@ -7,8 +7,9 @@ import {
OverlayTrigger,
Tooltip,
} from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Headings } from 'data/constants/grades';
import messages from './messages';
export const totalGradePercentageMessage = 'Total Grade values are always displayed as a percentage.';
@@ -23,12 +24,21 @@ const TotalGradeLabelReplacement = () => (
trigger={['hover', 'focus']}
key="left-basic"
placement="left"
overlay={(<Tooltip id="course-grade-tooltip">{totalGradePercentageMessage}</Tooltip>)}
overlay={(
<Tooltip id="course-grade-tooltip">
<FormattedMessage {...messages.totalGradePercentage} />
</Tooltip>
)}
>
<div>
{Headings.totalGrade}
<FormattedMessage {...messages.totalGradeHeading} />
<div id="courseGradeTooltipIcon">
<Icon className="fa fa-info-circle" screenReaderText={totalGradePercentageMessage} />
<Icon
className="fa fa-info-circle"
screenReaderText={(
<FormattedMessage {...messages.totalGradePercentage} />
)}
/>
</div>
</div>
</OverlayTrigger>
@@ -41,8 +51,12 @@ const TotalGradeLabelReplacement = () => (
*/
const UsernameLabelReplacement = () => (
<div>
<div>Username</div>
<div className="font-weight-normal student-key">Student Key*</div>
<div>
<FormattedMessage {...messages.usernameHeading} />
</div>
<div className="font-weight-normal student-key">
<FormattedMessage {...messages.studentKeyLabel} />
</div>
</div>
);

View File

@@ -4,7 +4,11 @@ exports[`LabelReplacements TotalGradeLabelReplacement displays overlay tooltip 1
<Tooltip
id="course-grade-tooltip"
>
Total Grade values are always displayed as a percentage.
<FormattedMessage
defaultMessage="Total Grade values are always displayed as a percentage"
description="Gradebook table message that total grades are displayed in percent format"
id="gradebook.GradesTab.table.totalGradePercentage"
/>
</Tooltip>
`;
@@ -16,7 +20,11 @@ exports[`LabelReplacements TotalGradeLabelReplacement snapshot 1`] = `
<Tooltip
id="course-grade-tooltip"
>
Total Grade values are always displayed as a percentage.
<FormattedMessage
defaultMessage="Total Grade values are always displayed as a percentage"
description="Gradebook table message that total grades are displayed in percent format"
id="gradebook.GradesTab.table.totalGradePercentage"
/>
</Tooltip>
}
placement="left"
@@ -28,13 +36,23 @@ exports[`LabelReplacements TotalGradeLabelReplacement snapshot 1`] = `
}
>
<div>
Total Grade (%)
<FormattedMessage
defaultMessage="Total Grade (%)"
description="Gradebook table total grade column header"
id="gradebook.GradesTab.table.headings.totalGrade"
/>
<div
id="courseGradeTooltipIcon"
>
<Icon
className="fa fa-info-circle"
screenReaderText="Total Grade values are always displayed as a percentage."
screenReaderText={
<FormattedMessage
defaultMessage="Total Grade values are always displayed as a percentage"
description="Gradebook table message that total grades are displayed in percent format"
id="gradebook.GradesTab.table.totalGradePercentage"
/>
}
/>
</div>
</div>
@@ -45,12 +63,20 @@ exports[`LabelReplacements TotalGradeLabelReplacement snapshot 1`] = `
exports[`LabelReplacements UsernameLabelReplacement snapshot 1`] = `
<div>
<div>
Username
<FormattedMessage
defaultMessage="Username"
description="Gradebook table username column header"
id="gradebook.GradesTab.table.headings.username"
/>
</div>
<div
className="font-weight-normal student-key"
>
Student Key*
<FormattedMessage
defaultMessage="Student Key*"
description="Gradebook table Student Key label"
id="gradebook.GradesTab.table.labels.studentKey"
/>
</div>
</div>
`;

View File

@@ -16,7 +16,11 @@ exports[`GradebookTable component snapshot - fields1 and 2 between email and tot
},
Object {
"key": "Email",
"label": "Email",
"label": <FormattedMessage
defaultMessage="Email"
description="Gradebook table email column header"
id="gradebook.GradesTab.table.headings.email"
/>,
},
Object {
"key": "field1",

View File

@@ -4,10 +4,12 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Table } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Headings } from 'data/constants/grades';
import selectors from 'data/selectors';
import { Headings } from 'data/constants/grades';
import messages from './messages';
import Fields from './Fields';
import LabelReplacements from './LabelReplacements';
import GradeButton from './GradeButton';
@@ -28,14 +30,17 @@ export class GradebookTable extends React.Component {
}
mapHeaders(heading) {
const replacement = {
[Headings.totalGrade]: <LabelReplacements.TotalGradeLabelReplacement />,
[Headings.username]: <LabelReplacements.UsernameLabelReplacement />,
}[heading];
return {
label: replacement !== undefined ? replacement : heading,
key: heading,
};
let label;
if (heading === Headings.totalGrade) {
label = <LabelReplacements.TotalGradeLabelReplacement />;
} else if (heading === Headings.username) {
label = <LabelReplacements.UsernameLabelReplacement />;
} else if (heading === Headings.email) {
label = <FormattedMessage {...messages.emailHeading} />;
} else {
label = heading;
}
return { label, key: heading };
}
mapRows(entry) {

View File

@@ -0,0 +1,36 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
emailHeading: {
id: 'gradebook.GradesTab.table.headings.email',
defaultMessage: 'Email',
description: 'Gradebook table email column header',
},
totalGradeHeading: {
id: 'gradebook.GradesTab.table.headings.totalGrade',
defaultMessage: 'Total Grade (%)',
description: 'Gradebook table total grade column header',
},
usernameHeading: {
id: 'gradebook.GradesTab.table.headings.username',
defaultMessage: 'Username',
description: 'Gradebook table username column header',
},
studentKeyLabel: {
id: 'gradebook.GradesTab.table.labels.studentKey',
defaultMessage: 'Student Key*',
description: 'Gradebook table Student Key label',
},
usernameLabel: {
id: 'gradebook.GradesTab.table.labels.username',
defaultMessage: 'Username',
description: 'Gradebook table username label',
},
totalGradePercentage: {
id: 'gradebook.GradesTab.table.totalGradePercentage',
defaultMessage: 'Total Grade values are always displayed as a percentage',
description: 'Gradebook table message that total grades are displayed in percent format',
},
});
export default messages;

View File

@@ -2,11 +2,13 @@ import React from 'react';
import { shallow } from 'enzyme';
import { Table } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import { Headings } from 'data/constants/grades';
import LabelReplacements from './LabelReplacements';
import Fields from './Fields';
import messages from './messages';
import { GradebookTable, mapStateToProps } from '.';
jest.mock('@edx/paragon', () => ({
@@ -94,7 +96,7 @@ describe('GradebookTable', () => {
test('email sets key and label from header', () => {
const heading = headings[1];
expect(heading.key).toEqual(Headings.email);
expect(heading.label).toEqual(Headings.email);
expect(heading.label).toEqual(<FormattedMessage {...messages.emailHeading} />);
});
test('subsections set key and label from header', () => {
expect(headings[2]).toEqual({ key: fields.field1, label: fields.field1 });

View File

@@ -19,7 +19,11 @@ exports[`PageButtons component snapshots buttons enabled with both endpoints pro
}
variant="outline-primary"
>
Previous Page
<FormattedMessage
defaultMessage="Previous Page"
description="Grades tab Previous Page button text"
id="gradebook.GradesTab.PageButtons.prevPage"
/>
</Button>
<Button
disabled={false}
@@ -31,7 +35,11 @@ exports[`PageButtons component snapshots buttons enabled with both endpoints pro
}
variant="outline-primary"
>
Next Page
<FormattedMessage
defaultMessage="Next Page"
description="Grades tab Next Page button text"
id="gradebook.GradesTab.PageButtons.nextPage"
/>
</Button>
</div>
`;
@@ -55,7 +63,11 @@ exports[`PageButtons component snapshots nextPage disabled if not provided 1`] =
}
variant="outline-primary"
>
Previous Page
<FormattedMessage
defaultMessage="Previous Page"
description="Grades tab Previous Page button text"
id="gradebook.GradesTab.PageButtons.prevPage"
/>
</Button>
<Button
disabled={true}
@@ -67,7 +79,11 @@ exports[`PageButtons component snapshots nextPage disabled if not provided 1`] =
}
variant="outline-primary"
>
Next Page
<FormattedMessage
defaultMessage="Next Page"
description="Grades tab Next Page button text"
id="gradebook.GradesTab.PageButtons.nextPage"
/>
</Button>
</div>
`;
@@ -91,7 +107,11 @@ exports[`PageButtons component snapshots prevPage disabled if not provided 1`] =
}
variant="outline-primary"
>
Previous Page
<FormattedMessage
defaultMessage="Previous Page"
description="Grades tab Previous Page button text"
id="gradebook.GradesTab.PageButtons.prevPage"
/>
</Button>
<Button
disabled={false}
@@ -103,7 +123,11 @@ exports[`PageButtons component snapshots prevPage disabled if not provided 1`] =
}
variant="outline-primary"
>
Next Page
<FormattedMessage
defaultMessage="Next Page"
description="Grades tab Next Page button text"
id="gradebook.GradesTab.PageButtons.nextPage"
/>
</Button>
</div>
`;

View File

@@ -3,9 +3,11 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import thunkActions from 'data/thunkActions';
import messages from './messages';
export class PageButtons extends React.Component {
constructor(props) {
@@ -34,7 +36,7 @@ export class PageButtons extends React.Component {
disabled={!this.props.prevPage}
onClick={this.getPrevGrades}
>
Previous Page
<FormattedMessage {...messages.prevPage} />
</Button>
<Button
style={{ margin: '20px' }}
@@ -42,7 +44,7 @@ export class PageButtons extends React.Component {
disabled={!this.props.nextPage}
onClick={this.getNextGrades}
>
Next Page
<FormattedMessage {...messages.nextPage} />
</Button>
</div>
);

View File

@@ -0,0 +1,16 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
prevPage: {
id: 'gradebook.GradesTab.PageButtons.prevPage',
defaultMessage: 'Previous Page',
description: 'Grades tab Previous Page button text',
},
nextPage: {
id: 'gradebook.GradesTab.PageButtons.nextPage',
defaultMessage: 'Next Page',
description: 'Grades tab Next Page button text',
},
});
export default messages;

View File

@@ -3,29 +3,37 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormControl, FormGroup, FormLabel } from '@edx/paragon';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import actions from 'data/actions';
import selectors from 'data/selectors';
import messages from './messages';
/**
* <ScoreViewInput />
* redux-connected select control for grade format (percent vs absolute)
*/
export const ScoreViewInput = ({ format, toggleFormat }) => (
export const ScoreViewInput = ({ format, intl, toggleFormat }) => (
<FormGroup controlId="ScoreView">
<FormLabel>Score View:</FormLabel>
<FormLabel><FormattedMessage {...messages.scoreView} />:</FormLabel>
<FormControl
as="select"
value={format}
onChange={toggleFormat}
>
<option value="percent">Percent</option>
<option value="absolute">Absolute</option>
<option value="percent">{intl.formatMessage(messages.percent)}</option>
<option value="absolute">{intl.formatMessage(messages.absolute)}</option>
</FormControl>
</FormGroup>
);
ScoreViewInput.defaultProps = {
format: 'percent',
};
ScoreViewInput.propTypes = {
format: PropTypes.string.isRequired,
// injected
intl: intlShape.isRequired,
// redux
format: PropTypes.string,
toggleFormat: PropTypes.func.isRequired,
};
@@ -37,4 +45,4 @@ export const mapDispatchToProps = {
toggleFormat: actions.grades.toggleGradeFormat,
};
export default connect(() => ({}), mapDispatchToProps)(ScoreViewInput);
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ScoreViewInput));

View File

@@ -35,6 +35,7 @@ describe('ScoreViewInput', () => {
let el;
beforeEach(() => {
props.toggleFormat = jest.fn();
props.intl = { formatMessage: (msg) => msg.defaultMessage };
el = shallow(<ScoreViewInput {...props} />);
});
const assertions = [

View File

@@ -3,11 +3,14 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button, Icon, SearchField } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import actions from 'data/actions';
import selectors from 'data/selectors';
import thunkActions from 'data/thunkActions';
import messages from './messages';
/**
* Controls for filtering the GradebookTable. Contains the "Edit Filters" button for opening the filter drawer
* as well as the search box for searching by username/email.
@@ -32,25 +35,25 @@ export class SearchControls extends React.Component {
render() {
return (
<>
<h4>Step 1: Filter the Grade Report</h4>
<h4><FormattedMessage {...messages.filterStepHeading} /></h4>
<div className="d-flex justify-content-between">
<Button
id="edit-filters-btn"
className="btn-primary align-self-start"
onClick={this.props.toggleFilterDrawer}
>
<Icon className="fa fa-filter" /> Edit Filters
<Icon className="fa fa-filter" /> <FormattedMessage {...messages.editFilters} />
</Button>
<div>
<SearchField
onSubmit={this.props.fetchGrades}
inputLabel="Search for a learner"
inputLabel={<FormattedMessage {...messages.searchLabel} />}
onChange={this.onChange}
onClear={this.onClear}
value={this.props.searchValue}
/>
<small className="form-text text-muted search-help-text">
Search by username, email, or student key
<FormattedMessage {...messages.searchHint} />
</small>
</div>
</div>

View File

@@ -3,12 +3,11 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { StatusAlert } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
import actions from 'data/actions';
export const maxCourseGradeInvalidMessage = 'Maximum course grade value must be between 0 and 100. ';
export const minCourseGradeInvalidMessage = 'Minimum course grade value must be between 0 and 100. ';
import messages from './messages';
export class StatusAlerts extends React.Component {
get isCourseGradeFilterAlertOpen() {
@@ -18,15 +17,24 @@ export class StatusAlerts extends React.Component {
);
}
get minValidityMessage() {
return (this.props.limitValidity.isMinValid)
? ''
: <FormattedMessage {...messages.minGradeInvalid} />;
}
get maxValidityMessage() {
return (this.props.limitValidity.isMaxValid)
? ''
: <FormattedMessage {...messages.maxGradeInvalid} />;
}
get courseGradeFilterAlertDialogText() {
let dialogText = '';
if (!this.props.limitValidity.isMinValid) {
dialogText += minCourseGradeInvalidMessage;
}
if (!this.props.limitValidity.isMaxValid) {
dialogText += maxCourseGradeInvalidMessage;
}
return dialogText;
return (
<>
{this.minValidityMessage}{this.maxValidityMessage}
</>
);
}
render() {
@@ -34,7 +42,7 @@ export class StatusAlerts extends React.Component {
<>
<StatusAlert
alertType="success"
dialog="The grade has been successfully edited. You may see a slight delay before updates appear in the Gradebook."
dialog={<FormattedMessage {...messages.editSuccessAlert} />}
onClose={this.props.handleCloseSuccessBanner}
open={this.props.showSuccessBanner}
/>

View File

@@ -1,14 +1,15 @@
import React from 'react';
import { shallow } from 'enzyme';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import actions from 'data/actions';
import selectors from 'data/selectors';
import messages from './messages';
import {
StatusAlerts,
mapDispatchToProps,
mapStateToProps,
maxCourseGradeInvalidMessage,
minCourseGradeInvalidMessage,
} from './StatusAlerts';
jest.mock('@edx/paragon', () => ({
@@ -77,18 +78,24 @@ describe('StatusAlerts', () => {
!isMinValid || !isMaxValid,
);
if (!isMaxValid) {
if (!isMinValid) {
expect(el.instance().courseGradeFilterAlertDialogText).toEqual(
<>
<FormattedMessage {...messages.minGradeInvalid} />
<FormattedMessage {...messages.maxGradeInvalid} />
</>,
);
} else {
expect(
el.instance().courseGradeFilterAlertDialogText,
// eslint-disable-next-line react/jsx-curly-brace-presence
).toEqual(<>{''}<FormattedMessage {...messages.maxGradeInvalid} /></>);
}
} else if (!isMinValid) {
expect(
el.instance().courseGradeFilterAlertDialogText,
).toEqual(
expect.stringContaining(maxCourseGradeInvalidMessage),
);
}
if (!isMinValid) {
expect(
el.instance().courseGradeFilterAlertDialogText,
).toEqual(
expect.stringContaining(minCourseGradeInvalidMessage),
);
// eslint-disable-next-line react/jsx-curly-brace-presence
).toEqual(<><FormattedMessage {...messages.minGradeInvalid} />{''}</>);
}
});
});

View File

@@ -3,6 +3,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import selectors from 'data/selectors';
/**
@@ -18,9 +20,15 @@ export const UsersLabel = ({
}
const bold = (val) => (<span className="font-weight-bold">{val}</span>);
return (
<>
Showing {bold(filteredUsersCount)} of {bold(totalUsersCount)} total learners
</>
<FormattedMessage
id="gradebook.GradesTab.usersVisibilityLabel'"
defaultMessage="Showing {filteredUsers} of {totalUsers} total learners"
description="Users visibility label"
values={{
filteredUsers: bold(filteredUsersCount),
totalUsers: bold(totalUsersCount),
}}
/>
);
};
UsersLabel.propTypes = {

View File

@@ -5,7 +5,12 @@ exports[`ScoreViewInput component snapshot - select box with percent and absolut
controlId="ScoreView"
>
<FormLabel>
Score View:
<FormattedMessage
defaultMessage="Score View"
description="Score format select dropdown label"
id="gradebook.GradesTab.scoreViewLabel"
/>
:
</FormLabel>
<FormControl
as="select"

View File

@@ -3,7 +3,11 @@
exports[`SearchControls Component Snapshots basic snapshot 1`] = `
<React.Fragment>
<h4>
Step 1: Filter the Grade Report
<FormattedMessage
defaultMessage="Step 1: Filter the Grade Report"
description="Filter controls container heading string"
id="gradebook.GradesTab.filterHeading"
/>
</h4>
<div
className="d-flex justify-content-between"
@@ -16,11 +20,22 @@ exports[`SearchControls Component Snapshots basic snapshot 1`] = `
<Icon
className="fa fa-filter"
/>
Edit Filters
<FormattedMessage
defaultMessage="Edit Filters"
description="Button text on Grades tab to open/close the Filters tab"
id="gradebook.GradesTab.editFilterLabel"
/>
</Button>
<div>
<SearchField
inputLabel="Search for a learner"
inputLabel={
<FormattedMessage
defaultMessage="Search for a learner"
description="Search description label"
id="gradebook.GradesTab.search.label"
/>
}
onChange={[MockFunction onChange]}
onClear={[MockFunction onClear]}
onSubmit={[MockFunction fetchGrades]}
@@ -29,7 +44,11 @@ exports[`SearchControls Component Snapshots basic snapshot 1`] = `
<small
className="form-text text-muted search-help-text"
>
Search by username, email, or student key
<FormattedMessage
defaultMessage="Search by username, email, or student key"
description="Search hint label"
id="gradebook.GradesTab.search.hint"
/>
</small>
</div>
</div>

View File

@@ -4,7 +4,13 @@ exports[`StatusAlerts snapshots basic snapshot 1`] = `
<React.Fragment>
<StatusAlert
alertType="success"
dialog="The grade has been successfully edited. You may see a slight delay before updates appear in the Gradebook."
dialog={
<FormattedMessage
defaultMessage="The grade has been successfully edited. You may see a slight delay before updates appear in the Gradebook."
description="Alert text for successful edit action"
id="gradebook.GradesTab.editSuccessAlert"
/>
}
onClose={[MockFunction handleCloseSuccessBanner]}
open={true}
/>

View File

@@ -1,19 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UsersLabel component snapshot - displays label with number of filtered users out of total 1`] = `
<Fragment>
Showing
<span
className="font-weight-bold"
>
23
</span>
of
<span
className="font-weight-bold"
>
140
</span>
total learners
</Fragment>
<FormattedMessage
defaultMessage="Showing {filteredUsers} of {totalUsers} total learners"
description="Users visibility label"
id="gradebook.GradesTab.usersVisibilityLabel'"
values={
Object {
"filteredUsers": <span
className="font-weight-bold"
>
23
</span>,
"totalUsers": <span
className="font-weight-bold"
>
140
</span>,
}
}
/>
`;

View File

@@ -9,7 +9,11 @@ exports[`GradesTab Component snapshots basic snapshot 1`] = `
/>
<StatusAlerts />
<h4>
Step 2: View or Modify Individual Grades
<FormattedMessage
defaultMessage="Step 2: View or Modify Individual Grades"
description="Alert text for invalid minimum course grade"
id="gradebook.GradesTab.gradebookStepHeading"
/>
</h4>
<UsersLabel />
<div
@@ -21,7 +25,12 @@ exports[`GradesTab Component snapshots basic snapshot 1`] = `
<GradebookTable />
<PageButtons />
<p>
* available for learners in the Master's track only
*
<FormattedMessage
defaultMessage="available for learners in the Master's track only"
description="Masters feature availability hint on Grades Tab"
id="gradebook.GradesTab.mastersHint"
/>
</p>
<EditModal />
</React.Fragment>

View File

@@ -3,6 +3,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import actions from 'data/actions';
import thunkActions from 'data/thunkActions';
@@ -17,6 +19,7 @@ import StatusAlerts from './StatusAlerts';
import SpinnerIcon from './SpinnerIcon';
import ScoreViewInput from './ScoreViewInput';
import UsersLabel from './UsersLabel';
import messages from './messages';
export class GradesTab extends React.Component {
constructor(props) {
@@ -43,7 +46,7 @@ export class GradesTab extends React.Component {
<FilterBadges handleClose={this.handleFilterBadgeClose} />
<StatusAlerts />
<h4>Step 2: View or Modify Individual Grades</h4>
<h4><FormattedMessage {...messages.gradebookStepHeading} /></h4>
<UsersLabel />
<div className="d-flex justify-content-between align-items-center mb-2">
@@ -54,7 +57,7 @@ export class GradesTab extends React.Component {
<GradebookTable />
<PageButtons />
<p>* available for learners in the Master&apos;s track only</p>
<p>* <FormattedMessage {...messages.mastersHint} /></p>
<EditModal />
</>
);

View File

@@ -0,0 +1,76 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
bulkManagement: {
id: 'gradebook.GradesTab.BulkManagementControls.bulkManagementLabel',
defaultMessage: 'Bulk Management',
description: 'Button text for bulk grades download control in GradesTab',
},
interventions: {
id: 'gradebook.GradesTab.BulkManagementControls.interventionsLabel',
defaultMessage: 'Interventions',
description: 'Button text for intervention report download control in GradesTab',
},
scoreView: {
id: 'gradebook.GradesTab.scoreViewLabel',
defaultMessage: 'Score View',
description: 'Score format select dropdown label',
},
absolute: {
id: 'gradebook.GradesTab.absoluteOption',
defaultMessage: 'Absolute',
description: 'Score format select dropdown option',
},
percent: {
id: 'gradebook.GradesTab.percentOption',
defaultMessage: 'Percent',
description: 'Score format select dropdown option',
},
filterStepHeading: {
id: 'gradebook.GradesTab.filterHeading',
defaultMessage: 'Step 1: Filter the Grade Report',
description: 'Filter controls container heading string',
},
editFilters: {
id: 'gradebook.GradesTab.editFilterLabel',
defaultMessage: 'Edit Filters',
description: 'Button text on Grades tab to open/close the Filters tab',
},
searchLabel: {
id: 'gradebook.GradesTab.search.label',
defaultMessage: 'Search for a learner',
description: 'Search description label',
},
searchHint: {
id: 'gradebook.GradesTab.search.hint',
defaultMessage: 'Search by username, email, or student key',
description: 'Search hint label',
},
editSuccessAlert: {
id: 'gradebook.GradesTab.editSuccessAlert',
defaultMessage: 'The grade has been successfully edited. You may see a slight delay before updates appear in the Gradebook.',
description: 'Alert text for successful edit action',
},
maxGradeInvalid: {
id: 'gradebook.GradesTab.maxCourseGradeInvalid',
defaultMessage: 'Maximum course grade must be between 0 and 100',
description: 'Alert text for invalid maximum course grade',
},
minGradeInvalid: {
id: 'gradebook.GradesTab.minCourseGradeInvalid',
defaultMessage: 'Minimum course grade must be between 0 and 100',
description: 'Alert text for invalid minimum course grade',
},
gradebookStepHeading: {
id: 'gradebook.GradesTab.gradebookStepHeading',
defaultMessage: 'Step 2: View or Modify Individual Grades',
description: 'Alert text for invalid minimum course grade',
},
mastersHint: {
id: 'gradebook.GradesTab.mastersHint',
defaultMessage: "available for learners in the Master's track only",
description: 'Masters feature availability hint on Grades Tab',
},
});
export default messages;

View File

@@ -6,6 +6,7 @@ import thunkActions from 'data/thunkActions';
import {
GradesTab,
mapStateToProps,
mapDispatchToProps,
} from '.';
@@ -78,6 +79,9 @@ describe('GradesTab', () => {
});
});
});
test('mapStateToProps is empty', () => {
expect(mapStateToProps({ some: 'state' })).toEqual({});
});
describe('mapDispatchToProps', () => {
describe('fetchGrades', () => {
test('from thunkActions.grades.fetchGrades', () => {

View File

@@ -1,19 +1,22 @@
import { createAction } from '@reduxjs/toolkit';
export const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: 'UTC',
};
export const timeOptions = {
hour: '2-digit',
minute: '2-digit',
timeZone: 'UTC',
timeZoneName: 'short',
};
const formatDateForDisplay = (inputDate) => {
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: 'UTC',
};
const timeOptions = {
hour: '2-digit',
minute: '2-digit',
timeZone: 'UTC',
timeZoneName: 'short',
};
return `${inputDate.toLocaleDateString('en-US', options)} at ${inputDate.toLocaleTimeString('en-US', timeOptions)}`;
const date = inputDate.toLocaleDateString('en-US', options);
const time = inputDate.toLocaleTimeString('en-US', timeOptions);
return `${date} at ${time}`;
};
const sortAlphaAsc = (gradeRowA, gradeRowB) => {

View File

@@ -0,0 +1,34 @@
import { createAction } from '@reduxjs/toolkit';
import * as utils from './utils';
jest.mock('@reduxjs/toolkit', () => ({
createAction: (key, ...args) => ({ action: key, args }),
}));
describe('redux action utils', () => {
describe('formatDateForDisplay', () => {
it('returns the datetime as a formatted string', () => {
expect(utils.formatDateForDisplay(new Date('Jun 3 2021 11:59 AM EDT'))).toEqual(
'June 3, 2021 at 03:59 PM UTC',
);
});
});
describe('sortAlphaAsc', () => {
it('returns sorting value (-1, 0, 1) by uppercase username', () => {
const sort = (v1, v2) => utils.sortAlphaAsc({ username: v1 }, { username: v2 });
expect(sort('aName', 'ANAme')).toEqual(0);
expect(sort('aName', 'laterName')).toEqual(-1);
expect(sort('laterName', 'aName')).toEqual(1);
});
});
describe('createActionFactory', () => {
it('returns an action creator with the data key', () => {
const dataKey = 'part-of-the-model';
const actionKey = 'an-action';
const args = ['some', 'args'];
expect(utils.createActionFactory(dataKey)(actionKey, ...args)).toEqual(
createAction(`${dataKey}/${actionKey}`, ...args),
);
});
});
});

View File

@@ -1,14 +0,0 @@
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}`
);

View File

@@ -1,4 +1,7 @@
import { StrictDict } from 'utils';
import { getConfig } from '@edx/frontend-platform';
export const routePath = `${getConfig().PUBLIC_PATH}:courseId`;
export const modalFieldKeys = StrictDict({
adjustedGradePossible: 'adjustedGradePossible',
@@ -49,6 +52,13 @@ export const bulkManagementColumns = [
},
];
export const gradeOverrideHistoryColumns = StrictDict({
adjustedGrade: 'adjustedGrade',
date: 'date',
grader: 'grader',
reason: 'reason',
});
/**
* Display strings for various app components.
* Note: this is a temporary storage location for these strings, before we put them in

View File

@@ -1,5 +1,7 @@
import { StrictDict } from 'utils';
import messages from './filters.messages';
export const filters = StrictDict({
assignment: 'assignment',
assignmentGrade: 'assignmentGrade',
@@ -10,6 +12,7 @@ export const filters = StrictDict({
courseGrade: 'courseGrade',
courseGradeMax: 'courseGradeMax',
courseGradeMin: 'courseGradeMin',
excludedCourseRoles: 'excludedCourseRoles',
includeCourseRoleMembers: 'includeCourseRoleMembers',
track: 'track',
});
@@ -28,34 +31,34 @@ const initialFilters = {
export const filterConfig = StrictDict({
[filters.assignment]: {
displayName: 'Assignment',
displayName: messages[filters.assignment],
connectedFilters: ['assignment', 'assignmentGradeMax', 'assignmentGradeMax'],
},
[filters.assignmentType]: {
displayName: 'Assignment Type',
displayName: messages[filters.assignmentType],
connectedFilters: ['assignmentType'],
},
[filters.assignmentGrade]: {
displayName: 'Assignment Grade',
filterOrder: ['courseGradeMin', 'courseGradeMax'],
connectedFilters: ['courseGradeMax', 'courseGradeMin'],
displayName: messages[filters.assignmentGrade],
filterOrder: ['assignmentGradeMin', 'assignmentGradeMax'],
connectedFilters: ['assignmentGradeMax', 'assignmentGradeMin'],
},
[filters.cohort]: {
displayName: 'Cohort',
displayName: messages[filters.cohort],
connectedFilters: ['cohort'],
},
[filters.courseGrade]: {
displayName: 'Course Grade',
displayName: messages[filters.courseGrade],
filterOrder: ['courseGradeMin', 'courseGradeMax'],
connectedFilters: ['courseGradeMax', 'courseGradeMin'],
},
[filters.includeCourseRoleMembers]: {
displayName: 'Includeing Course Team Members',
displayName: messages[filters.includeCourseRoleMembers],
connectedFilters: ['includeCourseRoleMembers'],
hideValue: true,
},
[filters.track]: {
displayName: 'Track',
displayName: messages[filters.track],
connectedFilters: ['track'],
},
});

View File

@@ -0,0 +1,41 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
assignment: {
id: 'gradebook.GradesTab.FilterBadges.assignment',
defaultMessage: 'Assignment',
description: 'Assignment FilterBadge label',
},
assignmentGrade: {
id: 'gradebook.GradesTab.FilterBadges.assignmentGrade',
defaultMessage: 'Assignment Grade',
description: 'Assignment Grade FilterBadge label',
},
assignmentType: {
id: 'gradebook.GradesTab.FilterBadges.assignmentType',
defaultMessage: 'Assignment Type',
description: 'Assignment Type FilterBadge label',
},
cohort: {
id: 'gradebook.GradesTab.FilterBadges.cohort',
defaultMessage: 'Cohort',
description: 'Cohort FilterBadge label',
},
courseGrade: {
id: 'gradebook.GradesTab.FilterBadges.courseGrade',
defaultMessage: 'Course Grade',
description: 'Course Grade FilterBadge label',
},
includeCourseRoleMembers: {
id: 'gradebook.GradesTab.FilterBadges.includeCourseRoleMembers',
defaultMessage: 'Include Course Team Members',
description: 'Include Course Team Members FilterBadge label',
},
track: {
id: 'gradebook.GradesTab.FilterBadges.track',
defaultMessage: 'Track',
description: 'Track FilterBadge label',
},
});
export default messages;

View File

@@ -42,6 +42,37 @@ describe('app reducer', () => {
).toEqual({ ...testingState, courseId: testValue });
});
});
describe('appActions.filterMenu.startTransition', () => {
it('sets filterMenu.transitioning to true', () => {
expect(
app(testingState, appActions.filterMenu.startTransition()),
).toEqual({
...testingState,
filterMenu: { ...testingState.filterMenu, transitioning: true },
});
});
});
describe('appActions.filterMenu.endTransition', () => {
it('sets filterMenu.transitioning to false', () => {
const transitioningState = {
...testingState,
filterMenu: { ...testingState.filterMenu, transitioning: true },
};
expect(
app(transitioningState, appActions.filterMenu.endTransition()),
).toEqual(testingState);
});
});
describe('appActions.filterMenu.toggle', () => {
it('toggles filterMenu.open', () => {
const openState = {
...testingState,
filterMenu: { ...testingState.filterMenu, open: true },
};
expect(app(testingState, appActions.filterMenu.toggle())).toEqual(openState);
expect(app(openState, appActions.filterMenu.toggle())).toEqual(testingState);
});
});
describe('appActions.setLocalFilter', () => {
it('loads filter values from the payload', () => {
expect(

View File

@@ -30,17 +30,13 @@ const reducer = (state = initialState, { type: actionType, payload }) => {
assignmentGradeMax: payload.assignmentGradeMax,
assignmentGradeMin: payload.assignmentGradeMin,
};
case actions.update.assignmentType.toString():
return {
...state,
assignmentType: payload,
assignment: (
(
payload !== ''
&& (state.assignment || {}).type !== payload
) ? '' : state.assignment
),
};
case actions.update.assignmentType.toString(): {
const newState = { ...state, assignmentType: payload };
if (payload !== '' && state.assignment && payload !== state.assignment.type) {
newState.assignment = '';
}
return newState;
}
case actions.update.cohort.toString():
return { ...state, cohort: payload };
case actions.update.courseGradeLimits.toString():

View File

@@ -9,6 +9,7 @@ import grades from './grades';
import roles from './roles';
import tracks from './tracks';
/* istanbul ignore next */
const rootReducer = combineReducers({
app,
assignmentTypes,

View File

@@ -132,6 +132,16 @@ export const selectedAssignmentId = (state) => (simpleSelectors.assignment(state
*/
export const selectedAssignmentLabel = (state) => (simpleSelectors.assignment(state) || {}).label;
/**
* Returns the api value for excludedCourseRoles based on the
* internal Bool value for includeCourseRoleMembers.
* @param {object} state - redux state
* @return {string} - '' if to be included, else 'all'
*/
export const excludedCourseRoles = (state) => (
simpleSelectors.includeCourseRoleMembers(state) ? '' : 'all'
);
export default StrictDict({
...simpleSelectors,
isDefault,
@@ -143,5 +153,6 @@ export default StrictDict({
allFilters,
areAssignmentGradeFiltersSet,
chooseRelevantAssignmentData,
excludedCourseRoles,
getAssignmentsFromResultsSubstate,
});

View File

@@ -139,6 +139,15 @@ describe('filters selectors', () => {
});
});
describe('excludedCourseRoles', () => {
it('returns empty string if includeCourseRoleMembers', () => {
expect(selectors.excludedCourseRoles({ filters: { includeCourseRoleMembers: true } })).toEqual('');
});
it('returns "all" string if not includeCourseRoleMembers', () => {
expect(selectors.excludedCourseRoles({ filters: { includeCourseRoleMembers: false } })).toEqual('all');
});
});
describe('selectedAssignmentId', () => {
it('gets filtered assignment ID when available', () => {
const assignmentId = selectors.selectedAssignmentId(testState);

View File

@@ -105,11 +105,11 @@ export const headingMapper = (category, label = 'All') => {
filter = filters.byLabel;
}
const { username, email, totalGrade } = Headings;
const fillerLabels = (entry) => entry.filter(filter).map(s => s.label);
const filteredLabels = (entry) => entry.filter(filter).map(s => s.label);
return (entry) => (
entry
? [username, email, ...fillerLabels(entry), totalGrade]
? [username, email, ...filteredLabels(entry), totalGrade]
: []
);
};
@@ -133,7 +133,6 @@ export const transformHistoryEntry = ({
originalFilename,
resultsSummary: {
rowId: id,
courseId,
text: module.getRowsProcessed(data),
},
...rest,
@@ -188,7 +187,7 @@ export const allGrades = ({ grades: { results } }) => results;
*/
export const bulkImportError = ({ grades: { bulkManagement } }) => (
(!!bulkManagement && bulkManagement.errorMessages)
? `Errors while processing: ${bulkManagement.errorMessages.join(', ')}`
? `Errors while processing: ${bulkManagement.errorMessages.join('; ')};`
: ''
);

View File

@@ -87,6 +87,44 @@ describe('grades selectors', () => {
describe('grade formatters', () => {
const selectedAssignment = { assignmentId: 'block-v1:edX+type@sequential+block@abcde' };
describe('formatGradeOverrideForDisplay', () => {
it('maps history entries with formatted date, grader, reason, and adjusted grade', () => {
const historyArray = [
{
history_date: 'Jan 01 2021',
history_user: 'Grog',
override_reason: 'rage',
earned_graded_override: 0,
},
{
history_date: 'Jan 02 2021',
history_user: 'Keyleth',
override_reason: 'nature',
earned_graded_override: 10,
},
{
history_date: 'Jan 03 2021',
history_user: 'Pike',
override_reason: 'Sarenrae',
earned_graded_override: 9001,
},
];
const mapped = selectors.formatGradeOverrideForDisplay(historyArray);
const testEntry = (index) => {
const entry = historyArray[index];
expect(mapped[index]).toEqual({
date: formatDateForDisplay(new Date(entry.history_date)),
grader: entry.history_user,
reason: entry.override_reason,
adjustedGrade: entry.earned_graded_override,
});
};
testEntry(0);
testEntry(1);
testEntry(2);
});
});
describe('formatMinAssignmentGrade', () => {
const modifiedGrade = '1';
const selector = selectors.formatMinAssignmentGrade;
@@ -200,7 +238,6 @@ describe('grades selectors', () => {
it('summarizes processed rows', () => {
expect(output.resultsSummary).toEqual({
text: selectors.getRowsProcessed(rawEntry.data),
courseId: rawEntry.unique_id,
rowId: rawEntry.id,
});
});
@@ -276,7 +313,7 @@ describe('grades selectors', () => {
expect(
selectors.bulkImportError({ grades: { bulkManagement: { errorMessages } } }),
).toEqual(
`Errors while processing: ${errorMessages[0]}, ${errorMessages[1]}`,
`Errors while processing: ${errorMessages[0]}; ${errorMessages[1]};`,
);
});
});

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