Skip to content

Commit 8f13e60

Browse files
Refactor existing CSV converter (#1677)
* Refactor existing CSV converter https://eaflood.atlassian.net/browse/WATER-4776 As part of the work to implement notifications in the system repo we have found some logic that will need to be duplicated in upcoming work. This change lifts the convert to csv service into the lib. It has been renamed to transform to csv, The existing logic has been updated to be more precise in the transformation it is doing (transforms an array into a CSV row).
1 parent b3467b5 commit 8f13e60

File tree

4 files changed

+175
-130
lines changed

4 files changed

+175
-130
lines changed

app/services/jobs/export/convert-to-csv.service.js renamed to app/lib/transform-to-csv.lib.js

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
'use strict'
22

33
/**
4-
* Convert data to CSV format
5-
* @module ConvertToCSVService
6-
*/
7-
8-
/**
9-
* Converts data to a CSV formatted string
4+
* Transforms an array into a CSV formatted string.
5+
*
6+
* A CSV header / row is the same regardless of transformation. The header is commonly the first row.
7+
* Therefore, this function works for both a header and row.
108
*
11-
* @param {string[]} data - An array representing either the headers or rows from a db table
9+
* The function can transform most common types:
10+
* - Objects are stringified and parsed
11+
* - Numbers are converted to strings
12+
* - Booleans are converted to strings
13+
* - Falsey values are converted to empty strings
14+
* - Dates are converted into ISO string format
15+
* - Strings are escaped to handle special characters (e.g., ',', '/', '.', '\\', '"', '\n')
1216
*
13-
* @returns {string} A CSV formatted string
17+
* @param {Array} arrayToTransform - An array of data to transform. Each element can be of any type
18+
* (string, number, object, boolean, date, etc.)
19+
*
20+
* @returns {string} A CSV formatted string with each value separated by commas,
21+
* with a separated by newline characters (`\n`) to signify the end of the row.
22+
*
23+
* @private
1424
*/
15-
function go(data) {
16-
if (!data) {
25+
function transformArrayToCSVRow(arrayToTransform) {
26+
if (!arrayToTransform) {
1727
return undefined
1828
}
1929

20-
return _transformDataToCSV(data)
21-
}
22-
23-
/**
24-
* Transforms each row or header to CSV format and joins the values with commas
25-
*
26-
* @private
27-
*/
28-
function _transformDataToCSV(data) {
29-
const transformedRow = data
30+
const transformedRow = arrayToTransform
3031
.map((value) => {
3132
return _transformValueToCSV(value)
3233
})
@@ -72,5 +73,5 @@ function _transformValueToCSV(value) {
7273
}
7374

7475
module.exports = {
75-
go
76+
transformArrayToCSVRow
7677
}

app/services/jobs/export/write-table-to-file.service.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const { pipeline, Transform } = require('stream')
1111
const path = require('path')
1212
const util = require('util')
1313

14-
const ConvertToCSVService = require('./convert-to-csv.service.js')
14+
const { transformArrayToCSVRow } = require('../../../lib/transform-to-csv.lib.js')
1515

1616
/**
1717
* Converts data into CSV format and writes it to a file
@@ -30,7 +30,7 @@ async function go(headers, rows, schemaFolderPath, tableName) {
3030

3131
const transformDataStream = _transformDataStream()
3232

33-
const convertedHeaders = ConvertToCSVService.go(headers)
33+
const convertedHeaders = transformArrayToCSVRow(headers)
3434

3535
writeToFileStream.write(convertedHeaders)
3636

@@ -48,7 +48,7 @@ function _transformDataStream() {
4848
return new Transform({
4949
objectMode: true,
5050
transform: function (row, _encoding, callback) {
51-
const datRow = ConvertToCSVService.go(Object.values(row))
51+
const datRow = transformArrayToCSVRow(Object.values(row))
5252

5353
callback(null, datRow)
5454
}

test/lib/transform-to-csv.lib.test.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
'use strict'
2+
3+
// Test framework dependencies
4+
const Lab = require('@hapi/lab')
5+
const Code = require('@hapi/code')
6+
7+
const { describe, it, beforeEach } = (exports.lab = Lab.script())
8+
const { expect } = Code
9+
10+
// Thing under test
11+
const { transformArrayToCSVRow } = require('../../app/lib/transform-to-csv.lib.js')
12+
13+
describe('Transform to csv', () => {
14+
describe('#transformArrayToCSVRow', () => {
15+
let testArray
16+
17+
beforeEach(() => {
18+
testArray = _testArray()
19+
})
20+
21+
it('correctly transforms all data types to csv', () => {
22+
const result = transformArrayToCSVRow(testArray)
23+
24+
expect(result).to.equal(
25+
'"20146cdc-9b40-4769-aa78-b51c17080d56",' +
26+
'"4.1.1",' +
27+
'9700,' +
28+
'"Low loss tidal abstraction of water up to and ""including"" 25,002 megalitres, known as ML/yr a year where no model applies",' +
29+
'2022-12-14T18:39:45.000Z,' +
30+
'true,' +
31+
',' +
32+
',' +
33+
'false,' +
34+
',' +
35+
'25002,' +
36+
'"{""message"": ""a json object""}"' +
37+
'\n'
38+
)
39+
})
40+
41+
describe('when the data type is', () => {
42+
describe('an object', () => {
43+
it('correctly formats the object to a string', () => {
44+
const result = transformArrayToCSVRow([{ message: 'a json object' }])
45+
46+
expect(result).to.equal('"{""message"": ""a json object""}"\n')
47+
})
48+
})
49+
50+
describe('a UUID', () => {
51+
it('correctly formats the UUID to a string', () => {
52+
const result = transformArrayToCSVRow(['20146cdc-9b40-4769-aa78-b51c17080d56'])
53+
54+
expect(result).to.equal('"20146cdc-9b40-4769-aa78-b51c17080d56"\n')
55+
})
56+
})
57+
58+
describe('a boolean', () => {
59+
it('correctly formats the boolean to a string', () => {
60+
const result = transformArrayToCSVRow([true])
61+
62+
expect(result).to.equal('true\n')
63+
})
64+
})
65+
66+
describe('a number', () => {
67+
it('correctly formats the number to a string', () => {
68+
const result = transformArrayToCSVRow([100])
69+
70+
expect(result).to.equal('100\n')
71+
})
72+
})
73+
74+
describe('a string containing', () => {
75+
describe('a comma ,', () => {
76+
it('correctly formats the string', () => {
77+
const result = transformArrayToCSVRow(['I am a, comma seperated sentence.'])
78+
79+
expect(result).to.equal('"I am a, comma seperated sentence."\n')
80+
})
81+
})
82+
83+
describe('a single double quote "', () => {
84+
it('correctly formats the string', () => {
85+
const result = transformArrayToCSVRow(['I am a " double quote sentence.'])
86+
87+
expect(result).to.equal('"I am a "" double quote sentence."\n')
88+
})
89+
})
90+
91+
describe('a double double quote ""', () => {
92+
it('correctly formats the string', () => {
93+
const result = transformArrayToCSVRow(['I am a "" double quote sentence.'])
94+
95+
expect(result).to.equal('"I am a """" double quote sentence."\n')
96+
})
97+
})
98+
99+
describe('a back slash "\\" ', () => {
100+
it('correctly formats the string', () => {
101+
const result = transformArrayToCSVRow(['I am a "\\" back slash sentence.'])
102+
103+
expect(result).to.equal('"I am a ""\\"" back slash sentence."\n')
104+
})
105+
})
106+
})
107+
108+
describe('a date', () => {
109+
it('correctly formats the date to an iso string', () => {
110+
const result = transformArrayToCSVRow([new Date('2021-02-01')])
111+
112+
expect(result).to.equal('2021-02-01T00:00:00.000Z\n')
113+
})
114+
})
115+
})
116+
117+
describe('when an array of strings us provided', () => {
118+
it('converts the data to a CSV format', () => {
119+
const result = transformArrayToCSVRow(['name', 'age'])
120+
121+
expect(result).to.equal('"name","age"\n')
122+
})
123+
})
124+
125+
describe('when no array is provided', () => {
126+
it('returns undefined', () => {
127+
const result = transformArrayToCSVRow()
128+
129+
expect(result).to.equal(undefined)
130+
})
131+
})
132+
})
133+
})
134+
135+
function _testArray() {
136+
return [
137+
'20146cdc-9b40-4769-aa78-b51c17080d56',
138+
'4.1.1',
139+
9700,
140+
'Low loss tidal abstraction of water up to and "including" 25,002 megalitres, known as ML/yr a year where no model applies',
141+
new Date(2022, 11, 14, 18, 39, 45),
142+
true,
143+
null,
144+
undefined,
145+
false,
146+
'',
147+
25002,
148+
{ message: 'a json object' }
149+
]
150+
}

test/services/jobs/export/convert-to-csv.service.test.js

Lines changed: 0 additions & 106 deletions
This file was deleted.

0 commit comments

Comments
 (0)