Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract table results to a file #193

Merged
merged 31 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
289328d
Extract table results to a file
Beckyrose200 Apr 19, 2023
303ef69
Rename module change
Beckyrose200 Apr 19, 2023
646bfbc
Convert to CSV service
Beckyrose200 Apr 19, 2023
b1071b1
Export table to file service
Beckyrose200 Apr 19, 2023
9c33442
Test convert-to-csv.service
Beckyrose200 Apr 20, 2023
733a88f
Merge branch 'main' into extract-table-to-file
Beckyrose200 Apr 20, 2023
782a0bb
Test for column name export
Beckyrose200 Apr 20, 2023
5c677b6
Merge branch 'main' into extract-table-to-file
Beckyrose200 Apr 20, 2023
41b346c
Remove .only from test
Beckyrose200 Apr 20, 2023
47ee54c
Test for file export
Beckyrose200 Apr 20, 2023
65a8d8e
Change value formats and refactor tests
Beckyrose200 Apr 21, 2023
f5e2ee7
Merge branch 'main' into extract-table-to-file
Beckyrose200 Apr 21, 2023
6c851f0
Test refactor
Beckyrose200 Apr 21, 2023
dc9fb24
Test refactor
Beckyrose200 Apr 24, 2023
805411c
Move export to temp file
Beckyrose200 Apr 24, 2023
a80b08e
Remove .only
Beckyrose200 Apr 24, 2023
117c5ca
Remove unnecessary await, remove table from file name, add module des…
Beckyrose200 Apr 25, 2023
31e0911
Removed fetch column names service
Beckyrose200 Apr 25, 2023
ac11448
Test refactor
Beckyrose200 Apr 26, 2023
73ca9ad
Stub the export data files service with globalNotifier
Beckyrose200 Apr 26, 2023
035ebf6
Merge branch 'main' into extract-table-to-file
Beckyrose200 Apr 26, 2023
edbf7ea
Refactor csvRows map to be more explicit
Beckyrose200 Apr 26, 2023
5f6b668
Changing the temp directory to be that of the operating system the pr…
Beckyrose200 Apr 26, 2023
e724bd1
Merge branch 'main' into extract-table-to-file
Beckyrose200 Apr 26, 2023
f9da6cf
Code refactored and comments rewritten
Beckyrose200 Apr 27, 2023
91f41cf
Merge branch 'main' into extract-table-to-file
Beckyrose200 Apr 27, 2023
10268ac
Code refactor
Beckyrose200 Apr 27, 2023
8f669d1
Comment changes
Beckyrose200 Apr 27, 2023
c1c0370
Remove spaces from the file export name
Beckyrose200 Apr 27, 2023
0824865
Fix test
Beckyrose200 Apr 27, 2023
e5e5ee7
Refactor code
Beckyrose200 Apr 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions app/services/db-export/convert-to-csv.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use strict'

/**
* Convert data to CSV format
* @module ConvertToCSVService
Beckyrose200 marked this conversation as resolved.
Show resolved Hide resolved
*/

/**
* Converts data to a CSV formatted string
*
* @param {Object} data An object containing the tables column names and the tables data
*
* @returns {String} A CSV formatted string
*/
function go (data) {
const transformedHeaders = _transformDataToCSV([data.headers])[0]

if (!data.rows) {
return transformedHeaders
}

const transformedRows = _transformDataToCSV(data.rows)
const dataToCSV = _joinHeaderAndRows(transformedHeaders, transformedRows)

return dataToCSV
}

/**
* Transforms each row to CSV format and joins the values with commas
*
* @param {Object} rows The data to be transformed to CSV
*
* @returns {String[]} An array of transformed data
*/
function _transformDataToCSV (rows) {
const transformedRows = []

rows.forEach((row) => {
const transformedRow = row.map((value) => {
return _transformValueToCSV(value)
}).join(',')
transformedRows.push(transformedRow)
})

return transformedRows
}

