Replaced unmaintained dependency

This commit is contained in:
Alejandro Celaya 2022-03-31 20:18:05 +02:00
parent e2c8551baf
commit e875e05538
13 changed files with 189 additions and 65 deletions

162
package-lock.json generated
View file

@ -19,9 +19,10 @@
"chart.js": "^3.7.1", "chart.js": "^3.7.1",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"compare-versions": "^4.1.3", "compare-versions": "^4.1.3",
"csvjson": "^5.1.0", "csvtojson": "^2.0.10",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"event-source-polyfill": "^1.0.25", "event-source-polyfill": "^1.0.25",
"json2csv": "^5.0.7",
"leaflet": "^1.7.1", "leaflet": "^1.7.1",
"qs": "^6.9.6", "qs": "^6.9.6",
"ramda": "^0.27.2", "ramda": "^0.27.2",
@ -57,6 +58,7 @@
"@types/classnames": "^2.3.1", "@types/classnames": "^2.3.1",
"@types/enzyme": "^3.10.11", "@types/enzyme": "^3.10.11",
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@types/json2csv": "^5.0.3",
"@types/leaflet": "^1.7.9", "@types/leaflet": "^1.7.9",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@types/ramda": "0.27.38", "@types/ramda": "0.27.38",
@ -4754,6 +4756,15 @@
"integrity": "sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A==", "integrity": "sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A==",
"dev": true "dev": true
}, },
"node_modules/@types/json2csv": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@types/json2csv/-/json2csv-5.0.3.tgz",
"integrity": "sha512-ZJEv6SzhPhgpBpxZU4n/TZekbZqI4EcyXXRwms1lAITG2kIAtj85PfNYafUOY1zy8bWs5ujaub0GU4copaA0sw==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/json5": { "node_modules/@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -6843,8 +6854,7 @@
"node_modules/bluebird": { "node_modules/bluebird": {
"version": "3.7.2", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
"dev": true
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.19.0", "version": "1.19.0",
@ -8101,10 +8111,32 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz",
"integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==" "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g=="
}, },
"node_modules/csvjson": { "node_modules/csvtojson": {
"version": "5.1.0", "version": "2.0.10",
"resolved": "https://registry.yarnpkg.com/csvjson/-/csvjson-5.1.0.tgz", "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz",
"integrity": "sha1-8FVmCCTr+0TcCJ2QEmf9xdnoQUo=" "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==",
"dependencies": {
"bluebird": "^3.5.1",
"lodash": "^4.17.3",
"strip-bom": "^2.0.0"
},
"bin": {
"csvtojson": "bin/csvtojson"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/csvtojson/node_modules/strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
"dependencies": {
"is-utf8": "^0.2.0"
},
"engines": {
"node": ">=0.10.0"
}
}, },
"node_modules/damerau-levenshtein": { "node_modules/damerau-levenshtein": {
"version": "1.0.8", "version": "1.0.8",
@ -12942,6 +12974,11 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
},
"node_modules/is-weakref": { "node_modules/is-weakref": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@ -15622,6 +15659,31 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true "dev": true
}, },
"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": { "node_modules/json5": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
@ -15637,6 +15699,14 @@
"node": ">=6" "node": ">=6"
} }
}, },
"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": { "node_modules/jsonpointer": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz",
@ -15777,8 +15847,7 @@
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"dev": true
}, },
"node_modules/lodash.debounce": { "node_modules/lodash.debounce": {
"version": "4.0.8", "version": "4.0.8",
@ -15804,6 +15873,11 @@
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
"dev": true "dev": true
}, },
"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.groupby": { "node_modules/lodash.groupby": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
@ -30221,6 +30295,15 @@
"integrity": "sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A==", "integrity": "sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A==",
"dev": true "dev": true
}, },
"@types/json2csv": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@types/json2csv/-/json2csv-5.0.3.tgz",
"integrity": "sha512-ZJEv6SzhPhgpBpxZU4n/TZekbZqI4EcyXXRwms1lAITG2kIAtj85PfNYafUOY1zy8bWs5ujaub0GU4copaA0sw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/json5": { "@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -31855,8 +31938,7 @@
"bluebird": { "bluebird": {
"version": "3.7.2", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
"dev": true
}, },
"body-parser": { "body-parser": {
"version": "1.19.0", "version": "1.19.0",
@ -32853,10 +32935,25 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz",
"integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==" "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g=="
}, },
"csvjson": { "csvtojson": {
"version": "5.1.0", "version": "2.0.10",
"resolved": "https://registry.yarnpkg.com/csvjson/-/csvjson-5.1.0.tgz", "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz",
"integrity": "sha1-8FVmCCTr+0TcCJ2QEmf9xdnoQUo=" "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==",
"requires": {
"bluebird": "^3.5.1",
"lodash": "^4.17.3",
"strip-bom": "^2.0.0"
},
"dependencies": {
"strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
"requires": {
"is-utf8": "^0.2.0"
}
}
}
}, },
"damerau-levenshtein": { "damerau-levenshtein": {
"version": "1.0.8", "version": "1.0.8",
@ -36568,6 +36665,11 @@
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"dev": true "dev": true
}, },
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
},
"is-weakref": { "is-weakref": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@ -38514,6 +38616,23 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true "dev": true
}, },
"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": { "json5": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
@ -38523,6 +38642,11 @@
"minimist": "^1.2.5" "minimist": "^1.2.5"
} }
}, },
"jsonparse": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA="
},
"jsonpointer": { "jsonpointer": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.0.tgz",
@ -38633,8 +38757,7 @@
"lodash": { "lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"dev": true
}, },
"lodash.debounce": { "lodash.debounce": {
"version": "4.0.8", "version": "4.0.8",
@ -38660,6 +38783,11 @@
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
"dev": true "dev": true
}, },
"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.groupby": { "lodash.groupby": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",

View file

@ -35,9 +35,10 @@
"chart.js": "^3.7.1", "chart.js": "^3.7.1",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"compare-versions": "^4.1.3", "compare-versions": "^4.1.3",
"csvjson": "^5.1.0", "csvtojson": "^2.0.10",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"event-source-polyfill": "^1.0.25", "event-source-polyfill": "^1.0.25",
"json2csv": "^5.0.7",
"leaflet": "^1.7.1", "leaflet": "^1.7.1",
"qs": "^6.9.6", "qs": "^6.9.6",
"ramda": "^0.27.2", "ramda": "^0.27.2",
@ -73,6 +74,7 @@
"@types/classnames": "^2.3.1", "@types/classnames": "^2.3.1",
"@types/enzyme": "^3.10.11", "@types/enzyme": "^3.10.11",
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@types/json2csv": "^5.0.3",
"@types/leaflet": "^1.7.9", "@types/leaflet": "^1.7.9",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@types/ramda": "0.27.38", "@types/ramda": "0.27.38",

View file

@ -7,11 +7,4 @@ declare module 'event-source-polyfill' {
} }
} }
declare module 'csvjson' {
export declare class CsvJson {
public toObject<T>(content: string): T[];
public toCSV<T>(data: T[], options: { headers: 'full' | 'none' | 'relative' | 'key'; wrap?: true }): string;
}
}
declare module '*.png' declare module '*.png'

