diff --git a/package-lock.json b/package-lock.json index e233526c..f3a3cf27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "chart.js": "^3.7.1", "chartjs-adapter-moment": "^1.0.0", "chartjs-plugin-datalabels": "^2.0.0", + "json2csv": "^5.0.7", "moment": "^2.29.1", "prop-types": "^15.7.2", "rc-table": "^7.17.1", @@ -9311,6 +9312,31 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" }, + "node_modules/json2csv": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-5.0.7.tgz", + "integrity": "sha512-YRZbUnyaJZLZUJSRi2G/MqahCyRv9n/ds+4oIetjDF3jWQA7AG7iSeKTiZiCNqtMZM7HDyt0e/W6lEnoGEmMGA==", + "dependencies": { + "commander": "^6.1.0", + "jsonparse": "^1.3.1", + "lodash.get": "^4.4.2" + }, + "bin": { + "json2csv": "bin/json2csv.js" + }, + "engines": { + "node": ">= 10", + "npm": ">= 6.13.0" + } + }, + "node_modules/json2csv/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "engines": { + "node": ">= 6" + } + }, "node_modules/json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", @@ -9357,6 +9383,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "engines": [ + "node >= 0.2.0" + ] + }, "node_modules/jsonpointer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", @@ -9535,6 +9569,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -22551,6 +22590,23 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" }, + "json2csv": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-5.0.7.tgz", + "integrity": "sha512-YRZbUnyaJZLZUJSRi2G/MqahCyRv9n/ds+4oIetjDF3jWQA7AG7iSeKTiZiCNqtMZM7HDyt0e/W6lEnoGEmMGA==", + "requires": { + "commander": "^6.1.0", + "jsonparse": "^1.3.1", + "lodash.get": "^4.4.2" + }, + "dependencies": { + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" + } + } + }, "json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", @@ -22591,6 +22647,11 @@ } } }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + }, "jsonpointer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", @@ -22723,6 +22784,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", diff --git a/package.json b/package.json index 2ecb4221..17de3095 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "chart.js": "^3.7.1", "chartjs-adapter-moment": "^1.0.0", "chartjs-plugin-datalabels": "^2.0.0", + "json2csv": "^5.0.7", "moment": "^2.29.1", "prop-types": "^15.7.2", "rc-table": "^7.17.1", diff --git a/src/views/Task.js b/src/views/Task.js index d97307b4..2769fd34 100644 --- a/src/views/Task.js +++ b/src/views/Task.js @@ -15,6 +15,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import SotaChart from '../components/SotaChart' import { FacebookShareButton, TwitterShareButton, FacebookIcon, TwitterIcon } from 'react-share' import moment from 'moment' +import { parse } from 'json2csv' library.add(faEdit) @@ -29,6 +30,7 @@ class Task extends React.Component { item: { submissions: [], childTasks: [], parentTask: {} }, allTaskNames: [], results: [], + resultsJson: [], chartData: {}, chartKey: '', metricNames: [], @@ -41,6 +43,7 @@ class Task extends React.Component { this.handleOnChange = this.handleOnChange.bind(this) this.handleTrimTasks = this.handleTrimTasks.bind(this) this.sliceChartData = this.sliceChartData.bind(this) + this.handleCsvExport = this.handleCsvExport.bind(this) } handleShowEditModal () { @@ -150,7 +153,17 @@ class Task extends React.Component { return 0 } }) - this.setState({ results: results }) + const resultsJson = results.map(row => + ({ + key: row.id, + submissionId: this.state.item.submissions.find(e => e.name === row.submissionName).id, + name: row.submissionName, + methodName: row.methodName, + metricName: row.metricName, + metricValue: row.metricValue, + tableDate: row.evaluatedAt ? new Date(row.evaluatedAt).toLocaleDateString() : new Date(row.createdAt).toLocaleDateString() + })) + this.setState({ results: results, resultsJson: resultsJson }) this.sliceChartData(results) }) .catch(err => { @@ -225,6 +238,23 @@ class Task extends React.Component { this.setState({ metricNames: metricNames, chartKey: chartKey, chartData: chartData, isLowerBetterDict: isLowerBetterDict }) } + handleCsvExport () { + const fields = Object.keys(this.state.resultsJson[0]) + const opts = { fields } + const csv = parse(this.state.resultsJson, opts) + + const element = document.createElement('a') + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(csv)) + element.setAttribute('download', this.state.item.name) + + element.style.display = 'none' + document.body.appendChild(element) + + element.click() + + document.body.removeChild(element) + } + render () { return (
@@ -357,7 +387,7 @@ class Task extends React.Component {

{(this.state.results.length > 0) && -

Results

} +

Results

} {(this.state.results.length > 0) &&
@@ -393,16 +423,7 @@ class Task extends React.Component { key: 'metricValue', width: 300 }]} - data={this.state.results.map(row => - ({ - key: row.id, - submissionId: this.state.item.submissions.find(e => e.name === row.submissionName).id, - name: row.submissionName, - methodName: row.methodName, - metricName: row.metricName, - metricValue: row.metricValue, - tableDate: row.evaluatedAt ? new Date(row.evaluatedAt).toLocaleDateString() : new Date(row.createdAt).toLocaleDateString() - }))} + data={this.state.resultsJson} onRow={(record) => ({ onClick () { window.location.href = '/Submission/' + record.submissionId } })}