From 7c60dd4d3eae3d233222bb648c6a71fc82c48f60 Mon Sep 17 00:00:00 2001 From: Daniel Strano Date: Mon, 7 Mar 2022 17:14:45 -0500 Subject: [PATCH 1/2] #459: Export task to CSV --- package-lock.json | 22 ++++++++++++++++++++++ package.json | 2 ++ src/views/Task.js | 44 ++++++++++++++++++++++++++++++++------------ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index e233526c..ac258d93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,8 @@ "chart.js": "^3.7.1", "chartjs-adapter-moment": "^1.0.0", "chartjs-plugin-datalabels": "^2.0.0", + "export-to-csv": "^0.2.1", + "export-to-csv-fix-source-map": "^0.2.1", "moment": "^2.29.1", "prop-types": "^15.7.2", "rc-table": "^7.17.1", @@ -6899,6 +6901,16 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/export-to-csv": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/export-to-csv/-/export-to-csv-0.2.1.tgz", + "integrity": "sha512-KTbrd3CAZ0cFceJEZr1e5uiMasabeCpXq1/5uvVxDl53o4jXJHnltasQoj2NkzrxD8hU9kdwjnMhoir/7nNx/A==" + }, + "node_modules/export-to-csv-fix-source-map": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/export-to-csv-fix-source-map/-/export-to-csv-fix-source-map-0.2.1.tgz", + "integrity": "sha512-02CGZG9Kduc0l9ErJ5b+99+PjeQIyoeVGz+f7/Yc04V4YKNPbdT63sQqBC5RW05va4w0MZen7pQpf92H3funYw==" + }, "node_modules/express": { "version": "4.17.2", "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", @@ -20782,6 +20794,16 @@ "jest-message-util": "^27.4.6" } }, + "export-to-csv": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/export-to-csv/-/export-to-csv-0.2.1.tgz", + "integrity": "sha512-KTbrd3CAZ0cFceJEZr1e5uiMasabeCpXq1/5uvVxDl53o4jXJHnltasQoj2NkzrxD8hU9kdwjnMhoir/7nNx/A==" + }, + "export-to-csv-fix-source-map": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/export-to-csv-fix-source-map/-/export-to-csv-fix-source-map-0.2.1.tgz", + "integrity": "sha512-02CGZG9Kduc0l9ErJ5b+99+PjeQIyoeVGz+f7/Yc04V4YKNPbdT63sQqBC5RW05va4w0MZen7pQpf92H3funYw==" + }, "express": { "version": "4.17.2", "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", diff --git a/package.json b/package.json index 2ecb4221..bc9dd21c 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "chart.js": "^3.7.1", "chartjs-adapter-moment": "^1.0.0", "chartjs-plugin-datalabels": "^2.0.0", + "export-to-csv": "^0.2.1", + "export-to-csv-fix-source-map": "^0.2.1", "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..1056f002 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 { ExportToCsv } from 'export-to-csv' 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,22 @@ class Task extends React.Component { this.setState({ metricNames: metricNames, chartKey: chartKey, chartData: chartData, isLowerBetterDict: isLowerBetterDict }) } + handleCsvExport () { + const options = { + filename: this.state.item.name, + fieldSeparator: ',', + quoteStrings: '"', + decimalSeparator: '.', + showLabels: true, + showTitle: false, + useTextFile: false, + useBom: true, + useKeysAsHeaders: true + } + const csvExporter = new ExportToCsv(options) + csvExporter.generateCsv(this.state.resultsJson) + } + render () { return (
@@ -357,7 +386,7 @@ class Task extends React.Component {

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

Results

} +

Results

} {(this.state.results.length > 0) &&
@@ -393,16 +422,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 } })} From 2db19476dfe9e6520d3b154de73e6491dc8c9c84 Mon Sep 17 00:00:00 2001 From: Daniel Strano Date: Tue, 8 Mar 2022 14:27:35 -0500 Subject: [PATCH 2/2] Replace export-to-csv with json2csv --- package-lock.json | 88 +++++++++++++++++++++++++++++++++++------------ package.json | 3 +- src/views/Task.js | 29 ++++++++-------- 3 files changed, 82 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac258d93..f3a3cf27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,8 +20,7 @@ "chart.js": "^3.7.1", "chartjs-adapter-moment": "^1.0.0", "chartjs-plugin-datalabels": "^2.0.0", - "export-to-csv": "^0.2.1", - "export-to-csv-fix-source-map": "^0.2.1", + "json2csv": "^5.0.7", "moment": "^2.29.1", "prop-types": "^15.7.2", "rc-table": "^7.17.1", @@ -6901,16 +6900,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/export-to-csv": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/export-to-csv/-/export-to-csv-0.2.1.tgz", - "integrity": "sha512-KTbrd3CAZ0cFceJEZr1e5uiMasabeCpXq1/5uvVxDl53o4jXJHnltasQoj2NkzrxD8hU9kdwjnMhoir/7nNx/A==" - }, - "node_modules/export-to-csv-fix-source-map": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/export-to-csv-fix-source-map/-/export-to-csv-fix-source-map-0.2.1.tgz", - "integrity": "sha512-02CGZG9Kduc0l9ErJ5b+99+PjeQIyoeVGz+f7/Yc04V4YKNPbdT63sQqBC5RW05va4w0MZen7pQpf92H3funYw==" - }, "node_modules/express": { "version": "4.17.2", "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", @@ -9323,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", @@ -9369,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", @@ -9547,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", @@ -20794,16 +20821,6 @@ "jest-message-util": "^27.4.6" } }, - "export-to-csv": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/export-to-csv/-/export-to-csv-0.2.1.tgz", - "integrity": "sha512-KTbrd3CAZ0cFceJEZr1e5uiMasabeCpXq1/5uvVxDl53o4jXJHnltasQoj2NkzrxD8hU9kdwjnMhoir/7nNx/A==" - }, - "export-to-csv-fix-source-map": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/export-to-csv-fix-source-map/-/export-to-csv-fix-source-map-0.2.1.tgz", - "integrity": "sha512-02CGZG9Kduc0l9ErJ5b+99+PjeQIyoeVGz+f7/Yc04V4YKNPbdT63sQqBC5RW05va4w0MZen7pQpf92H3funYw==" - }, "express": { "version": "4.17.2", "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", @@ -22573,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", @@ -22613,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", @@ -22745,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 bc9dd21c..17de3095 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,7 @@ "chart.js": "^3.7.1", "chartjs-adapter-moment": "^1.0.0", "chartjs-plugin-datalabels": "^2.0.0", - "export-to-csv": "^0.2.1", - "export-to-csv-fix-source-map": "^0.2.1", + "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 1056f002..2769fd34 100644 --- a/src/views/Task.js +++ b/src/views/Task.js @@ -15,7 +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 { ExportToCsv } from 'export-to-csv' +import { parse } from 'json2csv' library.add(faEdit) @@ -239,19 +239,20 @@ class Task extends React.Component { } handleCsvExport () { - const options = { - filename: this.state.item.name, - fieldSeparator: ',', - quoteStrings: '"', - decimalSeparator: '.', - showLabels: true, - showTitle: false, - useTextFile: false, - useBom: true, - useKeysAsHeaders: true - } - const csvExporter = new ExportToCsv(options) - csvExporter.generateCsv(this.state.resultsJson) + 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 () {