Merge pull request #343 from kimatata/develop
This commit is contained in:
132
backend/package-lock.json
generated
132
backend/package-lock.json
generated
@@ -19,6 +19,7 @@
|
|||||||
"sequelize": "^6.37.7",
|
"sequelize": "^6.37.7",
|
||||||
"sequelize-cli": "^6.6.3",
|
"sequelize-cli": "^6.6.3",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||||
"xmlbuilder2": "^3.1.1"
|
"xmlbuilder2": "^3.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -585,9 +586,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@@ -1008,27 +1010,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/encoding": {
|
|
||||||
"version": "0.1.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"iconv-lite": "^0.6.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/encoding/node_modules/iconv-lite": {
|
|
||||||
"version": "0.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
|
||||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/end-of-stream": {
|
"node_modules/end-of-stream": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||||
@@ -1114,9 +1095,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.21.1",
|
"version": "4.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
@@ -1137,7 +1120,7 @@
|
|||||||
"methods": "~1.1.2",
|
"methods": "~1.1.2",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"path-to-regexp": "0.1.10",
|
"path-to-regexp": "0.1.12",
|
||||||
"proxy-addr": "~2.0.7",
|
"proxy-addr": "~2.0.7",
|
||||||
"qs": "6.13.0",
|
"qs": "6.13.0",
|
||||||
"range-parser": "~1.2.1",
|
"range-parser": "~1.2.1",
|
||||||
@@ -1152,6 +1135,10 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express-rate-limit": {
|
"node_modules/express-rate-limit": {
|
||||||
@@ -2427,9 +2414,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.10",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/pg-connection-string": {
|
"node_modules/pg-connection-string": {
|
||||||
"version": "2.9.1",
|
"version": "2.9.1",
|
||||||
@@ -3182,9 +3170,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar-fs": {
|
"node_modules/tar-fs": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||||
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chownr": "^1.1.1",
|
"chownr": "^1.1.1",
|
||||||
"mkdirp-classic": "^0.5.2",
|
"mkdirp-classic": "^0.5.2",
|
||||||
@@ -3356,9 +3345,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/validator": {
|
"node_modules/validator": {
|
||||||
"version": "13.15.15",
|
"version": "13.15.20",
|
||||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
|
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz",
|
||||||
"integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==",
|
"integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
@@ -3457,6 +3446,18 @@
|
|||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/xlsx": {
|
||||||
|
"version": "0.20.3",
|
||||||
|
"resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||||
|
"integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"xlsx": "bin/xlsx.njs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xmlbuilder2": {
|
"node_modules/xmlbuilder2": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz",
|
||||||
@@ -3953,9 +3954,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@@ -4265,26 +4266,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="
|
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="
|
||||||
},
|
},
|
||||||
"encoding": {
|
|
||||||
"version": "0.1.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"iconv-lite": "^0.6.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"iconv-lite": {
|
|
||||||
"version": "0.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
|
||||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"end-of-stream": {
|
"end-of-stream": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||||
@@ -4344,9 +4325,10 @@
|
|||||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
|
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
|
||||||
},
|
},
|
||||||
"express": {
|
"express": {
|
||||||
"version": "4.21.1",
|
"version": "4.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||||
|
"peer": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
@@ -4367,7 +4349,7 @@
|
|||||||
"methods": "~1.1.2",
|
"methods": "~1.1.2",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"path-to-regexp": "0.1.10",
|
"path-to-regexp": "0.1.12",
|
||||||
"proxy-addr": "~2.0.7",
|
"proxy-addr": "~2.0.7",
|
||||||
"qs": "6.13.0",
|
"qs": "6.13.0",
|
||||||
"range-parser": "~1.2.1",
|
"range-parser": "~1.2.1",
|
||||||
@@ -5292,9 +5274,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"path-to-regexp": {
|
"path-to-regexp": {
|
||||||
"version": "0.1.10",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
|
||||||
},
|
},
|
||||||
"pg-connection-string": {
|
"pg-connection-string": {
|
||||||
"version": "2.9.1",
|
"version": "2.9.1",
|
||||||
@@ -5807,9 +5789,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tar-fs": {
|
"tar-fs": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||||
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"chownr": "^1.1.1",
|
"chownr": "^1.1.1",
|
||||||
"mkdirp-classic": "^0.5.2",
|
"mkdirp-classic": "^0.5.2",
|
||||||
@@ -5942,9 +5924,9 @@
|
|||||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
},
|
},
|
||||||
"validator": {
|
"validator": {
|
||||||
"version": "13.15.15",
|
"version": "13.15.20",
|
||||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
|
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz",
|
||||||
"integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A=="
|
"integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw=="
|
||||||
},
|
},
|
||||||
"vary": {
|
"vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
@@ -6014,6 +5996,10 @@
|
|||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||||
},
|
},
|
||||||
|
"xlsx": {
|
||||||
|
"version": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||||
|
"integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA=="
|
||||||
|
},
|
||||||
"xmlbuilder2": {
|
"xmlbuilder2": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"sequelize": "^6.37.7",
|
"sequelize": "^6.37.7",
|
||||||
"sequelize-cli": "^6.6.3",
|
"sequelize-cli": "^6.6.3",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||||
"xmlbuilder2": "^3.1.1"
|
"xmlbuilder2": "^3.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,16 @@ const router = express.Router();
|
|||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import Papa from 'papaparse';
|
import Papa from 'papaparse';
|
||||||
import defineCase from '../../models/cases.js';
|
import defineCase from '../../models/cases.js';
|
||||||
|
import defineStep from '../../models/steps.js';
|
||||||
import authMiddleware from '../../middleware/auth.js';
|
import authMiddleware from '../../middleware/auth.js';
|
||||||
import visibilityMiddleware from '../../middleware/verifyVisible.js';
|
import visibilityMiddleware from '../../middleware/verifyVisible.js';
|
||||||
import { testRunStatus, priorities, testTypes, automationStatus, templates } from '../../config/enums.js';
|
import { testRunStatus, priorities, testTypes, automationStatus, templates } from '../../config/enums.js';
|
||||||
|
|
||||||
export default function (sequelize) {
|
export default function (sequelize) {
|
||||||
const Case = defineCase(sequelize, DataTypes);
|
const Case = defineCase(sequelize, DataTypes);
|
||||||
|
const Step = defineStep(sequelize, DataTypes);
|
||||||
|
Case.belongsToMany(Step, { through: 'caseSteps' });
|
||||||
|
Step.belongsToMany(Case, { through: 'caseSteps' });
|
||||||
const { verifySignedIn } = authMiddleware(sequelize);
|
const { verifySignedIn } = authMiddleware(sequelize);
|
||||||
const { verifyProjectVisibleFromFolderId } = visibilityMiddleware(sequelize);
|
const { verifyProjectVisibleFromFolderId } = visibilityMiddleware(sequelize);
|
||||||
|
|
||||||
@@ -25,6 +29,15 @@ export default function (sequelize) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const cases = await Case.findAll({
|
const cases = await Case.findAll({
|
||||||
|
attributes: { exclude: ['createdAt', 'updatedAt', 'caseSteps'] },
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Step,
|
||||||
|
through: { attributes: [] },
|
||||||
|
order: [['stepNo', 'ASC']],
|
||||||
|
attributes: { exclude: ['createdAt', 'updatedAt'] },
|
||||||
|
},
|
||||||
|
],
|
||||||
where: { folderId },
|
where: { folderId },
|
||||||
raw: true,
|
raw: true,
|
||||||
});
|
});
|
||||||
@@ -33,20 +46,22 @@ export default function (sequelize) {
|
|||||||
return res.status(404).send('No cases found');
|
return res.status(404).send('No cases found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'json') {
|
// Convert numeric values to human-readable labels
|
||||||
return res.json(cases);
|
const casesWithLabels = cases.map((c) => ({
|
||||||
} else if (type === 'csv') {
|
...c,
|
||||||
// Convert numeric values to human-readable labels
|
state: testRunStatus[c.state] || c.state,
|
||||||
const casesWithLabels = cases.map((c) => ({
|
priority: priorities[c.priority] || c.priority,
|
||||||
...c,
|
type: testTypes[c.type] || c.type,
|
||||||
state: testRunStatus[c.state] || c.state,
|
automationStatus: automationStatus[c.automationStatus] || c.automationStatus,
|
||||||
priority: priorities[c.priority] || c.priority,
|
template: templates[c.template] || c.template,
|
||||||
type: testTypes[c.type] || c.type,
|
}));
|
||||||
automationStatus: automationStatus[c.automationStatus] || c.automationStatus,
|
|
||||||
template: templates[c.template] || c.template,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const csv = Papa.unparse(casesWithLabels, {
|
if (type === 'json') {
|
||||||
|
const formattedJsonCases = _formatRawCasesToJson(casesWithLabels);
|
||||||
|
return res.json(formattedJsonCases);
|
||||||
|
} else if (type === 'csv') {
|
||||||
|
const formattedCsvCases = _formatRawCasesToCsv(casesWithLabels);
|
||||||
|
const csv = Papa.unparse(formattedCsvCases, {
|
||||||
quotes: true,
|
quotes: true,
|
||||||
skipEmptyLines: true,
|
skipEmptyLines: true,
|
||||||
});
|
});
|
||||||
@@ -65,3 +80,55 @@ export default function (sequelize) {
|
|||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Group cases by caseId and format steps to Steps array to better visualization
|
||||||
|
const _formatRawCasesToJson = (cases) => {
|
||||||
|
const casesObject = {};
|
||||||
|
|
||||||
|
cases.forEach((c) => {
|
||||||
|
if (!casesObject[c.id]) {
|
||||||
|
casesObject[c.id] = {
|
||||||
|
id: c.id,
|
||||||
|
folderId: c.folderId,
|
||||||
|
title: c.title,
|
||||||
|
state: c.state,
|
||||||
|
priority: c.priority,
|
||||||
|
type: c.type,
|
||||||
|
automationStatus: c.automationStatus,
|
||||||
|
description: c.description,
|
||||||
|
template: c.template,
|
||||||
|
preConditions: c.preConditions,
|
||||||
|
expectedResults: c.expectedResults,
|
||||||
|
Steps: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c['Steps.id']) {
|
||||||
|
casesObject[c.id].Steps.push({
|
||||||
|
step: c['Steps.step'],
|
||||||
|
expectedStepResult: c['Steps.result'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return casesObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rename fields to better CSV headers
|
||||||
|
const _formatRawCasesToCsv = (cases) => {
|
||||||
|
return cases.map((c) => ({
|
||||||
|
id: c.id,
|
||||||
|
folderId: c.folderId,
|
||||||
|
title: c.title,
|
||||||
|
state: c.state,
|
||||||
|
priority: c.priority,
|
||||||
|
type: c.type,
|
||||||
|
automationStatus: c.automationStatus,
|
||||||
|
description: c.description,
|
||||||
|
template: c.template,
|
||||||
|
preConditions: c.preConditions,
|
||||||
|
expectedResults: c.expectedResults,
|
||||||
|
step: c['Steps.step'],
|
||||||
|
expectedStepResult: c['Steps.result'],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|||||||
@@ -37,11 +37,19 @@ vi.mock('../../middleware/verifyVisible.js', () => ({
|
|||||||
// mock defineCase
|
// mock defineCase
|
||||||
const mockCase = {
|
const mockCase = {
|
||||||
findAll: vi.fn(),
|
findAll: vi.fn(),
|
||||||
|
belongsToMany: vi.fn(),
|
||||||
};
|
};
|
||||||
vi.mock('../../models/cases.js', () => ({
|
vi.mock('../../models/cases.js', () => ({
|
||||||
default: () => mockCase,
|
default: () => mockCase,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mockStep = {
|
||||||
|
belongsToMany: vi.fn(),
|
||||||
|
};
|
||||||
|
vi.mock('../../models/steps.js', () => ({
|
||||||
|
default: () => mockStep,
|
||||||
|
}));
|
||||||
|
|
||||||
describe('GET /download with type=csv', () => {
|
describe('GET /download with type=csv', () => {
|
||||||
let app;
|
let app;
|
||||||
const sequelize = new Sequelize({
|
const sequelize = new Sequelize({
|
||||||
|
|||||||
145
backend/routes/cases/import.js
Normal file
145
backend/routes/cases/import.js
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import multer from 'multer';
|
||||||
|
import XLSX from 'xlsx';
|
||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import defineCase from '../../models/cases.js';
|
||||||
|
import authMiddleware from '../../middleware/auth.js';
|
||||||
|
import editableMiddleware from '../../middleware/verifyEditable.js';
|
||||||
|
import { priorities, testTypes, automationStatus, templates } from '../../config/enums.js';
|
||||||
|
|
||||||
|
const fileFilter = (req, file, cb) => {
|
||||||
|
const allowedFileTypes = ['.xlsx', '.xls'];
|
||||||
|
const allowedMimeTypes = [
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'application/vnd.ms-excel',
|
||||||
|
];
|
||||||
|
const extname = allowedFileTypes.includes(path.extname(file.originalname).toLowerCase());
|
||||||
|
const mimetype = allowedMimeTypes.includes(file.mimetype);
|
||||||
|
|
||||||
|
if (extname && mimetype) {
|
||||||
|
return cb(null, true);
|
||||||
|
} else {
|
||||||
|
cb(new Error('Only Excel files (.xlsx, .xls) are allowed!'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const storage = multer.memoryStorage();
|
||||||
|
const upload = multer({
|
||||||
|
storage: storage,
|
||||||
|
fileFilter,
|
||||||
|
limits: { fileSize: 50 * 1024 * 1024 }, // 50 MB limit
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function (sequelize) {
|
||||||
|
const Case = defineCase(sequelize, DataTypes);
|
||||||
|
const { verifySignedIn } = authMiddleware(sequelize);
|
||||||
|
const { verifyProjectDeveloperFromFolderId } = editableMiddleware(sequelize);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/import',
|
||||||
|
(req, res, next) => {
|
||||||
|
upload.single('file')(req, res, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return res.status(400).json({ error: err.message });
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
verifySignedIn,
|
||||||
|
verifyProjectDeveloperFromFolderId,
|
||||||
|
async (req, res) => {
|
||||||
|
const { folderId } = req.query;
|
||||||
|
|
||||||
|
if (!req.file || !req.file.buffer) {
|
||||||
|
return res.status(400).json({ error: 'No file uploaded' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!folderId) {
|
||||||
|
return res.status(400).json({ error: 'folderId is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const workbook = XLSX.read(req.file.buffer, { type: 'buffer' });
|
||||||
|
const sheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[sheetName];
|
||||||
|
const jsonData = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
|
||||||
|
let errorMessage = null;
|
||||||
|
const casesToCreate = [];
|
||||||
|
const requiredFields = ['title', 'priority', 'type', 'template'];
|
||||||
|
for (const [index, row] of jsonData.entries()) {
|
||||||
|
const rowNumber = index + 2;
|
||||||
|
for (const field of requiredFields) {
|
||||||
|
if (!row[field]) {
|
||||||
|
errorMessage = `Row ${rowNumber} is missing required field: ${field}`;
|
||||||
|
console.log(`Error found for field: ${errorMessage}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate priority if provided
|
||||||
|
let priorityIndex = priorities.indexOf('medium'); // default to 'medium'
|
||||||
|
if (row['priority']) {
|
||||||
|
priorityIndex = priorities.indexOf(row['priority'].toLowerCase());
|
||||||
|
if (priorityIndex === -1) {
|
||||||
|
errorMessage = `Row ${rowNumber} has invalid priority: ${row['priority']}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate type if provided
|
||||||
|
let typeIndex = testTypes.indexOf('other'); // default to 'other'
|
||||||
|
if (row['type']) {
|
||||||
|
typeIndex = testTypes.indexOf(row['type'].toLowerCase());
|
||||||
|
if (typeIndex === -1) {
|
||||||
|
errorMessage = `Row ${rowNumber} has invalid type: ${row['type']}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate automationStatus if provided
|
||||||
|
let automationStatusIndex = automationStatus.indexOf('automation-not-required'); // default to 'automation-not-required'
|
||||||
|
if (row['automationStatus']) {
|
||||||
|
automationStatusIndex = automationStatus.indexOf(row['automationStatus'].toLowerCase());
|
||||||
|
if (automationStatusIndex === -1) {
|
||||||
|
errorMessage = `Row ${rowNumber} has invalid automationStatus: ${row['automationStatus']}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate template if provided
|
||||||
|
let templateIndex = templates.indexOf('text'); // default to 'text'
|
||||||
|
if (row['template']) {
|
||||||
|
templateIndex = templates.indexOf(row['template'].toLowerCase());
|
||||||
|
if (templateIndex === -1) {
|
||||||
|
errorMessage = `Row ${rowNumber} has invalid template: ${row['template']}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorMessage) {
|
||||||
|
return res.status(400).json({ error: errorMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
casesToCreate.push({
|
||||||
|
folderId: folderId,
|
||||||
|
title: row['title'],
|
||||||
|
description: row['description'] || '',
|
||||||
|
state: 0, // default state
|
||||||
|
priority: priorityIndex,
|
||||||
|
type: typeIndex,
|
||||||
|
preConditions: row['preConditions'],
|
||||||
|
expectedResults: row['expectedResults'],
|
||||||
|
automationStatus: automationStatusIndex,
|
||||||
|
template: templateIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdCases = await Case.bulkCreate(casesToCreate);
|
||||||
|
res.json(createdCases);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).send('Internal Server Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ export default function (sequelize) {
|
|||||||
Tags.belongsToMany(Case, { through: 'caseTags', foreignKey: 'tagId', otherKey: 'caseId' });
|
Tags.belongsToMany(Case, { through: 'caseTags', foreignKey: 'tagId', otherKey: 'caseId' });
|
||||||
|
|
||||||
router.get('/', verifySignedIn, verifyProjectVisibleFromFolderId, async (req, res) => {
|
router.get('/', verifySignedIn, verifyProjectVisibleFromFolderId, async (req, res) => {
|
||||||
const { folderId, title, priority, type, tag } = req.query;
|
const { folderId, search, priority, type, tag } = req.query;
|
||||||
|
|
||||||
if (!folderId) {
|
if (!folderId) {
|
||||||
return res.status(400).json({ error: 'folderId is required' });
|
return res.status(400).json({ error: 'folderId is required' });
|
||||||
@@ -28,15 +28,18 @@ export default function (sequelize) {
|
|||||||
folderId: folderId,
|
folderId: folderId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (title) {
|
if (search) {
|
||||||
const searchTerm = title.trim();
|
const searchTerm = search.trim();
|
||||||
|
|
||||||
if (searchTerm.length > 100) {
|
if (searchTerm.length > 100) {
|
||||||
return res.status(400).json({ error: 'too long title param' });
|
return res.status(400).json({ error: 'too long search param' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchTerm.length >= 1) {
|
if (searchTerm.length >= 1) {
|
||||||
whereClause[Op.or] = [{ title: { [Op.like]: `%${searchTerm}%` } }];
|
whereClause[Op.or] = [
|
||||||
|
{ title: { [Op.like]: `%${searchTerm}%` } },
|
||||||
|
{ description: { [Op.like]: `%${searchTerm}%` } },
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ import casesNewRoute from './routes/cases/new.js';
|
|||||||
import casesEditRoute from './routes/cases/edit.js';
|
import casesEditRoute from './routes/cases/edit.js';
|
||||||
import casesDeleteRoute from './routes/cases/delete.js';
|
import casesDeleteRoute from './routes/cases/delete.js';
|
||||||
import casesCloneRoute from './routes/cases/clone.js';
|
import casesCloneRoute from './routes/cases/clone.js';
|
||||||
|
import casesImportRoute from './routes/cases/import.js';
|
||||||
app.use('/cases', casesDownloadRoute(sequelize));
|
app.use('/cases', casesDownloadRoute(sequelize));
|
||||||
app.use('/cases', casesMoveRoute(sequelize));
|
app.use('/cases', casesMoveRoute(sequelize));
|
||||||
app.use('/cases', casesIndexRoute(sequelize));
|
app.use('/cases', casesIndexRoute(sequelize));
|
||||||
@@ -109,6 +110,7 @@ app.use('/cases', casesNewRoute(sequelize));
|
|||||||
app.use('/cases', casesEditRoute(sequelize));
|
app.use('/cases', casesEditRoute(sequelize));
|
||||||
app.use('/cases', casesDeleteRoute(sequelize));
|
app.use('/cases', casesDeleteRoute(sequelize));
|
||||||
app.use('/cases', casesCloneRoute(sequelize));
|
app.use('/cases', casesCloneRoute(sequelize));
|
||||||
|
app.use('/cases', casesImportRoute(sequelize));
|
||||||
|
|
||||||
// "/steps"
|
// "/steps"
|
||||||
import stepsEditRoute from './routes/steps/edit.js';
|
import stepsEditRoute from './routes/steps/edit.js';
|
||||||
|
|||||||
@@ -223,6 +223,7 @@
|
|||||||
"no_cases_found": "No test cases found",
|
"no_cases_found": "No test cases found",
|
||||||
"case_title": "Test Case Title",
|
"case_title": "Test Case Title",
|
||||||
"case_description": "Test Case Description",
|
"case_description": "Test Case Description",
|
||||||
|
"case_title_or_description": "Test case title or description",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"please_enter": "Please enter test case title",
|
"please_enter": "Please enter test case title",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
@@ -239,7 +240,15 @@
|
|||||||
"cases_moved": "Test cases moved",
|
"cases_moved": "Test cases moved",
|
||||||
"cases_cloned": "Test cases cloned",
|
"cases_cloned": "Test cases cloned",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
"select_tags": "Select tags"
|
"select_tags": "Select tags",
|
||||||
|
"import": "Import",
|
||||||
|
"import_cases": "Import Test Cases",
|
||||||
|
"import_available": "You can import test cases from Excel file (xlsx, xls). The Excel file to be imported must follow the specified format.",
|
||||||
|
"download_template": "Download Template",
|
||||||
|
"click_to_upload": "Click to upload",
|
||||||
|
"or_drag_and_drop": " or drag and drop",
|
||||||
|
"max_file_size": "Max. file size",
|
||||||
|
"cases_imported": "Test cases imported"
|
||||||
},
|
},
|
||||||
"Case": {
|
"Case": {
|
||||||
"back_to_cases": "Back to test cases",
|
"back_to_cases": "Back to test cases",
|
||||||
|
|||||||
@@ -223,6 +223,7 @@
|
|||||||
"no_cases_found": "テストケースがありません",
|
"no_cases_found": "テストケースがありません",
|
||||||
"case_title": "テストケースタイトル",
|
"case_title": "テストケースタイトル",
|
||||||
"case_description": "テストケース詳細",
|
"case_description": "テストケース詳細",
|
||||||
|
"case_title_or_description": "テストケースのタイトルまたは説明",
|
||||||
"create": "作成",
|
"create": "作成",
|
||||||
"please_enter": "テストケースタイトルを入力してください",
|
"please_enter": "テストケースタイトルを入力してください",
|
||||||
"filter": "フィルター",
|
"filter": "フィルター",
|
||||||
@@ -239,7 +240,15 @@
|
|||||||
"cases_moved": "テストケースを移動しました",
|
"cases_moved": "テストケースを移動しました",
|
||||||
"cases_cloned": "テストケースをクローンしました",
|
"cases_cloned": "テストケースをクローンしました",
|
||||||
"tags": "タグ",
|
"tags": "タグ",
|
||||||
"select_tags": "タグを選択"
|
"select_tags": "タグを選択",
|
||||||
|
"import": "インポート",
|
||||||
|
"import_cases": "テストケースのインポート",
|
||||||
|
"import_available": "Excelファイル(xlsx, xls)からテストケースをインポートできます。インポートするExcelファイルは既定のフォーマットに従う必要があります。",
|
||||||
|
"download_template": "テンプレートをダウンロード",
|
||||||
|
"click_to_upload": "クリックしてアップロード",
|
||||||
|
"or_drag_and_drop": "またはドラッグアンドドロップ",
|
||||||
|
"max_file_size": "最大ファイルサイズ",
|
||||||
|
"cases_imported": "テストケースがインポートされました"
|
||||||
},
|
},
|
||||||
"Case": {
|
"Case": {
|
||||||
"back_to_cases": "テストケース一覧に戻る",
|
"back_to_cases": "テストケース一覧に戻る",
|
||||||
|
|||||||
@@ -223,6 +223,7 @@
|
|||||||
"no_cases_found": "Nenhum caso de teste encontrado",
|
"no_cases_found": "Nenhum caso de teste encontrado",
|
||||||
"case_title": "Título do Caso de Teste",
|
"case_title": "Título do Caso de Teste",
|
||||||
"case_description": "Descrição do Caso de Teste",
|
"case_description": "Descrição do Caso de Teste",
|
||||||
|
"case_title_or_description": "Título ou descrição do caso de teste",
|
||||||
"create": "Criar",
|
"create": "Criar",
|
||||||
"please_enter": "Por favor, insira o título do caso de teste",
|
"please_enter": "Por favor, insira o título do caso de teste",
|
||||||
"filter": "Filtrar",
|
"filter": "Filtrar",
|
||||||
@@ -239,7 +240,15 @@
|
|||||||
"cases_moved": "Casos de teste movidos",
|
"cases_moved": "Casos de teste movidos",
|
||||||
"cases_cloned": "Casos de teste clonados",
|
"cases_cloned": "Casos de teste clonados",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
"select_tags": "Selecionar tags"
|
"select_tags": "Selecionar tags",
|
||||||
|
"import": "Importar",
|
||||||
|
"import_cases": "Importar Casos de Teste",
|
||||||
|
"import_available": "Você pode importar casos de teste a partir de um arquivo Excel (xlsx, xls). O arquivo Excel a ser importado deve seguir o formato especificado.",
|
||||||
|
"download_template": "Baixar Modelo",
|
||||||
|
"click_to_upload": "Clique para enviar",
|
||||||
|
"or_drag_and_drop": " ou arraste e solte",
|
||||||
|
"max_file_size": "Tamanho máx. do arquivo",
|
||||||
|
"cases_imported": "Casos de teste importados"
|
||||||
},
|
},
|
||||||
"Case": {
|
"Case": {
|
||||||
"back_to_cases": "Voltar para os casos de teste",
|
"back_to_cases": "Voltar para os casos de teste",
|
||||||
|
|||||||
9
frontend/package-lock.json
generated
9
frontend/package-lock.json
generated
@@ -9988,9 +9988,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001700",
|
"version": "1.0.30001754",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz",
|
||||||
"integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==",
|
"integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -10004,7 +10004,8 @@
|
|||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
|
|||||||
BIN
frontend/public/template/unittcms-import-template-v1.xlsx
Normal file
BIN
frontend/public/template/unittcms-import-template-v1.xlsx
Normal file
Binary file not shown.
@@ -62,7 +62,7 @@ export default function HealthPage({ messages, locale }: Props) {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow key="1">
|
<TableRow key="1">
|
||||||
<TableCell>{messages.unittcms_version}</TableCell>
|
<TableCell>{messages.unittcms_version}</TableCell>
|
||||||
<TableCell>1.0.0-beta.23</TableCell>
|
<TableCell>1.0.0-beta.24</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow key="2">
|
<TableRow key="2">
|
||||||
<TableCell>{messages.api_server}</TableCell>
|
<TableCell>{messages.api_server}</TableCell>
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
'use client';
|
||||||
|
import { useState, ChangeEvent, DragEvent } from 'react';
|
||||||
|
import { Button, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Spinner, Alert } from '@heroui/react';
|
||||||
|
import { ArrowUpFromLine } from 'lucide-react';
|
||||||
|
import { CasesMessages } from '@/types/case';
|
||||||
|
import { importCases } from '@/utils/caseControl';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
folderId: number;
|
||||||
|
isDisabled: boolean;
|
||||||
|
onImport: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
messages: CasesMessages;
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CaseImportDialog({ isOpen, folderId, isDisabled, onImport, onCancel, messages, token }: Props) {
|
||||||
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
|
const [importError, setImportError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleDrop = (event: DragEvent<HTMLElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
const filesArray = Array.from(event.dataTransfer.files);
|
||||||
|
handleFiles(filesArray);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInput = (event: ChangeEvent) => {
|
||||||
|
if (event.target) {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
if (input.files) {
|
||||||
|
handleFiles(Array.from(input.files));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFiles = async (filesArray: File[]) => {
|
||||||
|
setIsProcessing(true);
|
||||||
|
|
||||||
|
if (filesArray.length !== 1) {
|
||||||
|
console.error('Error multiple file');
|
||||||
|
} else {
|
||||||
|
const ret = await importCases(token, folderId, filesArray[0]);
|
||||||
|
if (ret.error) {
|
||||||
|
setImportError(ret.error);
|
||||||
|
} else {
|
||||||
|
onImport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setIsProcessing(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCloseDialog = () => {
|
||||||
|
setImportError(null);
|
||||||
|
onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onOpenChange={() => {
|
||||||
|
onCloseDialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader className="flex flex-col gap-1">{messages.importCases}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div className="flex items-center justify-center w-full">
|
||||||
|
<div className={`mt-1 text-neutral-500 dark:text-neutral-400 text-sm rounded`}>
|
||||||
|
<div>{messages.importAvailable}</div>
|
||||||
|
<a href="/template/unittcms-import-template-v1.xlsx" download className="text-tiny underline">
|
||||||
|
{messages.downloadTemplate}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{importError && <Alert color="danger" className="mt-1" title="error" description={importError} />}
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center w-full mt-3"
|
||||||
|
onDrop={(event) => {
|
||||||
|
if (isDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleDrop(event);
|
||||||
|
}}
|
||||||
|
onDragOver={(event) => event.preventDefault()}
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
htmlFor="dropzone-file"
|
||||||
|
className={`flex flex-col items-center justify-center w-full h-32 border-2 border-neutral-200 border-dashed rounded-lg bg-neutral-50 dark:hover:bg-bray-800 dark:bg-neutral-700 hover:bg-neutral-100 dark:border-neutral-600 dark:hover:border-neutral-500 dark:hover:bg-neutral-600 ${isDisabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}`}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center justify-center pt-5 pb-6">
|
||||||
|
<ArrowUpFromLine />
|
||||||
|
<p className="mb-2 text-sm text-neutral-500 dark:text-neutral-400">
|
||||||
|
<span className="font-semibold">{messages.clickToUpload}</span>
|
||||||
|
<span>{messages.orDragAndDrop}</span>
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-neutral-500 dark:text-neutral-400">{messages.maxFileSize}: 50 MB</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="dropzone-file"
|
||||||
|
type="file"
|
||||||
|
className="hidden"
|
||||||
|
disabled={isDisabled}
|
||||||
|
onChange={(e) => handleInput(e)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
{isProcessing ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button variant="light" size="sm" onPress={onCloseDialog}>
|
||||||
|
{messages.close}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useState, useEffect, useContext, useCallback } from 'react';
|
import { useState, useEffect, useContext, useCallback } from 'react';
|
||||||
|
import { addToast } from '@heroui/react';
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
import TestCaseTable from './TestCaseTable';
|
import TestCaseTable from './TestCaseTable';
|
||||||
import CaseDialog from './CaseDialog';
|
import CaseDialog from './CaseDialog';
|
||||||
import CaseMoveDialog from './CaseMoveDialog';
|
import CaseMoveDialog from './CaseMoveDialog';
|
||||||
|
import CaseImportDialog from './CaseImportDialog';
|
||||||
import DeleteConfirmDialog from '@/components/DeleteConfirmDialog';
|
import DeleteConfirmDialog from '@/components/DeleteConfirmDialog';
|
||||||
import { TokenContext } from '@/utils/TokenProvider';
|
import { TokenContext } from '@/utils/TokenProvider';
|
||||||
import { fetchCases, createCase, deleteCases, exportCases } from '@/utils/caseControl';
|
import { fetchCases, createCase, deleteCases, exportCases } from '@/utils/caseControl';
|
||||||
@@ -34,7 +36,7 @@ export default function CasesPane({
|
|||||||
}: Props) {
|
}: Props) {
|
||||||
const [cases, setCases] = useState<CaseType[]>([]);
|
const [cases, setCases] = useState<CaseType[]>([]);
|
||||||
const [isCaseDialogOpen, setIsCaseDialogOpen] = useState(false);
|
const [isCaseDialogOpen, setIsCaseDialogOpen] = useState(false);
|
||||||
const [titleFilter, setTitleFilter] = useState('');
|
const [searchFilter, setSearchFilter] = useState('');
|
||||||
const [priorityFilter, setPriorityFilter] = useState<number[]>([]);
|
const [priorityFilter, setPriorityFilter] = useState<number[]>([]);
|
||||||
const [typeFilter, setTypeFilter] = useState<number[]>([]);
|
const [typeFilter, setTypeFilter] = useState<number[]>([]);
|
||||||
const [tagFilter, setTagFilter] = useState<number[]>([]);
|
const [tagFilter, setTagFilter] = useState<number[]>([]);
|
||||||
@@ -45,13 +47,13 @@ export default function CasesPane({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const updateUrlParams = (updates: { title?: string; priority?: number[]; type?: number[]; tag?: number[] }) => {
|
const updateUrlParams = (updates: { search?: string; priority?: number[]; type?: number[]; tag?: number[] }) => {
|
||||||
const currentParams = new URLSearchParams(searchParams.toString());
|
const currentParams = new URLSearchParams(searchParams.toString());
|
||||||
|
|
||||||
if (updates.title) {
|
if (updates.search) {
|
||||||
currentParams.set('title', updates.title);
|
currentParams.set('search', updates.search);
|
||||||
} else {
|
} else {
|
||||||
currentParams.delete('title');
|
currentParams.delete('search');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updates.priority && updates.priority.length > 0) {
|
if (updates.priority && updates.priority.length > 0) {
|
||||||
@@ -76,38 +78,38 @@ export default function CasesPane({
|
|||||||
router.push(newUrl, { scroll: false });
|
router.push(newUrl, { scroll: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const refreshCases = useCallback(async () => {
|
||||||
async function fetchDataEffect() {
|
if (!context.isSignedIn()) return;
|
||||||
if (!context.isSignedIn()) return;
|
|
||||||
|
|
||||||
const titleParam = searchParams.get('title') || '';
|
const searchParam = searchParams.get('search') || '';
|
||||||
const priorityParam = parseQueryParam(searchParams.get('priority'));
|
const priorityParam = parseQueryParam(searchParams.get('priority'));
|
||||||
const typeParam = parseQueryParam(searchParams.get('type'));
|
const typeParam = parseQueryParam(searchParams.get('type'));
|
||||||
const tagParam = parseQueryParam(searchParams.get('tag'));
|
const tagParam = parseQueryParam(searchParams.get('tag'));
|
||||||
|
|
||||||
setTitleFilter(titleParam);
|
setSearchFilter(searchParam);
|
||||||
setPriorityFilter(priorityParam);
|
setPriorityFilter(priorityParam);
|
||||||
setTypeFilter(typeParam);
|
setTypeFilter(typeParam);
|
||||||
setTagFilter(tagParam);
|
setTagFilter(tagParam);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await fetchCases(
|
const data = await fetchCases(
|
||||||
context.token.access_token,
|
context.token.access_token,
|
||||||
Number(folderId),
|
Number(folderId),
|
||||||
titleParam || undefined,
|
searchParam || undefined,
|
||||||
priorityParam.length > 0 ? priorityParam : undefined,
|
priorityParam.length > 0 ? priorityParam : undefined,
|
||||||
typeParam.length > 0 ? typeParam : undefined,
|
typeParam.length > 0 ? typeParam : undefined,
|
||||||
tagParam.length > 0 ? tagParam : undefined
|
tagParam.length > 0 ? tagParam : undefined
|
||||||
);
|
);
|
||||||
setCases(data);
|
setCases(data);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logError('Error fetching cases:', error);
|
logError('Error fetching cases:', error);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchDataEffect();
|
|
||||||
}, [context, folderId, searchParams]);
|
}, [context, folderId, searchParams]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refreshCases();
|
||||||
|
}, [refreshCases]);
|
||||||
|
|
||||||
const closeDialog = () => setIsCaseDialogOpen(false);
|
const closeDialog = () => setIsCaseDialogOpen(false);
|
||||||
|
|
||||||
const onSubmit = async (title: string, description: string) => {
|
const onSubmit = async (title: string, description: string) => {
|
||||||
@@ -143,12 +145,12 @@ export default function CasesPane({
|
|||||||
await exportCases(context.token.access_token, Number(folderId), type);
|
await exportCases(context.token.access_token, Number(folderId), type);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilterChange = (title: string, priorities: number[], types: number[], tag: number[]) => {
|
const handleFilterChange = (search: string, priorities: number[], types: number[], tag: number[]) => {
|
||||||
setTitleFilter(title);
|
setSearchFilter(search);
|
||||||
setPriorityFilter(priorities);
|
setPriorityFilter(priorities);
|
||||||
setTypeFilter(types);
|
setTypeFilter(types);
|
||||||
setTagFilter(tag);
|
setTagFilter(tag);
|
||||||
updateUrlParams({ title: title, priority: priorities, type: types, tag: tag });
|
updateUrlParams({ search: search, priority: priorities, type: types, tag: tag });
|
||||||
};
|
};
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
@@ -175,6 +177,20 @@ export default function CasesPane({
|
|||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, [openMoveDialog]);
|
}, [openMoveDialog]);
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// Import cases
|
||||||
|
// **************************************************************************
|
||||||
|
const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);
|
||||||
|
const handleImport = () => {
|
||||||
|
refreshCases();
|
||||||
|
setIsImportDialogOpen(false);
|
||||||
|
addToast({
|
||||||
|
title: 'Success',
|
||||||
|
color: 'success',
|
||||||
|
description: messages.casesImported,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TestCaseTable
|
<TestCaseTable
|
||||||
@@ -184,9 +200,10 @@ export default function CasesPane({
|
|||||||
onCreateCase={() => setIsCaseDialogOpen(true)}
|
onCreateCase={() => setIsCaseDialogOpen(true)}
|
||||||
onDeleteCase={onDeleteCase}
|
onDeleteCase={onDeleteCase}
|
||||||
onDeleteCases={onDeleteCases}
|
onDeleteCases={onDeleteCases}
|
||||||
|
onShowImportDialog={() => setIsImportDialogOpen(true)}
|
||||||
onExportCases={onExportCases}
|
onExportCases={onExportCases}
|
||||||
onFilterChange={handleFilterChange}
|
onFilterChange={handleFilterChange}
|
||||||
activeTitleFilter={titleFilter}
|
activeSearchFilter={searchFilter}
|
||||||
activePriorityFilters={priorityFilter}
|
activePriorityFilters={priorityFilter}
|
||||||
activeTypeFilters={typeFilter}
|
activeTypeFilters={typeFilter}
|
||||||
activeTagFilters={tagFilter}
|
activeTagFilters={tagFilter}
|
||||||
@@ -210,6 +227,16 @@ export default function CasesPane({
|
|||||||
token={context.token.access_token}
|
token={context.token.access_token}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CaseImportDialog
|
||||||
|
isOpen={isImportDialogOpen}
|
||||||
|
folderId={Number(folderId)}
|
||||||
|
isDisabled={!context.isProjectDeveloper(Number(projectId))}
|
||||||
|
onImport={handleImport}
|
||||||
|
onCancel={() => setIsImportDialogOpen(false)}
|
||||||
|
messages={messages}
|
||||||
|
token={context.token.access_token}
|
||||||
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
isOpen={isDeleteConfirmDialogOpen}
|
isOpen={isDeleteConfirmDialogOpen}
|
||||||
onCancel={closeDeleteConfirmDialog}
|
onCancel={closeDeleteConfirmDialog}
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ type TestCaseFilterProps = {
|
|||||||
messages: CasesMessages;
|
messages: CasesMessages;
|
||||||
priorityMessages: PriorityMessages;
|
priorityMessages: PriorityMessages;
|
||||||
testTypeMessages: TestTypeMessages;
|
testTypeMessages: TestTypeMessages;
|
||||||
activeTitleFilter: string;
|
activeSearchFilter: string;
|
||||||
activePriorityFilters: number[];
|
activePriorityFilters: number[];
|
||||||
activeTypeFilters: number[];
|
activeTypeFilters: number[];
|
||||||
activeTagFilters: number[];
|
activeTagFilters: number[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
onFilterChange: (title: string, priorities: number[], types: number[], tags: number[]) => void;
|
onFilterChange: (search: string, priorities: number[], types: number[], tags: number[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Tag = Pick<TagType, 'id' | 'name'>;
|
type Tag = Pick<TagType, 'id' | 'name'>;
|
||||||
@@ -37,7 +37,7 @@ export default function TestCaseFilter({
|
|||||||
messages,
|
messages,
|
||||||
priorityMessages,
|
priorityMessages,
|
||||||
testTypeMessages,
|
testTypeMessages,
|
||||||
activeTitleFilter,
|
activeSearchFilter,
|
||||||
activePriorityFilters,
|
activePriorityFilters,
|
||||||
activeTypeFilters,
|
activeTypeFilters,
|
||||||
activeTagFilters,
|
activeTagFilters,
|
||||||
@@ -45,7 +45,7 @@ export default function TestCaseFilter({
|
|||||||
projectId,
|
projectId,
|
||||||
}: TestCaseFilterProps) {
|
}: TestCaseFilterProps) {
|
||||||
const tokenContext = useContext(TokenContext);
|
const tokenContext = useContext(TokenContext);
|
||||||
const [title, setTitle] = useState<string>('');
|
const [search, setSearch] = useState<string>('');
|
||||||
const [selectedPriorities, setSelectedPriorities] = useState<Selection>(new Set([]));
|
const [selectedPriorities, setSelectedPriorities] = useState<Selection>(new Set([]));
|
||||||
const [selectedTypes, setSelectedTypes] = useState<Selection>(new Set([]));
|
const [selectedTypes, setSelectedTypes] = useState<Selection>(new Set([]));
|
||||||
const [selectedTags, setSelectedTags] = useState<Selection>(new Set([]));
|
const [selectedTags, setSelectedTags] = useState<Selection>(new Set([]));
|
||||||
@@ -74,8 +74,8 @@ export default function TestCaseFilter({
|
|||||||
}, [activeTagFilters]);
|
}, [activeTagFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle(activeTitleFilter);
|
setSearch(activeSearchFilter);
|
||||||
}, [activeTitleFilter]);
|
}, [activeSearchFilter]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activePriorityFilters.length > 0) {
|
if (activePriorityFilters.length > 0) {
|
||||||
@@ -125,7 +125,7 @@ export default function TestCaseFilter({
|
|||||||
.filter((id) => !isNaN(id));
|
.filter((id) => !isNaN(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterChange(title, priorityIndices, typeIndices, tagIds);
|
onFilterChange(search, priorityIndices, typeIndices, tagIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClearFilter = () => {
|
const handleClearFilter = () => {
|
||||||
@@ -137,7 +137,7 @@ export default function TestCaseFilter({
|
|||||||
return (
|
return (
|
||||||
<div className="p-3">
|
<div className="p-3">
|
||||||
<div className="mb-3 space-y-1">
|
<div className="mb-3 space-y-1">
|
||||||
<h3 className="text-default-500 text-small">{messages.caseTitle}</h3>
|
<h3 className="text-default-500 text-small">{messages.caseTitleOrDescription}</h3>
|
||||||
<Input
|
<Input
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
classNames={{
|
classNames={{
|
||||||
@@ -148,8 +148,8 @@ export default function TestCaseFilter({
|
|||||||
size="sm"
|
size="sm"
|
||||||
startContent={<SearchIcon size={18} />}
|
startContent={<SearchIcon size={18} />}
|
||||||
type="search"
|
type="search"
|
||||||
value={title}
|
value={search}
|
||||||
onValueChange={setTitle}
|
onValueChange={setSearch}
|
||||||
maxLength={100}
|
maxLength={100}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
Plus,
|
Plus,
|
||||||
MoreVertical,
|
MoreVertical,
|
||||||
Trash,
|
Trash,
|
||||||
|
FileUp,
|
||||||
FileDown,
|
FileDown,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
@@ -45,9 +46,10 @@ type Props = {
|
|||||||
onCreateCase: () => void;
|
onCreateCase: () => void;
|
||||||
onDeleteCase: (caseId: number) => void;
|
onDeleteCase: (caseId: number) => void;
|
||||||
onDeleteCases: (caseIds: number[]) => void;
|
onDeleteCases: (caseIds: number[]) => void;
|
||||||
|
onShowImportDialog: () => void;
|
||||||
onExportCases: (type: string) => void;
|
onExportCases: (type: string) => void;
|
||||||
onFilterChange: (query: string, priorities: number[], types: number[], tag: number[]) => void;
|
onFilterChange: (query: string, priorities: number[], types: number[], tag: number[]) => void;
|
||||||
activeTitleFilter: string;
|
activeSearchFilter: string;
|
||||||
activePriorityFilters: number[];
|
activePriorityFilters: number[];
|
||||||
activeTypeFilters: number[];
|
activeTypeFilters: number[];
|
||||||
activeTagFilters: number[];
|
activeTagFilters: number[];
|
||||||
@@ -64,9 +66,10 @@ export default function TestCaseTable({
|
|||||||
onCreateCase,
|
onCreateCase,
|
||||||
onDeleteCase,
|
onDeleteCase,
|
||||||
onDeleteCases,
|
onDeleteCases,
|
||||||
|
onShowImportDialog,
|
||||||
onExportCases,
|
onExportCases,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
activeTitleFilter,
|
activeSearchFilter,
|
||||||
activePriorityFilters,
|
activePriorityFilters,
|
||||||
activeTypeFilters,
|
activeTypeFilters,
|
||||||
activeTagFilters,
|
activeTagFilters,
|
||||||
@@ -105,7 +108,7 @@ export default function TestCaseTable({
|
|||||||
>
|
>
|
||||||
{highlightSearchTerm({
|
{highlightSearchTerm({
|
||||||
text: cellValue as string,
|
text: cellValue as string,
|
||||||
searchTerm: activeTitleFilter,
|
searchTerm: activeSearchFilter,
|
||||||
})}
|
})}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@@ -147,7 +150,7 @@ export default function TestCaseTable({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[activeTitleFilter]
|
[activeSearchFilter]
|
||||||
);
|
);
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
@@ -155,7 +158,7 @@ export default function TestCaseTable({
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
const [showFilter, setShowFilter] = useState(false);
|
const [showFilter, setShowFilter] = useState(false);
|
||||||
const activeFilterNum =
|
const activeFilterNum =
|
||||||
(activeTitleFilter ? 1 : 0) + activePriorityFilters.length + activeTypeFilters.length + activeTagFilters.length;
|
(activeSearchFilter ? 1 : 0) + activePriorityFilters.length + activeTypeFilters.length + activeTagFilters.length;
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// select test case
|
// select test case
|
||||||
@@ -318,7 +321,7 @@ export default function TestCaseTable({
|
|||||||
messages={messages}
|
messages={messages}
|
||||||
priorityMessages={priorityMessages}
|
priorityMessages={priorityMessages}
|
||||||
testTypeMessages={testTypeMessages}
|
testTypeMessages={testTypeMessages}
|
||||||
activeTitleFilter={activeTitleFilter}
|
activeSearchFilter={activeSearchFilter}
|
||||||
activePriorityFilters={activePriorityFilters}
|
activePriorityFilters={activePriorityFilters}
|
||||||
activeTypeFilters={activeTypeFilters}
|
activeTypeFilters={activeTypeFilters}
|
||||||
activeTagFilters={activeTagFilters}
|
activeTagFilters={activeTagFilters}
|
||||||
@@ -355,6 +358,15 @@ export default function TestCaseTable({
|
|||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
<Button
|
||||||
|
startContent={<FileUp size={16} />}
|
||||||
|
size="sm"
|
||||||
|
variant="bordered"
|
||||||
|
className="me-2"
|
||||||
|
onPress={onShowImportDialog}
|
||||||
|
>
|
||||||
|
{messages.import}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
startContent={<Plus size={16} />}
|
startContent={<Plus size={16} />}
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export default function Page({ params }: { params: { projectId: string; folderId
|
|||||||
noCasesFound: t('no_cases_found'),
|
noCasesFound: t('no_cases_found'),
|
||||||
caseTitle: t('case_title'),
|
caseTitle: t('case_title'),
|
||||||
caseDescription: t('case_description'),
|
caseDescription: t('case_description'),
|
||||||
|
caseTitleOrDescription: t('case_title_or_description'),
|
||||||
create: t('create'),
|
create: t('create'),
|
||||||
pleaseEnter: t('please_enter'),
|
pleaseEnter: t('please_enter'),
|
||||||
apply: t('apply'),
|
apply: t('apply'),
|
||||||
@@ -48,6 +49,14 @@ export default function Page({ params }: { params: { projectId: string; folderId
|
|||||||
casesCloned: t('cases_cloned'),
|
casesCloned: t('cases_cloned'),
|
||||||
tags: t('tags'),
|
tags: t('tags'),
|
||||||
selectTags: t('select_tags'),
|
selectTags: t('select_tags'),
|
||||||
|
import: t('import'),
|
||||||
|
importCases: t('import_cases'),
|
||||||
|
importAvailable: t('import_available'),
|
||||||
|
downloadTemplate: t('download_template'),
|
||||||
|
clickToUpload: t('click_to_upload'),
|
||||||
|
orDragAndDrop: t('or_drag_and_drop'),
|
||||||
|
maxFileSize: t('max_file_size'),
|
||||||
|
casesImported: t('cases_imported'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const priorityTranslation = useTranslations('Priority');
|
const priorityTranslation = useTranslations('Priority');
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ type CasesMessages = {
|
|||||||
noCasesFound: string;
|
noCasesFound: string;
|
||||||
caseTitle: string;
|
caseTitle: string;
|
||||||
caseDescription: string;
|
caseDescription: string;
|
||||||
|
caseTitleOrDescription: string;
|
||||||
create: string;
|
create: string;
|
||||||
pleaseEnter: string;
|
pleaseEnter: string;
|
||||||
filter: string;
|
filter: string;
|
||||||
@@ -96,6 +97,14 @@ type CasesMessages = {
|
|||||||
tags: string;
|
tags: string;
|
||||||
casesCloned: string;
|
casesCloned: string;
|
||||||
selectTags: string;
|
selectTags: string;
|
||||||
|
import: string;
|
||||||
|
importCases: string;
|
||||||
|
importAvailable: string;
|
||||||
|
downloadTemplate: string;
|
||||||
|
clickToUpload: string;
|
||||||
|
orDragAndDrop: string;
|
||||||
|
maxFileSize: string;
|
||||||
|
casesImported: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CaseMessages = {
|
type CaseMessages = {
|
||||||
|
|||||||
@@ -29,15 +29,15 @@ async function fetchCase(jwt: string, caseId: number) {
|
|||||||
async function fetchCases(
|
async function fetchCases(
|
||||||
jwt: string,
|
jwt: string,
|
||||||
folderId: number,
|
folderId: number,
|
||||||
title?: string,
|
search?: string,
|
||||||
priority?: number[],
|
priority?: number[],
|
||||||
type?: number[],
|
type?: number[],
|
||||||
tag?: number[]
|
tag?: number[]
|
||||||
) {
|
) {
|
||||||
const queryParams = [`folderId=${folderId}`];
|
const queryParams = [`folderId=${folderId}`];
|
||||||
|
|
||||||
if (title) {
|
if (search) {
|
||||||
queryParams.push(`title=${title}`);
|
queryParams.push(`search=${search}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (priority && priority.length > 0) {
|
if (priority && priority.length > 0) {
|
||||||
@@ -235,4 +235,25 @@ async function exportCases(jwt: string, folderId: number, type: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { fetchCase, fetchCases, updateCase, createCase, deleteCases, cloneCases, exportCases };
|
async function importCases(jwt: string, folderId: number, file: File) {
|
||||||
|
const url = `${apiServer}/cases/import?folderId=${folderId}`;
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
|
},
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
logError('Error importing data', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { fetchCase, fetchCases, updateCase, createCase, deleteCases, cloneCases, exportCases, importCases };
|
||||||
|
|||||||
Reference in New Issue
Block a user