From 8fa0d0890be0be9fdf158df2ea907f2b9b8ffd1a Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Wed, 26 Jun 2024 09:22:16 -0400 Subject: [PATCH] [released bug] UI: Fix exporting data as CSV for json results (#19955) --- changes/19683-csv-comma-bug | 1 + .../convert_to_csv/convert_to_csv.tests.ts | 26 ++++++++++++++ frontend/utilities/convert_to_csv/index.ts | 34 +++++++++++++++---- 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 changes/19683-csv-comma-bug diff --git a/changes/19683-csv-comma-bug b/changes/19683-csv-comma-bug new file mode 100644 index 0000000000..d5b3f72667 --- /dev/null +++ b/changes/19683-csv-comma-bug @@ -0,0 +1 @@ +- Fix exporting CSVs with fields that contain commas to render properly diff --git a/frontend/utilities/convert_to_csv/convert_to_csv.tests.ts b/frontend/utilities/convert_to_csv/convert_to_csv.tests.ts index 5add0df5b1..0937a81f46 100644 --- a/frontend/utilities/convert_to_csv/convert_to_csv.tests.ts +++ b/frontend/utilities/convert_to_csv/convert_to_csv.tests.ts @@ -11,10 +11,36 @@ const objArray = [ }, ]; +const objArray2 = [ + { + host_display_name: "Rachel@Fleet", + last_fetched: "2024-06-25T13:11:18Z", + uid: "145", + json_result: { + "AC Power:": { + acwake: "0", + hibernatefile: "/var/vm/sleepimage", + }, + }, + }, +]; + +const tableHeaders = [ + { id: "host_display_name", sortType: "caseInsensitive" }, + { id: "last_fetched", sortType: "caseInsensitive" }, + { id: "uid", sortType: "alphanumeric" }, + { id: "json_result", sortType: "caseInsensitive" }, +]; + describe("convertToCSV - utility", () => { it("converts an array of objects to CSV format", () => { expect(convertToCSV({ objArray })).toEqual( '"first_name","last_name"\n"Mike","Stone"\n"Paul","Simon"' ); }); + it("correctly creates table headers and fields with quotes and commas to CSV format", () => { + expect(convertToCSV({ objArray: objArray2, tableHeaders })).toEqual( + '"host_display_name","last_fetched","uid","json_result"\n"Rachel@Fleet","2024-06-25T13:11:18Z","145","{""AC Power:"":{""acwake"":""0"",""hibernatefile"":""/var/vm/sleepimage""}}"' + ); + }); }); diff --git a/frontend/utilities/convert_to_csv/index.ts b/frontend/utilities/convert_to_csv/index.ts index e5c39cb242..e6b5eb9962 100644 --- a/frontend/utilities/convert_to_csv/index.ts +++ b/frontend/utilities/convert_to_csv/index.ts @@ -1,7 +1,7 @@ const defaultFieldSortFunc = (fields: string[]) => fields; interface ConvertToCSV { - objArray: any; // TODO: typing + objArray: any[]; // TODO: typing fieldSortFunc?: (fields: string[]) => string[]; tableHeaders?: any[]; // TODO: typing } @@ -12,21 +12,43 @@ const convertToCSV = ({ tableHeaders, }: ConvertToCSV) => { const tableHeadersStrings: string[] = tableHeaders - ? tableHeaders.map((header: { id: string }) => header.id) // TODO: typing + ? tableHeaders.map((header: { id: string }) => header.id) : Object.keys(objArray[0]); const fields = fieldSortFunc(tableHeadersStrings); - // TODO: Remove after v5 when host_hostname is removed rom API response. - const hostNameIndex = fields.indexOf("host_hostname"); + // TODO: Revisit after v5 if column names are modified/removed from API response. + const hostNameIndex = fields.indexOf("Host"); if (hostNameIndex >= 0) { fields.splice(hostNameIndex, 1); } - // Remove end + // Revisit end + const jsonFields = fields.map((field) => JSON.stringify(field)); const rows = objArray.map((row: any) => { // TODO: typing - return fields.map((field) => JSON.stringify(row[field])).join(","); + return fields + .map((field) => { + // Check if the value of the field is a string and needs to be quoted + let value = row[field]; + + // If the value is an object, stringify it first + if (typeof value === "object") { + value = JSON.stringify(value); + } + + // Escape double quotes in the value by doubling them + if (typeof value === "string") { + value = value.replace(/"/g, '""'); + } + + // Wrap the value in double quotes to enclose any value tha + // might have a, or a " in it to distinguish them from a comma separated delimiter + value = `"${value}"`; + + return value; + }) + .join(","); }); rows.unshift(jsonFields.join(","));