From 21b4719949f4c044b4fc29e92e17c9527beb07b0 Mon Sep 17 00:00:00 2001 From: Faton Ramadani Date: Tue, 19 Mar 2024 09:01:16 +0100 Subject: [PATCH] fix(frontend): Fix csv generation (#3439) * feat(frontend): properly convert JSON to CSV * feat(frontend): properly convert JSON to CSV --- frontend/package-lock.json | 20 ++++++ frontend/package.json | 1 + .../lib/components/table/AutoDataTable.svelte | 69 ++++++++++--------- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c9abe80f07..7e1d5cd4dd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,7 @@ "license": "AGPL-3.0", "dependencies": { "@aws-crypto/sha256-js": "^4.0.0", + "@json2csv/plainjs": "^7.0.6", "@leeoniya/ufuzzy": "^1.0.8", "@popperjs/core": "^2.11.6", "@redocly/json-to-json-schema": "^0.0.1", @@ -1044,6 +1045,20 @@ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true }, + "node_modules/@json2csv/formatters": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@json2csv/formatters/-/formatters-7.0.6.tgz", + "integrity": "sha512-hjIk1H1TR4ydU5ntIENEPgoMGW+Q7mJ+537sDFDbsk+Y3EPl2i4NfFVjw0NJRgT+ihm8X30M67mA8AS6jPidSA==" + }, + "node_modules/@json2csv/plainjs": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@json2csv/plainjs/-/plainjs-7.0.6.tgz", + "integrity": "sha512-4Md7RPDCSYpmW1HWIpWBOqCd4vWfIqm53S3e/uzQ62iGi7L3r34fK/8nhOMEe+/eVfCx8+gdSCt1d74SlacQHw==", + "dependencies": { + "@json2csv/formatters": "^7.0.6", + "@streamparser/json": "^0.0.20" + } + }, "node_modules/@kurkle/color": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", @@ -1411,6 +1426,11 @@ "node": ">=14.0.0" } }, + "node_modules/@streamparser/json": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.20.tgz", + "integrity": "sha512-VqAAkydywPpkw63WQhPVKCD3SdwXuihCUVZbbiY3SfSTGQyHmwRoq27y4dmJdZuJwd5JIlQoMPyGvMbUPY0RKQ==" + }, "node_modules/@sveltejs/adapter-static": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 29b959bbfd..39f5483eaf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -92,6 +92,7 @@ "type": "module", "dependencies": { "@aws-crypto/sha256-js": "^4.0.0", + "@json2csv/plainjs": "^7.0.6", "@leeoniya/ufuzzy": "^1.0.8", "@popperjs/core": "^2.11.6", "@redocly/json-to-json-schema": "^0.0.1", diff --git a/frontend/src/lib/components/table/AutoDataTable.svelte b/frontend/src/lib/components/table/AutoDataTable.svelte index e299b3db4c..bbd3a25e34 100644 --- a/frontend/src/lib/components/table/AutoDataTable.svelte +++ b/frontend/src/lib/components/table/AutoDataTable.svelte @@ -14,14 +14,14 @@ import DataTable from './DataTable.svelte' import Head from './Head.svelte' import Row from './Row.svelte' - import { pluralize } from '$lib/utils' + import { pluralize, sendUserToast } from '$lib/utils' import Badge from '$lib/components/common/badge/Badge.svelte' import { isEmail, isLink } from './tableUtils' import type { BadgeColor } from '../common' import Popover from '../Popover.svelte' import DarkModeObserver from '../DarkModeObserver.svelte' import Button from '../common/button/Button.svelte' - + import { Parser } from '@json2csv/plainjs' export let objects: Array> = [] let currentPage = 1 @@ -118,6 +118,16 @@ typeof objects[0][key] === 'boolean' ) } + + function convertJsonToCsv(arr: Array>): string { + try { + const parser = new Parser({}) + const csv = parser.parse(arr) + return csv + } catch (err) { + throw new Error('An error occured when generating CSV:' + err) + } + } @@ -157,39 +167,32 @@ color="light" startIcon={{ icon: Download }} on:click={() => { - const headers = - structuredObjects.length > 0 - ? Object.keys(structuredObjects[0].rowData).join(',') - : '' - const csvContent = [ - headers, // Add headers as the first row - ...structuredObjects - .filter(({ _id }) => { - if (selection.length > 0) { - return selection.includes(_id) - } else { - return true - } - }) - .map(({ rowData }) => - Object.values(rowData) - .map((field) => - /[\",\n]/.test(field) ? '"' + field.replace(/"/g, '""') + '"' : field - ) - .join(',') - ) - ].join('\n') + try { + const csvContent = convertJsonToCsv( + structuredObjects + .filter(({ _id }) => { + if (selection.length > 0) { + return selection.includes(_id) + } else { + return true + } + }) + .map((obj) => obj.rowData) + ) - const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }) - const url = URL.createObjectURL(blob) - const link = document.createElement('a') - link.setAttribute('href', url) - link.setAttribute('download', 'data.csv') - link.style.visibility = 'hidden' - document.body.appendChild(link) - link.click() + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }) + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + link.setAttribute('href', url) + link.setAttribute('download', 'data.csv') + link.style.visibility = 'hidden' + document.body.appendChild(link) + link.click() - document.body.removeChild(link) + document.body.removeChild(link) + } catch (err) { + sendUserToast(err, true) + } }} > {#if selection.length > 0}