/**
* Transform a value to CSV format
*
* @param {*} value The value to transform
*
* @returns {*} The value transformed to CSV format
*/
function _transformValueToCSV (value) {
// Return empty string for undefined or null values
if (!value && value !== false) {
return ''
}

// Return ISO date if value is a date object
if (value instanceof Date) {
return value.toISOString()
}

// Return integers and booleans as they are (not converted to a string)
if (Number.isInteger(value) || typeof value === 'boolean') {
return `${value}`
}

// Return objects by serializing them to JSON
if (typeof value === 'object') {
return JSON.stringify(value)
}

// Return strings by quoting them and escaping any double quotes
const stringValue = value.toString().replace(/"/g, '""')

return `"${stringValue}"`
}

function _joinHeaderAndRows (header, rows) {
return [header, ...rows].join('\n')
}

module.exports = {
go
}
53 changes: 53 additions & 0 deletions app/services/db-export/export-data-files.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict'

/**
* Export converted data to a temporary file
* @module ExportDataFilesService
*/
Beckyrose200 marked this conversation as resolved.
Show resolved Hide resolved

const path = require('path')

const fs = require('fs').promises
const os = require('os')

/**
* Converts the provided data to CSV format using the ConvertToCsvService and writes it to a file
*
* @param {Object} data The data to be converted to CSV and written to the file
*
* @returns {Boolean} True if the file is written successfully and false if not
*/
async function go (data) {
try {
await fs.writeFile(_filenameWithPath('billing_charge_categories_table_export.csv'), data)
global.GlobalNotifier.omg('Billing Charge Categories Table exported successfully')

return true
} catch (error) {
global.GlobalNotifier.omfg('Billing Charge Categories Table Export request errored', error)

return false
}
}

/**
* Returns a file path by joining the temp directory path with the given file name
*
* @param {String} name The name the file will be saved under
*
* @returns {String} The file path to save the file under
*/
function _filenameWithPath (name) {
const temporaryFilePath = os.tmpdir()

return path.normalize(
path.format({
dir: temporaryFilePath,
name
})
)
}

module.exports = {
go
}
36 changes: 28 additions & 8 deletions app/services/db-export/fetch-billing-charge-categories.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,43 @@

/**
* Fetch all records from the water.billing_charge_categories table
* @module BillingChargeCategoriesTableExportService
* @module FetchBillingChargeCategoriesService
*/

const { db } = require('../../../db/db.js')

/**
* Generates an array of the table billing_charge_categories
* Retrieves headers and rows from the table in the db, and returns them as an object
*
* This is a dump of running 'SELECT * FROM water.billing_charge_categories' for the database.
* Its part of the full db schema export work.
*
* @returns An array of objects containing the data from the table.
* @returns {Object} The headers and rows from the table
*/
async function go () {
return db
const data = {
headers: await _headers(),
rows: await _rows()
}

return data
}

async function _rows () {
// Retrieves all rows from the water.billingChargeCategories table
const rows = await db
.withSchema('water')
.select('*')
.from('water.billing_charge_categories')
.from('billingChargeCategories')

// We are only interested in the values from the table
return rows.map((row) => {
return Object.values(row)
})
}

async function _headers () {
const columns = await db('billingChargeCategories').withSchema('water').columnInfo()

// We are only interested in the column names
return Object.keys(columns)
}

module.exports = {
Expand Down
124 changes: 124 additions & 0 deletions test/services/db-export/convert-to-csv.service.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
'use strict'

// Test framework dependencies
const Lab = require('@hapi/lab')
const Code = require('@hapi/code')

const { describe, it } = exports.lab = Lab.script()
const { expect } = Code

// Thing under test
const ConvertToCSVService = require('../../../app/services/db-export/convert-to-csv.service')

/**
* billingChargeCategoriesTable has all data types we are testing for
* objects{Date} - dateCreated
* String - description
* String (with added quotes)- shortDescription
* Integer - maxVolume
* Null - lossFactor
* Undefined - modelTier
*/
const billingChargeCategoryRow = [
'20146cdc-9b40-4769-aa78-b51c17080d56',
'4.1.1',
9700,
'Low loss tidal abstraction of water up to and "including" 25,002 megalitres a year where no model applies',
'Low loss, tidal, up to and including 25,002 ML/yr',
new Date(2022, 11, 14, 18, 39, 45),
new Date(2022, 11, 14, 18, 39, 45),
true,
null,
undefined,
false,
'',
25002
]

const billingChargeCategoriesColumnInfo = [
'billingChargeCategoryId',
'reference',
'subsistenceCharge',
'description',
'shortDescription',
'dateCreated',
'dateUpdated',
'isTidal',
'lossFactor',
'modelTier',
'isRestrictedSource',
'minVolume',
'maxVolume'
]

const csvHeaders = [
'"billingChargeCategoryId"',
'"reference"',
'"subsistenceCharge"',
'"description"',
'"shortDescription"',
'"dateCreated"',
'"dateUpdated"',
'"isTidal"',
'"lossFactor"',
'"modelTier"',
'"isRestrictedSource"',
'"minVolume"',
'"maxVolume"'
]

const csvValues = [
'"20146cdc-9b40-4769-aa78-b51c17080d56"',
'"4.1.1"',
'9700',
'"Low loss tidal abstraction of water up to and ""including"" 25,002 megalitres a year where no model applies"',
'"Low loss, tidal, up to and including 25,002 ML/yr"',
'2022-12-14T18:39:45.000Z',
'2022-12-14T18:39:45.000Z',
'true',
'',
'',
'false',
'',
'25002'
]

describe('Convert to CSV service', () => {
describe('when given data to convert', () => {
describe('that only has one row of data', () => {
it('has the table columns as headers', () => {
const result = ConvertToCSVService.go({ headers: billingChargeCategoriesColumnInfo, rows: [billingChargeCategoryRow] })
const resultLines = result.split(/\r?\n/)

expect(resultLines[0]).to.equal(csvHeaders.join(','))
})

it('converts the data to a CSV format', () => {
const result = ConvertToCSVService.go({ headers: billingChargeCategoriesColumnInfo, rows: [billingChargeCategoryRow] })
const resultLines = result.split(/\r?\n/)

expect(resultLines[1]).to.equal(csvValues.join(','))
})
})

describe('that has multiple rows of data', () => {
it('transforms all the rows to CSV', () => {
const result = ConvertToCSVService.go({ headers: billingChargeCategoriesColumnInfo, rows: [billingChargeCategoryRow, billingChargeCategoryRow] })
const resultLines = result.split(/\r?\n/)

expect(resultLines[1]).to.equal(csvValues.join(','))
expect(resultLines[2]).to.equal(csvValues.join(','))
})
})
})

describe('when not given data to convert', () => {
describe('that has no rows of data', () => {
it('exports the table to CSV without any rows', () => {
const result = ConvertToCSVService.go({ headers: billingChargeCategoriesColumnInfo })

expect(result).to.equal(csvHeaders.toString())
})
})
})
})
Loading