diff --git a/.prettierrc.json b/.prettierrc.json index 72afa7d6..da7b7048 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,5 +1,6 @@ { "printWidth": 120, "semi": false, - "arrowParens": "avoid" + "arrowParens": "avoid", + "endOfLine": "auto" } diff --git a/enzyme.js b/enzyme.js new file mode 100644 index 00000000..c5f9c5d1 --- /dev/null +++ b/enzyme.js @@ -0,0 +1,6 @@ +import Enzyme, { configure, shallow, mount, render } from "enzyme" +import Adapter from "enzyme-adapter-react-16" + +configure({ adapter: new Adapter() }) +export { shallow, mount, render } +export default Enzyme diff --git a/package-lock.json b/package-lock.json index 7b8cbc51..2eac04e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "metadata-submitter-frontend", - "version": "0.3.0", + "version": "0.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2424,6 +2424,23 @@ "indent-string": "^4.0.0" } }, + "airbnb-prop-types": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz", + "integrity": "sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==", + "dev": true, + "requires": { + "array.prototype.find": "^2.1.1", + "function.prototype.name": "^1.1.2", + "is-regex": "^1.1.0", + "object-is": "^1.1.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.2", + "prop-types": "^15.7.2", + "prop-types-exact": "^1.2.0", + "react-is": "^16.13.1" + } + }, "ajv": { "version": "6.12.3", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", @@ -2553,6 +2570,12 @@ "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true + }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", @@ -2586,6 +2609,16 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, + "array.prototype.find": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz", + "integrity": "sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.4" + } + }, "array.prototype.flat": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", @@ -3687,6 +3720,75 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + }, + "dependencies": { + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "dev": true, + "requires": { + "@types/node": "*" + } + } + } + }, "chokidar": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", @@ -4857,6 +4959,12 @@ "path-type": "^3.0.0" } }, + "discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", + "dev": true + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -5138,6 +5246,93 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" }, + "enzyme": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", + "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", + "dev": true, + "requires": { + "array.prototype.flat": "^1.2.3", + "cheerio": "^1.0.0-rc.3", + "enzyme-shallow-equal": "^1.0.1", + "function.prototype.name": "^1.1.2", + "has": "^1.0.3", + "html-element-map": "^1.2.0", + "is-boolean-object": "^1.0.1", + "is-callable": "^1.1.5", + "is-number-object": "^1.0.4", + "is-regex": "^1.0.5", + "is-string": "^1.0.5", + "is-subset": "^0.1.1", + "lodash.escape": "^4.0.1", + "lodash.isequal": "^4.5.0", + "object-inspect": "^1.7.0", + "object-is": "^1.0.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1", + "object.values": "^1.1.1", + "raf": "^3.4.1", + "rst-selector-parser": "^2.2.3", + "string.prototype.trim": "^1.2.1" + } + }, + "enzyme-adapter-react-16": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.5.tgz", + "integrity": "sha512-33yUJGT1nHFQlbVI5qdo5Pfqvu/h4qPwi1o0a6ZZsjpiqq92a3HjynDhwd1IeED+Su60HDWV8mxJqkTnLYdGkw==", + "dev": true, + "requires": { + "enzyme-adapter-utils": "^1.13.1", + "enzyme-shallow-equal": "^1.0.4", + "has": "^1.0.3", + "object.assign": "^4.1.0", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "react-is": "^16.13.1", + "react-test-renderer": "^16.0.0-0", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "enzyme-adapter-utils": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.1.tgz", + "integrity": "sha512-5A9MXXgmh/Tkvee3bL/9RCAAgleHqFnsurTYCbymecO4ohvtNO5zqIhHxV370t7nJAwaCfkgtffarKpC0GPt0g==", + "dev": true, + "requires": { + "airbnb-prop-types": "^2.16.0", + "function.prototype.name": "^1.1.2", + "object.assign": "^4.1.0", + "object.fromentries": "^2.0.2", + "prop-types": "^15.7.2", + "semver": "^5.7.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "enzyme-shallow-equal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz", + "integrity": "sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q==", + "dev": true, + "requires": { + "has": "^1.0.3", + "object-is": "^1.1.2" + } + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -6426,11 +6621,28 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.2.tgz", + "integrity": "sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "functions-have-names": "^1.2.0" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, + "functions-have-names": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.1.tgz", + "integrity": "sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==", + "dev": true + }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", @@ -6789,6 +7001,15 @@ "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" }, + "html-element-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz", + "integrity": "sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==", + "dev": true, + "requires": { + "array-filter": "^1.0.0" + } + }, "html-encoding-sniffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", @@ -7437,6 +7658,12 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", + "dev": true + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -7541,6 +7768,12 @@ "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -7549,6 +7782,12 @@ "kind-of": "^3.0.2" } }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "dev": true + }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -7621,6 +7860,12 @@ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, "is-svg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", @@ -8651,6 +8896,24 @@ "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -9078,6 +9341,12 @@ "minimist": "^1.2.5" } }, + "moo": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", + "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", + "dev": true + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -9145,6 +9414,27 @@ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" }, + "nearley": { + "version": "2.19.7", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.7.tgz", + "integrity": "sha512-Y+KNwhBPcSJKeyQCFjn8B/MIe+DDlhaaDgjVldhy5xtFewIbiQgcbZV8k2gCVwkI1ZsKCnjIYZbR+0Fim5QYgg==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6", + "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -11028,6 +11318,17 @@ "react-is": "^16.8.1" } }, + "prop-types-exact": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", + "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", + "dev": true, + "requires": { + "has": "^1.0.3", + "object.assign": "^4.1.0", + "reflect.ownkeys": "^0.2.0" + } + }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -11144,11 +11445,27 @@ "performance-now": "^2.1.0" } }, + "railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", + "dev": true + }, "ramda": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==" }, + "randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "requires": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + } + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11677,6 +11994,18 @@ } } }, + "react-test-renderer": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.13.1.tgz", + "integrity": "sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "react-is": "^16.8.6", + "scheduler": "^0.19.1" + } + }, "react-transition-group": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", @@ -11813,6 +12142,12 @@ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==" }, + "reflect.ownkeys": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", + "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=", + "dev": true + }, "regenerate": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", @@ -12200,6 +12535,16 @@ "inherits": "^2.0.1" } }, + "rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", + "dev": true, + "requires": { + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" + } + }, "rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -13114,6 +13459,71 @@ "side-channel": "^1.0.2" } }, + "string.prototype.trim": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.2.tgz", + "integrity": "sha512-b5yrbl3BXIjHau9Prk7U0RRYcUYdN4wGSVaqoBQS50CCE3KBuYU0TYRNPFCP7aVoNMX87HKThdMRVIP3giclKg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, "string.prototype.trimend": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", diff --git a/package.json b/package.json index 656b830d..eca2b234 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metadata-submitter-frontend", - "version": "0.3.0", + "version": "0.4.0", "private": true, "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.6", @@ -56,6 +56,8 @@ }, "devDependencies": { "concurrently": "^5.3.0", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.5", "eslint-config-prettier": "^6.11.0", "eslint-plugin-flowtype": "^5.2.0", "eslint-plugin-import": "^2.22.0", diff --git a/src/App.js b/src/App.js index 8cf1ccdd..dd6db412 100644 --- a/src/App.js +++ b/src/App.js @@ -55,12 +55,12 @@ const App = () => { - + - + diff --git a/src/components/Nav.js b/src/components/Nav.js index 5e174f01..3d99a227 100644 --- a/src/components/Nav.js +++ b/src/components/Nav.js @@ -17,7 +17,7 @@ const useStyles = makeStyles(theme => ({ appBar: { borderBottom: `1px solid ${theme.palette.divider}`, color: theme.palette.text.primary, - backgroundColor: "white", + backgroundColor: "#FFF", }, logo: { height: "auto", @@ -45,14 +45,14 @@ const useStyles = makeStyles(theme => ({ const Menu = () => { const classes = useStyles() let location = useLocation() - if (location.pathname.match(/login/)) { + if (location.pathname === "/") { return null } return ( { return ( - + diff --git a/src/components/Nav.test.js b/src/components/Nav.test.js index 475892d6..b0c82a0a 100644 --- a/src/components/Nav.test.js +++ b/src/components/Nav.test.js @@ -2,7 +2,7 @@ import React from "react" import "@testing-library/jest-dom/extend-expect" import { render } from "@testing-library/react" -import { BrowserRouter as Router } from "react-router-dom" +import { MemoryRouter } from "react-router-dom" import Nav from "./Nav" @@ -11,9 +11,9 @@ describe("NavBar", () => { beforeEach(() => { component = render( - + - + ) }) diff --git a/src/components/NewDraftWizard/WizardComponents/AddObjectCard.js b/src/components/NewDraftWizard/WizardComponents/WizardAddObjectCard.js similarity index 81% rename from src/components/NewDraftWizard/WizardComponents/AddObjectCard.js rename to src/components/NewDraftWizard/WizardComponents/WizardAddObjectCard.js index 3faff112..4339c6d8 100644 --- a/src/components/NewDraftWizard/WizardComponents/AddObjectCard.js +++ b/src/components/NewDraftWizard/WizardComponents/WizardAddObjectCard.js @@ -8,10 +8,10 @@ import CardHeader from "@material-ui/core/CardHeader" import { makeStyles } from "@material-ui/core/styles" import { useDispatch, useSelector } from "react-redux" -import FillObjectDetailsForm from "components/NewDraftWizard/WizardForms/FillObjectDetailsForm" -import UploadObjectXMLForm from "components/NewDraftWizard/WizardForms/UploadObjectXMLForm" -import { resetObjectType } from "features/objectTypeSlice" -import { resetSubmissionType } from "features/submissionTypeSlice" +import WizardFillObjectDetailsForm from "components/NewDraftWizard/WizardForms/WizardFillObjectDetailsForm" +import WizardUploadObjectXMLForm from "components/NewDraftWizard/WizardForms/WizardUploadObjectXMLForm" +import { resetObjectType } from "features/wizardObjectTypeSlice" +import { resetSubmissionType } from "features/wizardSubmissionTypeSlice" const useStyles = makeStyles(theme => ({ card: { @@ -21,7 +21,7 @@ const useStyles = makeStyles(theme => ({ }, cardHeader: { backgroundColor: theme.palette.primary.main, - color: "white", + color: "#FFF", fontWeight: "bold", }, cardHeaderAction: { @@ -81,17 +81,17 @@ const CustomCardHeader = ({ title }: { title: string }) => { /* * Render correct form to add objects based on submission type in store */ -const AddObjectCard = () => { +const WizardAddObjectCard = () => { const classes = useStyles() const submissionType = useSelector(state => state.submissionType) const cards = { form: { title: "Fill form", - component: , + component: , }, xml: { title: "Upload XML file", - component: , + component: , }, existing: { title: "Choose existing object", @@ -106,4 +106,4 @@ const AddObjectCard = () => { ) } -export default AddObjectCard +export default WizardAddObjectCard diff --git a/src/components/NewDraftWizard/WizardComponents/WizardAlert.js b/src/components/NewDraftWizard/WizardComponents/WizardAlert.js new file mode 100644 index 00000000..344d7b73 --- /dev/null +++ b/src/components/NewDraftWizard/WizardComponents/WizardAlert.js @@ -0,0 +1,198 @@ +//@flow +import React from "react" + +import Button from "@material-ui/core/Button" +import Dialog from "@material-ui/core/Dialog" +import DialogActions from "@material-ui/core/DialogActions" +import DialogContent from "@material-ui/core/DialogContent" +import DialogContentText from "@material-ui/core/DialogContentText" +import DialogTitle from "@material-ui/core/DialogTitle" +import Link from "@material-ui/core/Link" +import { useSelector } from "react-redux" +import { Link as RouterLink } from "react-router-dom" + +/* + * Dialog contents are rendered based on parent component location and alert type + */ +const CancelFormDialog = ({ + handleDialog, + alertType, + parentLocation, + currentSubmissionType, +}: { + handleDialog: boolean => void, + alertType: string, + parentLocation: string, + currentSubmissionType: string, +}) => { + let [dialogTitle, dialogContent] = ["", ""] + let dialogActions + const formContent = "If you save form as a draft, you can continue filling it later." + const xmlContent = "If you save xml as a draft, you can upload it later." + const objectContent = "If you save object as a draft, you can upload it later." + switch (parentLocation) { + case "submission": { + switch (alertType) { + case "form": { + dialogTitle = "Would you like to save draft version of this form" + dialogContent = formContent + break + } + case "xml": { + dialogTitle = "Would you like to save draft version of this xml upload" + dialogContent = xmlContent + break + } + case "existing": { + dialogTitle = "Would you like to save draft version of this existing object upload" + dialogContent = objectContent + break + } + default: { + dialogTitle = "default" + dialogContent = "default content" + } + } + dialogActions = ( + + handleDialog(false)} color="secondary"> + Cancel + + handleDialog(true)} color="primary"> + Do not save + + handleDialog(true)} color="primary"> + Save + + + ) + break + } + case "footer": { + switch (alertType) { + case "cancel": { + dialogTitle = "Cancel creating a submission folder?" + dialogContent = + "If you cancel creating submission folder, the folder and its content will not be saved anywhere." + dialogActions = ( + + handleDialog(false)} color="primary" autoFocus> + No, continue creating the folder + + + handleDialog(true)} color="primary"> + Yes, cancel creating folder + + + + ) + break + } + case "save": { + dialogTitle = "Folder saved" + dialogContent = "Folder has been saved" + dialogActions = ( + + + handleDialog(true)} color="primary"> + Return to homepage + + + + ) + break + } + default: { + dialogTitle = "default" + dialogContent = "default content" + } + } + break + } + case "stepper": { + dialogTitle = "Move to " + alertType + " step?" + dialogContent = "You have unsaved data. You can save current form as draft" + switch (currentSubmissionType) { + case "form": { + dialogContent = formContent + break + } + case "xml": { + dialogContent = xmlContent + break + } + case "existing": { + dialogContent = objectContent + break + } + default: { + dialogContent = "default content" + } + } + dialogActions = ( + + handleDialog(false)} color="secondary"> + Cancel + + handleDialog(true)} color="primary"> + Navigate without saving + + handleDialog(true)} color="primary"> + Save and navigate + + + ) + break + } + default: { + dialogTitle = "default" + dialogContent = "default content" + } + } + + return ( + handleDialog(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + {dialogTitle} + + {dialogContent} + + {dialogActions} + + ) +} + +/* + * Render alert form based on location and type + */ +const WizardAlert = ({ + onAlert, + parentLocation, + alertType, +}: { + onAlert: boolean => void, + parentLocation: string, + alertType: string, +}) => { + const currentSubmissionType = useSelector(state => state.submissionType) + const handleDialog = (action: boolean) => { + onAlert(action) + } + + return ( + + + + ) +} + +export default WizardAlert diff --git a/src/components/NewDraftWizard/WizardComponents/WizardFooter.js b/src/components/NewDraftWizard/WizardComponents/WizardFooter.js index 086671b6..e1386c8e 100644 --- a/src/components/NewDraftWizard/WizardComponents/WizardFooter.js +++ b/src/components/NewDraftWizard/WizardComponents/WizardFooter.js @@ -2,19 +2,14 @@ import React, { useState } from "react" import Button from "@material-ui/core/Button" -import Dialog from "@material-ui/core/Dialog" -import DialogActions from "@material-ui/core/DialogActions" -import DialogContent from "@material-ui/core/DialogContent" -import DialogContentText from "@material-ui/core/DialogContentText" -import DialogTitle from "@material-ui/core/DialogTitle" -import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" import { useDispatch, useSelector } from "react-redux" -import { Link as RouterLink } from "react-router-dom" -import { resetObjectType } from "features/objectTypeSlice" -import { deleteFolderAndContent } from "features/submissionFolderSlice" +import WizardAlert from "./WizardAlert" + +import { resetObjectType } from "features/wizardObjectTypeSlice" import { resetWizard } from "features/wizardStepSlice" +import { deleteFolderAndContent } from "features/wizardSubmissionFolderSlice" const useStyles = makeStyles(theme => ({ footerRow: { @@ -23,7 +18,7 @@ const useStyles = makeStyles(theme => ({ justifyContent: "space-between", flexShrink: 0, borderTop: "solid 1px #ccc", - backgroundColor: "white", + backgroundColor: "#FFF", position: "fixed", left: 0, bottom: 0, @@ -42,35 +37,6 @@ const useStyles = makeStyles(theme => ({ }, })) -/* - * Render alert for wizard cancellation - */ -const CancelDialog = ({ open, handleCancel }: { open: boolean, handleCancel: boolean => void }) => ( - handleCancel(false)} - aria-labelledby="alert-dialog-title" - aria-describedby="alert-dialog-description" - > - {"Cancel creating a submission folder?"} - - - If you cancel creating submission folder, the folder and its content will not be saved anywhere. - - - - handleCancel(false)} color="primary" autoFocus> - No, continue creating the folder - - - handleCancel(true)} color="primary"> - Yes, cancel creating folder - - - - -) - /** * Define wizard footer with changing button actions. */ @@ -79,7 +45,8 @@ const WizardFooter = () => { const dispatch = useDispatch() const wizardStep = useSelector(state => state.wizardStep) const folder = useSelector(state => state.submissionFolder) - const [cancelDialogOpen, setCancelDialogOpen] = useState(false) + const [dialogOpen, setDialogOpen] = useState(false) + const [alertType, setAlertType] = useState("") const handleCancel = cancelWizard => { if (cancelWizard) { @@ -87,7 +54,7 @@ const WizardFooter = () => { dispatch(resetObjectType()) dispatch(deleteFolderAndContent(folder)) } else { - setCancelDialogOpen(false) + setDialogOpen(false) } } @@ -99,7 +66,10 @@ const WizardFooter = () => { setCancelDialogOpen(true)} + onClick={() => { + setDialogOpen(true) + setAlertType("cancel") + }} className={classes.footerButton} > Cancel @@ -112,8 +82,10 @@ const WizardFooter = () => { color="primary" disabled={wizardStep < 1} className={classes.footerButton} + // TODO: Implement save functionality onClick={() => { - console.log("This should save and exit!") + setDialogOpen(true) + setAlertType("save") }} > Save and Exit @@ -130,7 +102,7 @@ const WizardFooter = () => { )} - + {dialogOpen && } ) } diff --git a/src/components/NewDraftWizard/WizardComponents/WizardHeader.js b/src/components/NewDraftWizard/WizardComponents/WizardHeader.js index 04fbb9b2..8605526a 100644 --- a/src/components/NewDraftWizard/WizardComponents/WizardHeader.js +++ b/src/components/NewDraftWizard/WizardComponents/WizardHeader.js @@ -7,7 +7,7 @@ import Typography from "@material-ui/core/Typography" const useStyles = makeStyles(theme => ({ paperTitle: { fontWeight: "bold", - color: "white", + color: "#FFF", width: "100%", padding: theme.spacing(3), backgroundColor: "#9b416b", diff --git a/src/components/NewDraftWizard/WizardComponents/ObjectIndex.js b/src/components/NewDraftWizard/WizardComponents/WizardObjectIndex.js similarity index 76% rename from src/components/NewDraftWizard/WizardComponents/ObjectIndex.js rename to src/components/NewDraftWizard/WizardComponents/WizardObjectIndex.js index 2fa3134f..4f459bcc 100644 --- a/src/components/NewDraftWizard/WizardComponents/ObjectIndex.js +++ b/src/components/NewDraftWizard/WizardComponents/WizardObjectIndex.js @@ -4,12 +4,6 @@ import React, { useState } from "react" import MuiAccordion from "@material-ui/core/Accordion" import MuiAccordionDetails from "@material-ui/core/AccordionDetails" import MuiAccordionSummary from "@material-ui/core/AccordionSummary" -import Button from "@material-ui/core/Button" -import Dialog from "@material-ui/core/Dialog" -import DialogActions from "@material-ui/core/DialogActions" -import DialogContent from "@material-ui/core/DialogContent" -import DialogContentText from "@material-ui/core/DialogContentText" -import DialogTitle from "@material-ui/core/DialogTitle" import List from "@material-ui/core/List" import ListItem from "@material-ui/core/ListItem" import ListItemText from "@material-ui/core/ListItemText" @@ -18,8 +12,10 @@ import Typography from "@material-ui/core/Typography" import NoteAddIcon from "@material-ui/icons/NoteAdd" import { useDispatch, useSelector } from "react-redux" -import { setObjectType } from "features/objectTypeSlice" -import { setSubmissionType } from "features/submissionTypeSlice" +import WizardAlert from "./WizardAlert" + +import { setObjectType } from "features/wizardObjectTypeSlice" +import { setSubmissionType } from "features/wizardSubmissionTypeSlice" const useStyles = makeStyles(theme => ({ index: { @@ -71,7 +67,7 @@ const AccordionSummary = withStyles(theme => ({ }, }, content: { - color: "white", + color: "#FFF", fontWeight: "bold", "&$expanded": { margin: `${theme.spacing(2)} 0`, @@ -90,36 +86,6 @@ const AccordionDetails = withStyles({ }, })(MuiAccordionDetails) -/* - * Render alert for form cancellation options - */ -const CancelFormDialog = ({ open, handleCancelling }: { open: boolean, handleCancelling: boolean => void }) => ( - handleCancelling(false)} - aria-labelledby="alert-dialog-title" - aria-describedby="alert-dialog-description" - > - {"Would you like to save draft version of this form?"} - - - If you save form as a draft, you can continue filling it later. - - - - handleCancelling(false)} color="secondary"> - Cancel - - handleCancelling(true)} color="primary"> - Do not save - - handleCancelling(true)} color="primary"> - Save - - - -) - /* * Render list of submission types to be used in accordions */ @@ -161,7 +127,7 @@ const SubmissionTypeList = ({ /** * Render accordion for choosing object type and submission type */ -const ObjectIndex = () => { +const WizardObjectIndex = () => { const classes = useStyles() const dispatch = useDispatch() const objectTypes = ["study", "sample", "experiment", "run", "analysis", "dac", "policy", "dataset"] @@ -224,9 +190,15 @@ const ObjectIndex = () => { ) })} - + {cancelFormOpen && ( + + )} ) } -export default ObjectIndex +export default WizardObjectIndex diff --git a/src/components/NewDraftWizard/WizardComponents/WizardStepper.js b/src/components/NewDraftWizard/WizardComponents/WizardStepper.js index 72a94362..80238ff4 100644 --- a/src/components/NewDraftWizard/WizardComponents/WizardStepper.js +++ b/src/components/NewDraftWizard/WizardComponents/WizardStepper.js @@ -1,5 +1,5 @@ //@flow -import React from "react" +import React, { useState } from "react" import Button from "@material-ui/core/Button" import Step from "@material-ui/core/Step" @@ -13,8 +13,12 @@ import Check from "@material-ui/icons/Check" import clsx from "clsx" import { useDispatch, useSelector } from "react-redux" +import WizardAlert from "./WizardAlert" + import type { CreateFolderFormRef } from "components/NewDraftWizard/WizardSteps/WizardCreateFolderStep" +import { resetObjectType } from "features/wizardObjectTypeSlice" import { decrement, increment } from "features/wizardStepSlice" +import { resetSubmissionType } from "features/wizardSubmissionTypeSlice" /* * Customized stepper inspired by https://material-ui.com/components/steppers/#customized-stepper @@ -65,7 +69,7 @@ const useQontoStepIconStyles = makeStyles(theme => ({ }, floating: { border: "solid 1px #000", - backgroundColor: "white", + backgroundColor: "#FFF", boxShadow: 0, }, })) @@ -122,6 +126,20 @@ const WizardStepper = ({ createFolderFormRef }: { createFolderFormRef?: CreateFo const dispatch = useDispatch() const wizardStep = useSelector(state => state.wizardStep) const steps = ["Folder Name & Description", "Add Objects", "Summary"] + const formState = useSelector(state => state.submissionType) + const [alert, setAlert] = useState(false) + const [direction, setDirection] = useState("") + + const handleNavigation = (step: boolean) => { + setDirection("") + setAlert(false) + if (step) { + direction === "previous" ? dispatch(decrement()) : dispatch(increment()) + dispatch(resetObjectType()) + dispatch(resetSubmissionType()) + } + } + return ( dispatch(decrement())} + onClick={() => { + if (wizardStep === 1 && formState.trim().length > 0) { + setDirection("previous") + setAlert(true) + } else { + dispatch(decrement()) + } + }} > Back @@ -157,7 +182,10 @@ const WizardStepper = ({ createFolderFormRef }: { createFolderFormRef?: CreateFo if (createFolderFormRef?.current) { await createFolderFormRef.current.submitForm() } - if ( + if (wizardStep === 1 && formState.trim().length > 0) { + setDirection("next") + setAlert(true) + } else if ( wizardStep !== 2 && (!createFolderFormRef?.current || Object.entries(createFolderFormRef?.current?.errors).length === 0) ) { @@ -168,6 +196,9 @@ const WizardStepper = ({ createFolderFormRef }: { createFolderFormRef?: CreateFo Next + {wizardStep === 1 && alert && ( + + )} ) } diff --git a/src/components/NewDraftWizard/WizardForms/ajvResolver.js b/src/components/NewDraftWizard/WizardForms/WizardAjvResolver.js similarity index 94% rename from src/components/NewDraftWizard/WizardForms/ajvResolver.js rename to src/components/NewDraftWizard/WizardForms/WizardAjvResolver.js index 494fa95d..adb38b36 100644 --- a/src/components/NewDraftWizard/WizardForms/ajvResolver.js +++ b/src/components/NewDraftWizard/WizardForms/WizardAjvResolver.js @@ -1,7 +1,7 @@ import Ajv from "ajv" import { appendErrors } from "react-hook-form" -import JSONSchemaParser from "./JSONSchemaParser" +import JSONSchemaParser from "./WizardJSONSchemaParser" /* * Parse through ajv validation errors and transform them to errors readable by react-hook-form @@ -38,7 +38,7 @@ const parseErrorSchema = (validationError, validateAllFieldCriteria) => /* * Resolver for checking if form data is valid against schema. */ -export const ajvResolver = validationSchema => { +export const WizardAjvResolver = validationSchema => { if (!validationSchema) { throw new Error("Undefined schema, not able to validate") } diff --git a/src/components/NewDraftWizard/WizardForms/FillObjectDetailsForm.js b/src/components/NewDraftWizard/WizardForms/WizardFillObjectDetailsForm.js similarity index 74% rename from src/components/NewDraftWizard/WizardForms/FillObjectDetailsForm.js rename to src/components/NewDraftWizard/WizardForms/WizardFillObjectDetailsForm.js index 8993be32..681b2070 100644 --- a/src/components/NewDraftWizard/WizardForms/FillObjectDetailsForm.js +++ b/src/components/NewDraftWizard/WizardForms/WizardFillObjectDetailsForm.js @@ -11,10 +11,11 @@ import Ajv from "ajv" import { useForm, FormProvider } from "react-hook-form" import { useDispatch, useSelector } from "react-redux" -import { addObjectToFolder } from "../../../features/submissionFolderSlice" +import { addObjectToFolder } from "../../../features/wizardSubmissionFolderSlice" -import { ajvResolver } from "./ajvResolver" -import JSONSchemaParser from "./JSONSchemaParser" +import { WizardAjvResolver } from "./WizardAjvResolver" +import JSONSchemaParser from "./WizardJSONSchemaParser" +import WizardStatusMessageHandler from "./WizardStatusMessageHandler" import objectAPIService from "services/objectAPI" import schemaAPIService from "services/schemaAPI" @@ -59,19 +60,8 @@ const useStyles = makeStyles(theme => ({ }, })) -const checkResponseError = (response, prefixText) => { - switch (response.status) { - case 504: - return `Unfortunately we couldn't connect to our server.` - case 400: - return `${prefixText}, details: ${response.data.detail}` - default: - return "Unfortunately an unexpected error happened on our servers" - } -} - type FormContentProps = { - resolver: typeof ajvResolver, + resolver: typeof WizardAjvResolver, formSchema: any, onSubmit: () => Promise, } @@ -108,17 +98,18 @@ const FormContent = ({ resolver, formSchema, onSubmit }: FormContentProps) => { /* * Container for json schema based form. Handles json schema loading, form rendering, form submitting and error/success alerts. */ -const FillObjectDetailsForm = () => { +const WizardFillObjectDetailsForm = () => { const objectType = useSelector(state => state.objectType) const [isLoading, setIsLoading] = useState(true) - const [error, setError] = useState("") - const [successMessage, setSuccessMessage] = useState("") - const [successStatus, setSuccessStatus] = useState("info") + const [error, setError] = useState(false) + const [errorPrefix, setErrorPrefix] = useState("") + const [successStatus, setSuccessStatus] = useState("") const [formSchema, setFormSchema] = useState({}) const [validationSchema, setValidationSchema] = useState({}) const [submitting, setSubmitting] = useState(false) const dispatch = useDispatch() const { id: folderId } = useSelector(state => state.submissionFolder) + const [responseInfo, setResponseInfo] = useState([]) /* * Submit form with cleaned values and check for response errors @@ -127,15 +118,14 @@ const FillObjectDetailsForm = () => { setSubmitting(true) const waitForServertimer = setTimeout(() => { setSuccessStatus("info") - setSuccessMessage(`For some reason, your file is still being saved - to our database, please wait. If saving doesn't go through in two - minutes, please try saving the file again.`) }, 5000) const cleanedValues = JSONSchemaParser.cleanUpFormValues(data) const response = await objectAPIService.createFromJSON(objectType, cleanedValues) + + setResponseInfo(response) + if (response.ok) { setSuccessStatus("success") - setSuccessMessage(`Submitted with accessionid ${response.data.accessionId}`) dispatch( addObjectToFolder(folderId, { accessionId: response.data.accessionId, @@ -144,7 +134,7 @@ const FillObjectDetailsForm = () => { ) } else { setSuccessStatus("error") - setSuccessMessage(checkResponseError(response, "Validation failed")) + setErrorPrefix("Validation failed") } clearTimeout(waitForServertimer) setSubmitting(false) @@ -158,11 +148,13 @@ const FillObjectDetailsForm = () => { let schema = localStorage.getItem(`cached_${objectType}_schema`) if (!schema || !new Ajv().validateSchema(JSON.parse(schema))) { const response = await schemaAPIService.getSchemaByObjectType(objectType) + setResponseInfo(response) if (response.ok) { schema = response.data localStorage.setItem(`cached_${objectType}_schema`, JSON.stringify(schema)) } else { - setError(checkResponseError(response, "Unfortunately an error happened while catching form fields")) + setError(true) + setErrorPrefix("Unfortunately an error happened while catching form fields") setIsLoading(false) return } @@ -177,23 +169,22 @@ const FillObjectDetailsForm = () => { }, [objectType]) if (isLoading) return - if (error) return {error} + // Schema validation error differs from response status handler + if (error) return {errorPrefix} + return ( - + {submitting && } - {successMessage && ( - { - setSuccessMessage("") - }} - > - {successMessage} - + {successStatus && ( + )} ) } -export default FillObjectDetailsForm +export default WizardFillObjectDetailsForm diff --git a/src/components/NewDraftWizard/WizardForms/JSONSchemaParser.js b/src/components/NewDraftWizard/WizardForms/WizardJSONSchemaParser.js similarity index 100% rename from src/components/NewDraftWizard/WizardForms/JSONSchemaParser.js rename to src/components/NewDraftWizard/WizardForms/WizardJSONSchemaParser.js diff --git a/src/components/NewDraftWizard/WizardForms/WizardStatusMessageHandler.js b/src/components/NewDraftWizard/WizardForms/WizardStatusMessageHandler.js new file mode 100644 index 00000000..de8b9b16 --- /dev/null +++ b/src/components/NewDraftWizard/WizardForms/WizardStatusMessageHandler.js @@ -0,0 +1,84 @@ +//@flow +import React, { useState } from "react" + +import Snackbar from "@material-ui/core/Snackbar" +import Alert from "@material-ui/lab/Alert" +import { useDispatch } from "react-redux" + +import { setErrorMessage } from "features/wizardErrorMessageSlice" + +/* + * Error messages are shown both in snackbar and Formik form errors. Latter needs error message from state + */ +const ErrorHandler = ({ response, prefixText }: { response: any, prefixText: string }) => { + const [openStatus, setOpenStatus] = useState(true) + const dispatch = useDispatch() + let message = "" + switch (response.status) { + case 504: + message = `Unfortunately we couldn't connect to our server.` + break + case 400: + message = `${prefixText}, details: ${response.data.detail}` + break + default: + message = "Unfortunately an unexpected error happened on our servers" + } + dispatch(setErrorMessage(message)) + return ( + + + { + setOpenStatus(false) + }} + > + {message} + + + + ) +} + +// Info messages +const InfoHandler = () => { + const message = `For some reason, your file is still being saved + to our database, please wait. If saving doesn't go through in two + minutes, please try saving the file again.` + + return {message} +} + +// Success messages +const SuccessHandler = ({ response }: { response: any }) => { + const message = `Submitted with accessionid ${response.data.accessionId}` + return {message} +} + +const WizardStatusMessageHandler = ({ + successStatus, + response, + prefixText, +}: { + successStatus: string, + response: any, + prefixText: string, +}) => { + const messageTemplate = status => { + switch (status) { + case "success": + return + case "info": + return + case "error": + return + default: + return + } + } + + return {!Array.isArray(response) && messageTemplate(successStatus)} +} + +export default WizardStatusMessageHandler diff --git a/src/components/NewDraftWizard/WizardForms/WizardStatusMessageHandler.test.js b/src/components/NewDraftWizard/WizardForms/WizardStatusMessageHandler.test.js new file mode 100644 index 00000000..1a88bc68 --- /dev/null +++ b/src/components/NewDraftWizard/WizardForms/WizardStatusMessageHandler.test.js @@ -0,0 +1,32 @@ +import React from "react" + +import { Provider } from "react-redux" +import configureStore from "redux-mock-store" + +import { shallow, mount } from "../../../../enzyme" + +import WizardStatusMessageHandler from "./WizardStatusMessageHandler" + +const mockStore = configureStore([]) + +describe("WizardStatusMessageHandler", () => { + const store = mockStore({ + errorMessage: "", + }) + it("should render appropriate components", () => { + const statusList = ["error", "info", "success"] + statusList.forEach(status => { + const wrapper = shallow() + expect(wrapper.find(status.charAt(0).toUpperCase() + status.slice(1) + "Handler").length).toBe(1) + }) + }) + + it("should open snackbar on error message", () => { + const wrapper = mount( + + + + ) + expect(wrapper.find("ForwardRef(Snackbar)").length).toBe(1) + }) +}) diff --git a/src/components/NewDraftWizard/WizardForms/UploadObjectXMLForm.js b/src/components/NewDraftWizard/WizardForms/WizardUploadObjectXMLForm.js similarity index 79% rename from src/components/NewDraftWizard/WizardForms/UploadObjectXMLForm.js rename to src/components/NewDraftWizard/WizardForms/WizardUploadObjectXMLForm.js index 3bbe6eb1..66d08fd9 100644 --- a/src/components/NewDraftWizard/WizardForms/UploadObjectXMLForm.js +++ b/src/components/NewDraftWizard/WizardForms/WizardUploadObjectXMLForm.js @@ -11,7 +11,10 @@ import Alert from "@material-ui/lab/Alert" import { Field, FieldProps, Form, Formik, getIn } from "formik" import { useDispatch, useSelector } from "react-redux" -import { addObjectToFolder } from "features/submissionFolderSlice" +import WizardStatusMessageHandler from "./WizardStatusMessageHandler" + +import { resetErrorMessage } from "features/wizardErrorMessageSlice" +import { addObjectToFolder } from "features/wizardSubmissionFolderSlice" import objectAPIService from "services/objectAPI" import submissionAPIService from "services/submissionAPI" @@ -86,29 +89,17 @@ const FileUpload = ({ ) } -const checkResponseError = response => { - switch (response.status) { - case 504: - return `Unfortunately we couldn't connect to our server to validate your - file.` - case 400: - return `Unfortunately an error happened when saving your file to our - servers, details: ${response.data}` - default: - return "Unfortunately an unexpected error happened on our servers" - } -} /* * Return formik based form for uploading xml files. Handles form submitting, validating and error/success alerts. */ -const UploadObjectXMLForm = () => { - const [successMessage, setSuccessMessage] = useState("") - const [successStatus, setSuccessStatus] = useState("info") +const WizardUploadObjectXMLForm = () => { + const [successStatus, setSuccessStatus] = useState("") + const [responseStatus, setResponseStatus] = useState([]) const objectType = useSelector(state => state.objectType) const { id: folderId } = useSelector(state => state.submissionFolder) const dispatch = useDispatch() const classes = useStyles() - + const errorMessage = useSelector(state => state.errorMessage) return ( { errors.file = "Please attach an XML file." } else { const response = await submissionAPIService.validateXMLFile(objectType, values.file) - + setResponseStatus(response) if (!response.ok) { - errors.file = checkResponseError(response) + errors.file = errorMessage } else if (!response.data.isValid) { errors.file = `The file you attached is not valid ${objectType}, our server reported following error: @@ -135,24 +126,22 @@ const UploadObjectXMLForm = () => { onSubmit={async (values, { setSubmitting, setFieldError }) => { const waitForServertimer = setTimeout(() => { setSuccessStatus("info") - setSuccessMessage(`For some reason, your file is still being saved - to our database, please wait. If saving doesn't go through in two - minutes, please try saving the file again.`) }, 5000) const response = await objectAPIService.createFromXML(objectType, values.file) - + setResponseStatus(response) if (response.ok) { setSuccessStatus("success") - setSuccessMessage(`Submitted with accessionid ${response.data.accessionId}`) dispatch( addObjectToFolder(folderId, { accessionId: response.data.accessionId, schema: objectType, }) ) + dispatch(resetErrorMessage()) } else { - setFieldError("file", checkResponseError(response)) + setFieldError("file") + setSuccessStatus("error") } clearTimeout(waitForServertimer) setSubmitting(false) @@ -174,18 +163,15 @@ const UploadObjectXMLForm = () => { )} - {successMessage && ( - { - setSuccessMessage("") - }} - > - {successMessage} - + {successStatus && ( + )} ) } -export default UploadObjectXMLForm +export default WizardUploadObjectXMLForm diff --git a/src/components/NewDraftWizard/WizardSteps/WizardAddObjectStep.js b/src/components/NewDraftWizard/WizardSteps/WizardAddObjectStep.js index 34d21f74..cdd8cd15 100644 --- a/src/components/NewDraftWizard/WizardSteps/WizardAddObjectStep.js +++ b/src/components/NewDraftWizard/WizardSteps/WizardAddObjectStep.js @@ -4,9 +4,9 @@ import React from "react" import { makeStyles } from "@material-ui/core/styles" import { useSelector } from "react-redux" -import AddObjectCard from "../WizardComponents/AddObjectCard" -import ObjectIndex from "../WizardComponents/ObjectIndex" +import WizardAddObjectCard from "../WizardComponents/WizardAddObjectCard" import WizardHeader from "../WizardComponents/WizardHeader" +import WizardObjectIndex from "../WizardComponents/WizardObjectIndex" import WizardStepper from "../WizardComponents/WizardStepper" const useStyles = makeStyles(theme => ({ @@ -33,10 +33,10 @@ const WizardAddObjectStep = () => { const objectType = useSelector(state => state.objectType) return ( <> - + - + {objectType === "" ? ( @@ -44,7 +44,7 @@ const WizardAddObjectStep = () => { You can also add objects and edit them after saving your draft. ) : ( - + )} diff --git a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js index abdff643..3746b2c8 100644 --- a/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js +++ b/src/components/NewDraftWizard/WizardSteps/WizardCreateFolderStep.js @@ -10,7 +10,7 @@ import { useDispatch, useSelector } from "react-redux" import WizardHeader from "../WizardComponents/WizardHeader" import WizardStepper from "../WizardComponents/WizardStepper" -import { createNewDraftFolder } from "features/submissionFolderSlice" +import { createNewDraftFolder } from "features/wizardSubmissionFolderSlice" const useStyles = makeStyles(theme => ({ root: { @@ -121,7 +121,7 @@ const CreateFolderForm = ({ createFolderFormRef }: { createFolderFormRef: Create const WizardCreateFolderStep = ({ createFolderFormRef }: { createFolderFormRef: CreateFolderFormRef }) => ( <> - + > diff --git a/src/features/wizardErrorMessageSlice.js b/src/features/wizardErrorMessageSlice.js new file mode 100644 index 00000000..11498866 --- /dev/null +++ b/src/features/wizardErrorMessageSlice.js @@ -0,0 +1,15 @@ +//@flow +import { createSlice } from "@reduxjs/toolkit" + +const initialState = "" + +const wizardErrorMessageSlice = createSlice({ + name: "wizardErrorMessage", + initialState, + reducers: { + setErrorMessage: (state, action) => action.payload, + resetErrorMessage: () => initialState, + }, +}) +export const { setErrorMessage, resetErrorMessage } = wizardErrorMessageSlice.actions +export default wizardErrorMessageSlice.reducer diff --git a/src/features/objectTypeSlice.js b/src/features/wizardObjectTypeSlice.js similarity index 58% rename from src/features/objectTypeSlice.js rename to src/features/wizardObjectTypeSlice.js index 3aae4b4f..d178ebb0 100644 --- a/src/features/objectTypeSlice.js +++ b/src/features/wizardObjectTypeSlice.js @@ -3,7 +3,7 @@ import { createSlice } from "@reduxjs/toolkit" const initialState = "" -const objectTypeSlice = createSlice({ +const wizardObjectTypeSlice = createSlice({ name: "objectType", initialState, reducers: { @@ -12,5 +12,5 @@ const objectTypeSlice = createSlice({ }, }) -export const { setObjectType, resetObjectType } = objectTypeSlice.actions -export default objectTypeSlice.reducer +export const { setObjectType, resetObjectType } = wizardObjectTypeSlice.actions +export default wizardObjectTypeSlice.reducer diff --git a/src/features/submissionFolderSlice.js b/src/features/wizardSubmissionFolderSlice.js similarity index 92% rename from src/features/submissionFolderSlice.js rename to src/features/wizardSubmissionFolderSlice.js index c7baa1e1..1ad85e80 100644 --- a/src/features/submissionFolderSlice.js +++ b/src/features/wizardSubmissionFolderSlice.js @@ -7,7 +7,7 @@ import folderAPIService from "services/folderAPI" const initialState = null -const submissionFolderSlice = createSlice({ +const wizardSubmissionFolderSlice = createSlice({ name: "wizardStep", initialState, reducers: { @@ -18,8 +18,8 @@ const submissionFolderSlice = createSlice({ resetFolder: () => initialState, }, }) -export const { setFolder, addObject, resetFolder } = submissionFolderSlice.actions -export default submissionFolderSlice.reducer +export const { setFolder, addObject, resetFolder } = wizardSubmissionFolderSlice.actions +export default wizardSubmissionFolderSlice.reducer type FolderFromForm = { name: string, diff --git a/src/features/submissionTypeSlice.js b/src/features/wizardSubmissionTypeSlice.js similarity index 56% rename from src/features/submissionTypeSlice.js rename to src/features/wizardSubmissionTypeSlice.js index be4dc0d1..7d118368 100644 --- a/src/features/submissionTypeSlice.js +++ b/src/features/wizardSubmissionTypeSlice.js @@ -3,7 +3,7 @@ import { createSlice } from "@reduxjs/toolkit" const initialState = "" -const submissionTypeSlice = createSlice({ +const wizardSubmissionTypeSlice = createSlice({ name: "submissionType", initialState, reducers: { @@ -12,5 +12,5 @@ const submissionTypeSlice = createSlice({ }, }) -export const { setSubmissionType, resetSubmissionType } = submissionTypeSlice.actions -export default submissionTypeSlice.reducer +export const { setSubmissionType, resetSubmissionType } = wizardSubmissionTypeSlice.actions +export default wizardSubmissionTypeSlice.reducer diff --git a/src/index.js b/src/index.js index 22671412..6ca4accb 100644 --- a/src/index.js +++ b/src/index.js @@ -1,59 +1,16 @@ //@flow import React from "react" -import { createMuiTheme, ThemeProvider } from "@material-ui/core/styles" +import { ThemeProvider } from "@material-ui/core/styles" import ReactDOM from "react-dom" import { Provider } from "react-redux" import "style.css" +import CSCtheme from "./theme" + import App from "App" import store from "store" -/** - * Set up custom theme that follows CSC's design guidelines. - */ -const CSCtheme = createMuiTheme({ - overrides: { - MuiButton: { - root: { - textTransform: "none", - fontWeight: "bold", - paddingLeft: "32px", - paddingRight: "32px", - }, - }, - MuiTypography: { - subtitle1: { - fontWeight: 600, - }, - }, - }, - palette: { - primary: { - main: "#8b1a4f", - }, - secondary: { - main: "#dfe1e3", - }, - third: { - main: "#006476", - }, - background: { - default: "white", - }, - }, - props: { - MuiTextField: { - variant: "outlined", - size: "small", - }, - MuiFormControl: { - variant: "outlined", - size: "small", - }, - }, -}) - /** * Render app with redux store and custom theme. */ diff --git a/src/index.test.js b/src/index.test.js index 315da8fe..cb0ca39e 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -1,12 +1,14 @@ import React from "react" import "@testing-library/jest-dom/extend-expect" +import { ThemeProvider } from "@material-ui/core/styles" import { render } from "@testing-library/react" import { Provider } from "react-redux" import configureStore from "redux-mock-store" import App from "./App" const mockStore = configureStore([]) +import CSCtheme from "./theme" describe("App", () => { test("gets rendered without crashing", () => { @@ -15,7 +17,9 @@ describe("App", () => { }) render( - + + + ) }) diff --git a/src/rootReducer.js b/src/rootReducer.js index 4d1b911c..2c3ed6bc 100644 --- a/src/rootReducer.js +++ b/src/rootReducer.js @@ -2,12 +2,14 @@ import { combineReducers } from "@reduxjs/toolkit" -import objectTypeReducer from "features/objectTypeSlice" -import submissionFolderReducer from "features/submissionFolderSlice" -import submissionTypeReducer from "features/submissionTypeSlice" +import wizardErrorMessageReducer from "features/wizardErrorMessageSlice" +import objectTypeReducer from "features/wizardObjectTypeSlice" import wizardStepReducer from "features/wizardStepSlice" +import submissionFolderReducer from "features/wizardSubmissionFolderSlice" +import submissionTypeReducer from "features/wizardSubmissionTypeSlice" const rootReducer = combineReducers({ + errorMessage: wizardErrorMessageReducer, objectType: objectTypeReducer, wizardStep: wizardStepReducer, submissionFolder: submissionFolderReducer, diff --git a/src/setupProxy.js b/src/setupProxy.js index b3e1bcce..885049a4 100644 --- a/src/setupProxy.js +++ b/src/setupProxy.js @@ -7,7 +7,7 @@ const proxy = process.env.REACT_APP_BACKEND_PROXY || "localhost:5430" module.exports = function (app) { app.use( - ["/objects", "/schemas", "/validate", "/submit", "/folders", "/drafts"], + ["/objects", "/schemas", "/validate", "/submit", "/folders", "/drafts", "/aai", "/callback", "/logout"], createProxyMiddleware({ target: `http://${proxy}`, changeOrigin: true, diff --git a/src/theme.js b/src/theme.js new file mode 100644 index 00000000..7e35ea59 --- /dev/null +++ b/src/theme.js @@ -0,0 +1,48 @@ +import { createMuiTheme } from "@material-ui/core/styles" + +/** + * Set up custom theme that follows CSC's design guidelines. + */ +const CSCtheme = createMuiTheme({ + overrides: { + MuiButton: { + root: { + textTransform: "none", + fontWeight: "bold", + paddingLeft: "32px", + paddingRight: "32px", + }, + }, + MuiTypography: { + subtitle1: { + fontWeight: 600, + }, + }, + }, + palette: { + primary: { + main: "#8b1a4f", + }, + secondary: { + main: "#dfe1e3", + }, + third: { + main: "#006476", + }, + background: { + default: "#FFF", + }, + }, + props: { + MuiTextField: { + variant: "outlined", + size: "small", + }, + MuiFormControl: { + variant: "outlined", + size: "small", + }, + }, +}) + +export default CSCtheme diff --git a/src/views/Login.js b/src/views/Login.js index 7ea1e4b6..9d47baa5 100644 --- a/src/views/Login.js +++ b/src/views/Login.js @@ -25,18 +25,18 @@ const useStyles = makeStyles(theme => ({ width: "100%", }, whiteBanner: { - backgroundColor: "white", + backgroundColor: "#FFF", color: "#000", padding: "15px", }, whiteBanner2: { - backgroundColor: "white", + backgroundColor: "#FFF", color: theme.palette.primary.main, padding: "15px", }, purpleBanner: { backgroundColor: theme.palette.primary.main, - color: "white", + color: "#FFF", padding: "15px", }, login: { @@ -56,6 +56,11 @@ const useStyles = makeStyles(theme => ({ const Login = () => { const classes = useStyles() + let loginRoute = "/aai" + if (process.env.NODE_ENV === "development") { + loginRoute = "/home" + } + return ( @@ -81,7 +86,7 @@ const Login = () => { Login - +
You can also add objects and edit them after saving your draft.