View file

@ -1,13 +1,10 @@
import { CsvJson } from 'csvjson';
import { NormalizedVisit } from '../../visits/types'; import { NormalizedVisit } from '../../visits/types';
import { ExportableShortUrl } from '../../short-urls/data'; import { ExportableShortUrl } from '../../short-urls/data';
import { saveCsv } from '../../utils/helpers/files'; import { saveCsv } from '../../utils/helpers/files';
import { JsonToCsv } from '../../utils/helpers/csvjson';
export class ReportExporter { export class ReportExporter {
public constructor( public constructor(private readonly window: Window, private readonly jsonToCsv: JsonToCsv) {}
private readonly window: Window,
private readonly csvjson: CsvJson,
) {}
public readonly exportVisits = (filename: string, visits: NormalizedVisit[]) => { public readonly exportVisits = (filename: string, visits: NormalizedVisit[]) => {
if (!visits.length) { if (!visits.length) {
@ -26,7 +23,7 @@ export class ReportExporter {
}; };
private readonly exportCsv = (filename: string, rows: object[]) => { private readonly exportCsv = (filename: string, rows: object[]) => {
const csv = this.csvjson.toCSV(rows, { headers: 'key', wrap: true }); const csv = this.jsonToCsv(rows);
saveCsv(this.window, csv, filename); saveCsv(this.window, csv, filename);
}; };

View file

@ -20,7 +20,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.constant('axios', axios); bottle.constant('axios', axios);
bottle.service('ImageDownloader', ImageDownloader, 'axios', 'window'); bottle.service('ImageDownloader', ImageDownloader, 'axios', 'window');
bottle.service('ReportExporter', ReportExporter, 'window', 'csvjson'); bottle.service('ReportExporter', ReportExporter, 'window', 'jsonToCsv');
// Components // Components
bottle.serviceFactory('ScrollToTop', ScrollToTop); bottle.serviceFactory('ScrollToTop', ScrollToTop);

View file

@ -1,8 +1,8 @@
import { values } from 'ramda'; import { values } from 'ramda';
import { CsvJson } from 'csvjson';
import LocalStorage from '../../utils/services/LocalStorage'; import LocalStorage from '../../utils/services/LocalStorage';
import { ServersMap, serverWithIdToServerData } from '../data'; import { ServersMap, serverWithIdToServerData } from '../data';
import { saveCsv } from '../../utils/helpers/files'; import { saveCsv } from '../../utils/helpers/files';
import { JsonToCsv } from '../../utils/helpers/csvjson';
const SERVERS_FILENAME = 'shlink-servers.csv'; const SERVERS_FILENAME = 'shlink-servers.csv';
@ -10,14 +10,14 @@ export default class ServersExporter {
public constructor( public constructor(
private readonly storage: LocalStorage, private readonly storage: LocalStorage,
private readonly window: Window, private readonly window: Window,
private readonly csvjson: CsvJson, private readonly jsonToCsv: JsonToCsv,
) {} ) {}
public readonly exportServers = async () => { public readonly exportServers = async () => {
const servers = values(this.storage.get<ServersMap>('servers') ?? {}).map(serverWithIdToServerData); const servers = values(this.storage.get<ServersMap>('servers') ?? {}).map(serverWithIdToServerData);
try { try {
const csv = this.csvjson.toCSV(servers, { headers: 'key' }); const csv = this.jsonToCsv(servers);
saveCsv(this.window, csv, SERVERS_FILENAME); saveCsv(this.window, csv, SERVERS_FILENAME);
} catch (e) { } catch (e) {

View file

@ -1,5 +1,5 @@
import { CsvJson } from 'csvjson';
import { ServerData } from '../data'; import { ServerData } from '../data';
import { CsvToJson } from '../../utils/helpers/csvjson';
const validateServer = (server: any): server is ServerData => const validateServer = (server: any): server is ServerData =>
typeof server.url === 'string' && typeof server.apiKey === 'string' && typeof server.name === 'string'; typeof server.url === 'string' && typeof server.apiKey === 'string' && typeof server.name === 'string';
@ -8,7 +8,7 @@ const validateServers = (servers: any): servers is ServerData[] =>
Array.isArray(servers) && servers.every(validateServer); Array.isArray(servers) && servers.every(validateServer);
export class ServersImporter { export class ServersImporter {
public constructor(private readonly csvJson: CsvJson, private readonly fileReaderFactory: () => FileReader) {} public constructor(private readonly csvToJson: CsvToJson, private readonly fileReaderFactory: () => FileReader) {}
public readonly importServersFromFile = async (file?: File | null): Promise<ServerData[]> => { public readonly importServersFromFile = async (file?: File | null): Promise<ServerData[]> => {
if (!file) { if (!file) {
@ -18,11 +18,11 @@ export class ServersImporter {
const reader = this.fileReaderFactory(); const reader = this.fileReaderFactory();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
reader.addEventListener('loadend', (e: ProgressEvent<FileReader>) => { reader.addEventListener('loadend', async (e: ProgressEvent<FileReader>) => {
try { try {
// TODO Read as stream, otherwise, if the file is too big, this will block the browser tab // TODO Read as stream, otherwise, if the file is too big, this will block the browser tab
const content = e.target?.result?.toString() ?? ''; const content = e.target?.result?.toString() ?? '';
const servers = this.csvJson.toObject(content); const servers = await this.csvToJson(content);
if (!validateServers(servers)) { if (!validateServers(servers)) {
throw new Error('Provided file does not have the right format.'); throw new Error('Provided file does not have the right format.');

View file

@ -1,4 +1,3 @@
import csvjson from 'csvjson';
import Bottle from 'bottlejs'; import Bottle from 'bottlejs';
import CreateServer from '../CreateServer'; import CreateServer from '../CreateServer';
import ServersDropdown from '../ServersDropdown'; import ServersDropdown from '../ServersDropdown';
@ -69,10 +68,9 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
)); ));
// Services // Services
bottle.constant('csvjson', csvjson);
bottle.constant('fileReaderFactory', () => new FileReader()); bottle.constant('fileReaderFactory', () => new FileReader());
bottle.service('ServersImporter', ServersImporter, 'csvjson', 'fileReaderFactory'); bottle.service('ServersImporter', ServersImporter, 'csvToJson', 'fileReaderFactory');
bottle.service('ServersExporter', ServersExporter, 'Storage', 'window', 'csvjson'); bottle.service('ServersExporter', ServersExporter, 'Storage', 'window', 'jsonToCsv');
// Actions // Actions
bottle.serviceFactory('selectServer', selectServer, 'buildShlinkApiClient', 'loadMercureInfo'); bottle.serviceFactory('selectServer', selectServer, 'buildShlinkApiClient', 'loadMercureInfo');

View file

@ -0,0 +1,12 @@
import csv from 'csvtojson';
import { parse } from 'json2csv';
export const csvToJson = <T>(csvContent: string) => new Promise<T[]>((resolve) => {
csv().fromString(csvContent).then(resolve);
});
export type CsvToJson = typeof csvToJson;
export const jsonToCsv = <T>(data: T[]): string => parse(data);
export type JsonToCsv = typeof jsonToCsv;

View file

@ -2,12 +2,16 @@ import Bottle from 'bottlejs';
import { useStateFlagTimeout } from '../helpers/hooks'; import { useStateFlagTimeout } from '../helpers/hooks';
import LocalStorage from './LocalStorage'; import LocalStorage from './LocalStorage';
import ColorGenerator from './ColorGenerator'; import ColorGenerator from './ColorGenerator';
import { csvToJson, jsonToCsv } from '../helpers/csvjson';
const provideServices = (bottle: Bottle) => { const provideServices = (bottle: Bottle) => {
bottle.constant('localStorage', (global as any).localStorage); bottle.constant('localStorage', (global as any).localStorage);
bottle.service('Storage', LocalStorage, 'localStorage'); bottle.service('Storage', LocalStorage, 'localStorage');
bottle.service('ColorGenerator', ColorGenerator, 'Storage'); bottle.service('ColorGenerator', ColorGenerator, 'Storage');
bottle.constant('csvToJson', csvToJson);
bottle.constant('jsonToCsv', jsonToCsv);
bottle.constant('setTimeout', global.setTimeout); bottle.constant('setTimeout', global.setTimeout);
bottle.constant('clearTimeout', global.clearTimeout); bottle.constant('clearTimeout', global.clearTimeout);
bottle.serviceFactory('useStateFlagTimeout', useStateFlagTimeout, 'setTimeout', 'clearTimeout'); bottle.serviceFactory('useStateFlagTimeout', useStateFlagTimeout, 'setTimeout', 'clearTimeout');

View file

@ -1,13 +1,10 @@
import { Mock } from 'ts-mockery';
import { CsvJson } from 'csvjson';
import { ReportExporter } from '../../../src/common/services/ReportExporter'; import { ReportExporter } from '../../../src/common/services/ReportExporter';
import { NormalizedVisit } from '../../../src/visits/types'; import { NormalizedVisit } from '../../../src/visits/types';
import { windowMock } from '../../mocks/WindowMock'; import { windowMock } from '../../mocks/WindowMock';
import { ExportableShortUrl } from '../../../src/short-urls/data'; import { ExportableShortUrl } from '../../../src/short-urls/data';
describe('ReportExporter', () => { describe('ReportExporter', () => {
const toCSV = jest.fn(); const jsonToCsv = jest.fn();
const csvToJsonMock = Mock.of<CsvJson>({ toCSV });
let exporter: ReportExporter; let exporter: ReportExporter;
beforeEach(jest.clearAllMocks); beforeEach(jest.clearAllMocks);
@ -15,7 +12,7 @@ describe('ReportExporter', () => {
(global as any).Blob = class Blob {}; (global as any).Blob = class Blob {};
(global as any).URL = { createObjectURL: () => '' }; (global as any).URL = { createObjectURL: () => '' };
exporter = new ReportExporter(windowMock, csvToJsonMock); exporter = new ReportExporter(windowMock, jsonToCsv);
}); });
describe('exportVisits', () => { describe('exportVisits', () => {
@ -36,13 +33,13 @@ describe('ReportExporter', () => {
exporter.exportVisits('my_visits.csv', visits); exporter.exportVisits('my_visits.csv', visits);
expect(toCSV).toHaveBeenCalledWith(visits, { headers: 'key', wrap: true }); expect(jsonToCsv).toHaveBeenCalledWith(visits);
}); });
it('skips execution when list of visits is empty', () => { it('skips execution when list of visits is empty', () => {
exporter.exportVisits('my_visits.csv', []); exporter.exportVisits('my_visits.csv', []);
expect(toCSV).not.toHaveBeenCalled(); expect(jsonToCsv).not.toHaveBeenCalled();
}); });
}); });
@ -61,13 +58,13 @@ describe('ReportExporter', () => {
exporter.exportShortUrls(shortUrls); exporter.exportShortUrls(shortUrls);
expect(toCSV).toHaveBeenCalledWith(shortUrls, { headers: 'key', wrap: true }); expect(jsonToCsv).toHaveBeenCalledWith(shortUrls);
}); });
it('skips execution when list of visits is empty', () => { it('skips execution when list of visits is empty', () => {
exporter.exportShortUrls([]); exporter.exportShortUrls([]);
expect(toCSV).not.toHaveBeenCalled(); expect(jsonToCsv).not.toHaveBeenCalled();
}); });
}); });
}); });

View file

@ -1,5 +1,4 @@
import { Mock } from 'ts-mockery'; import { Mock } from 'ts-mockery';
import { CsvJson } from 'csvjson';
import ServersExporter from '../../../src/servers/services/ServersExporter'; import ServersExporter from '../../../src/servers/services/ServersExporter';
import LocalStorage from '../../../src/utils/services/LocalStorage'; import LocalStorage from '../../../src/utils/services/LocalStorage';
import { appendChild, removeChild, windowMock } from '../../mocks/WindowMock'; import { appendChild, removeChild, windowMock } from '../../mocks/WindowMock';
@ -22,9 +21,7 @@ describe('ServersExporter', () => {
const erroneousToCsv = jest.fn(() => { const erroneousToCsv = jest.fn(() => {
throw new Error(''); throw new Error('');
}); });
const createCsvjsonMock = (throwError = false) => Mock.of<CsvJson>({ const createCsvjsonMock = (throwError = false) => (throwError ? erroneousToCsv : jest.fn(() => ''));
toCSV: throwError ? erroneousToCsv : jest.fn(() => ''),
});
beforeEach(jest.clearAllMocks); beforeEach(jest.clearAllMocks);

View file

@ -1,12 +1,10 @@
import { Mock } from 'ts-mockery'; import { Mock } from 'ts-mockery';
import { CsvJson } from 'csvjson';
import { ServersImporter } from '../../../src/servers/services/ServersImporter'; import { ServersImporter } from '../../../src/servers/services/ServersImporter';
import { RegularServer } from '../../../src/servers/data'; import { RegularServer } from '../../../src/servers/data';
describe('ServersImporter', () => { describe('ServersImporter', () => {
const servers: RegularServer[] = [Mock.all<RegularServer>(), Mock.all<RegularServer>()]; const servers: RegularServer[] = [Mock.all<RegularServer>(), Mock.all<RegularServer>()];
const toObject = jest.fn().mockReturnValue(servers); const csvjsonMock = jest.fn().mockResolvedValue(servers);
const csvjsonMock = Mock.of<CsvJson>({ toObject });
const readAsText = jest.fn(); const readAsText = jest.fn();
const fileReaderMock = Mock.of<FileReader>({ const fileReaderMock = Mock.of<FileReader>({
readAsText, readAsText,
@ -28,9 +26,7 @@ describe('ServersImporter', () => {
it('rejects with error if parsing the file fails', async () => { it('rejects with error if parsing the file fails', async () => {
const expectedError = new Error('Error parsing file'); const expectedError = new Error('Error parsing file');
toObject.mockImplementation(() => { csvjsonMock.mockRejectedValue(expectedError);
throw expectedError;
});
await expect(importer.importServersFromFile(Mock.of<File>({ type: 'text/html' }))).rejects.toEqual(expectedError); await expect(importer.importServersFromFile(Mock.of<File>({ type: 'text/html' }))).rejects.toEqual(expectedError);
}); });
@ -59,7 +55,7 @@ describe('ServersImporter', () => {
], ],
], ],
])('rejects with error if provided file does not parse to valid list of servers', async (parsedObject) => { ])('rejects with error if provided file does not parse to valid list of servers', async (parsedObject) => {
toObject.mockReturnValue(parsedObject); csvjsonMock.mockResolvedValue(parsedObject);
await expect(importer.importServersFromFile(Mock.of<File>({ type: 'text/html' }))).rejects.toEqual( await expect(importer.importServersFromFile(Mock.of<File>({ type: 'text/html' }))).rejects.toEqual(
new Error('Provided file does not have the right format.'), new Error('Provided file does not have the right format.'),
@ -80,13 +76,13 @@ describe('ServersImporter', () => {
}, },
]; ];
toObject.mockReturnValue(expectedServers); csvjsonMock.mockResolvedValue(expectedServers);
const result = await importer.importServersFromFile(Mock.all<File>()); const result = await importer.importServersFromFile(Mock.all<File>());
expect(result).toEqual(expectedServers); expect(result).toEqual(expectedServers);
expect(readAsText).toHaveBeenCalledTimes(1); expect(readAsText).toHaveBeenCalledTimes(1);
expect(toObject).toHaveBeenCalledTimes(1); expect(csvjsonMock).toHaveBeenCalledTimes(1);
}); });
}); });
}); });