mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-05 03:30:05 +00:00
feat: improve convertToCsv function
This commit is contained in:
@@ -80,7 +80,7 @@ const errorAndCsrfData: any = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const testTable = 'sometable'
|
const testTable = 'sometable'
|
||||||
const testTableWithNullVars = {
|
const testTableWithNullVars: { [key: string]: any } = {
|
||||||
[testTable]: [
|
[testTable]: [
|
||||||
{ var1: 'string', var2: 232, nullvar: 'A' },
|
{ var1: 'string', var2: 232, nullvar: 'A' },
|
||||||
{ var1: 'string', var2: 232, nullvar: 'B' },
|
{ var1: 'string', var2: 232, nullvar: 'B' },
|
||||||
@@ -270,12 +270,25 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
|||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
let assertionRes = true
|
let assertionRes = true
|
||||||
|
|
||||||
testTableWithNullVars[testTable].forEach((row: {}, i: number) =>
|
testTableWithNullVars[testTable].forEach(
|
||||||
Object.keys(row).forEach((col: string) => {
|
(row: { [key: string]: any }, i: number) =>
|
||||||
if (col !== res[testTable][i][col.toUpperCase()]) {
|
Object.keys(row).forEach((col: string) => {
|
||||||
assertionRes = false
|
const resValue = res[testTable][i][col.toUpperCase()]
|
||||||
}
|
|
||||||
})
|
if (
|
||||||
|
typeof row[col] === 'string' &&
|
||||||
|
testTableWithNullVars[`$${testTable}`].formats[col] ===
|
||||||
|
'best.' &&
|
||||||
|
row[col].toUpperCase() !== resValue
|
||||||
|
) {
|
||||||
|
assertionRes = false
|
||||||
|
} else if (
|
||||||
|
typeof row[col] !== 'string' &&
|
||||||
|
row[col] !== resValue
|
||||||
|
) {
|
||||||
|
assertionRes = false
|
||||||
|
}
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
return assertionRes
|
return assertionRes
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { formatDataForRequest } from '../../utils/formatDataForRequest'
|
|||||||
describe('formatDataForRequest', () => {
|
describe('formatDataForRequest', () => {
|
||||||
const testTable = 'sometable'
|
const testTable = 'sometable'
|
||||||
|
|
||||||
it('should', () => {
|
it('should format table with special missing values', () => {
|
||||||
const testTableWithNullVars = {
|
const tableWithMissingValues = {
|
||||||
[testTable]: [
|
[testTable]: [
|
||||||
{ var1: 'string', var2: 232, nullvar: 'A' },
|
{ var1: 'string', var2: 232, nullvar: 'A' },
|
||||||
{ var1: 'string', var2: 232, nullvar: 'B' },
|
{ var1: 'string', var2: 232, nullvar: 'B' },
|
||||||
@@ -21,7 +21,7 @@ describe('formatDataForRequest', () => {
|
|||||||
sasjs_tables: testTable
|
sasjs_tables: testTable
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(formatDataForRequest(testTableWithNullVars)).toEqual(expectedOutput)
|
expect(formatDataForRequest(tableWithMissingValues)).toEqual(expectedOutput)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return error if string is more than 32765 characters', () => {
|
it('should return error if string is more than 32765 characters', () => {
|
||||||
@@ -53,4 +53,43 @@ describe('formatDataForRequest', () => {
|
|||||||
|
|
||||||
expect(formatDataForRequest(data)).toEqual(expectedOutput)
|
expect(formatDataForRequest(data)).toEqual(expectedOutput)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should throw an error if special missing values is not valid', () => {
|
||||||
|
let tableWithMissingValues = {
|
||||||
|
[testTable]: [{ var: 'AA' }, { var: 0 }],
|
||||||
|
[`$${testTable}`]: { formats: { var: 'best.' } }
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => formatDataForRequest(tableWithMissingValues)).toThrow(
|
||||||
|
new Error(
|
||||||
|
'Special missing value can only be a single character from A to Z or _'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should auto-detect special missing values type as best.', () => {
|
||||||
|
const tableWithMissingValues = {
|
||||||
|
[testTable]: [{ var: 'a' }, { var: 'A' }, { var: '_' }, { var: 0 }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedOutput = {
|
||||||
|
sasjs1data: `var:best.\r\n.a\r\n.a\r\n._\r\n0`,
|
||||||
|
sasjs_tables: testTable
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(formatDataForRequest(tableWithMissingValues)).toEqual(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should auto-detect values type as $char1.', () => {
|
||||||
|
const tableWithMissingValues = {
|
||||||
|
[testTable]: [{ var: 'a' }, { var: 'A' }, { var: '_' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedOutput = {
|
||||||
|
sasjs1data: `var:$char1.\r\na\r\nA\r\n_`,
|
||||||
|
sasjs_tables: testTable
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(formatDataForRequest(tableWithMissingValues)).toEqual(expectedOutput)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,75 +6,98 @@ export const convertToCSV = (
|
|||||||
data: any,
|
data: any,
|
||||||
sasFormats?: { formats: { [key: string]: string } }
|
sasFormats?: { formats: { [key: string]: string } }
|
||||||
) => {
|
) => {
|
||||||
const formats = sasFormats?.formats
|
let formats = sasFormats?.formats
|
||||||
let headers: string[] = []
|
let headers: string[] = []
|
||||||
let csvTest
|
let csvTest
|
||||||
let invalidString = false
|
let invalidString = false
|
||||||
|
const specialMissingValueRegExp = /^[a-z_]{1}$/i
|
||||||
|
|
||||||
if (formats) {
|
if (formats) {
|
||||||
headers = Object.keys(formats).map((key) => `${key}:${formats[key]}`)
|
headers = Object.keys(formats).map((key) => `${key}:${formats![key]}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerFields = Object.keys(data[0])
|
const headerFields = Object.keys(data[0])
|
||||||
|
|
||||||
headerFields.forEach((field) => {
|
headerFields.forEach((field) => {
|
||||||
if (!formats || !Object.keys(formats).includes(field)) {
|
if (!formats || !Object.keys(formats).includes(field)) {
|
||||||
let firstFoundType: string | null = null
|
let hasNullOrNumber = false
|
||||||
let hasMixedTypes: boolean = false
|
let hasSpecialMissingString = false
|
||||||
let rowNumError: number = -1
|
|
||||||
|
|
||||||
const longestValueForField = data
|
data.forEach((row: { [key: string]: any }) => {
|
||||||
.map((row: any, index: number) => {
|
if (row[field] === null || typeof row[field] === 'number') {
|
||||||
if (row[field] || row[field] === '') {
|
hasNullOrNumber = true
|
||||||
if (firstFoundType) {
|
} else if (
|
||||||
let currentFieldType =
|
typeof row[field] === 'string' &&
|
||||||
row[field] === '' || typeof row[field] === 'string'
|
specialMissingValueRegExp.test(row[field])
|
||||||
? 'chars'
|
) {
|
||||||
: 'number'
|
hasSpecialMissingString = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (!hasMixedTypes) {
|
if (hasNullOrNumber && hasSpecialMissingString) {
|
||||||
hasMixedTypes = currentFieldType !== firstFoundType
|
headers.push(`${field}:best.`)
|
||||||
rowNumError = hasMixedTypes ? index + 1 : -1
|
|
||||||
}
|
if (!formats) formats = {}
|
||||||
} else {
|
|
||||||
if (row[field] === '') {
|
formats[field] = 'best.'
|
||||||
firstFoundType = 'chars'
|
} else {
|
||||||
|
let firstFoundType: string | null = null
|
||||||
|
let hasMixedTypes: boolean = false
|
||||||
|
let rowNumError: number = -1
|
||||||
|
|
||||||
|
const longestValueForField = data
|
||||||
|
.map((row: any, index: number) => {
|
||||||
|
if (row[field] || row[field] === '') {
|
||||||
|
if (firstFoundType) {
|
||||||
|
let currentFieldType =
|
||||||
|
row[field] === '' || typeof row[field] === 'string'
|
||||||
|
? 'chars'
|
||||||
|
: 'number'
|
||||||
|
|
||||||
|
if (!hasMixedTypes) {
|
||||||
|
hasMixedTypes = currentFieldType !== firstFoundType
|
||||||
|
rowNumError = hasMixedTypes ? index + 1 : -1
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
firstFoundType =
|
if (row[field] === '') {
|
||||||
typeof row[field] === 'string' ? 'chars' : 'number'
|
firstFoundType = 'chars'
|
||||||
|
} else {
|
||||||
|
firstFoundType =
|
||||||
|
typeof row[field] === 'string' ? 'chars' : 'number'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let byteSize
|
||||||
|
|
||||||
|
if (typeof row[field] === 'string') {
|
||||||
|
byteSize = getByteSize(row[field])
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteSize
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.sort((a: number, b: number) => b - a)[0]
|
||||||
|
|
||||||
let byteSize
|
if (longestValueForField && longestValueForField > 32765) {
|
||||||
|
invalidString = true
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof row[field] === 'string') {
|
if (hasMixedTypes) {
|
||||||
byteSize = getByteSize(row[field])
|
console.error(
|
||||||
}
|
`Row (${rowNumError}), Column (${field}) has mixed types: ERROR`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return byteSize
|
headers.push(
|
||||||
}
|
`${field}:${firstFoundType === 'chars' ? '$char' : ''}${
|
||||||
})
|
longestValueForField
|
||||||
.sort((a: number, b: number) => b - a)[0]
|
? longestValueForField
|
||||||
|
: firstFoundType === 'chars'
|
||||||
if (longestValueForField && longestValueForField > 32765) {
|
? '1'
|
||||||
invalidString = true
|
: 'best'
|
||||||
}
|
}.`
|
||||||
|
|
||||||
if (hasMixedTypes) {
|
|
||||||
console.error(
|
|
||||||
`Row (${rowNumError}), Column (${field}) has mixed types: ERROR`
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.push(
|
|
||||||
`${field}:${firstFoundType === 'chars' ? '$char' : ''}${
|
|
||||||
longestValueForField
|
|
||||||
? longestValueForField
|
|
||||||
: firstFoundType === 'chars'
|
|
||||||
? '1'
|
|
||||||
: 'best'
|
|
||||||
}.`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -99,6 +122,13 @@ export const convertToCSV = (
|
|||||||
value = currentCell === null ? '' : currentCell
|
value = currentCell === null ? '' : currentCell
|
||||||
|
|
||||||
if (formats && formats[fieldName] === 'best.') {
|
if (formats && formats[fieldName] === 'best.') {
|
||||||
|
if (value && !specialMissingValueRegExp.test(value)) {
|
||||||
|
console.log(`🤖[value]🤖`, value)
|
||||||
|
throw new Error(
|
||||||
|
'Special missing value can only be a single character from A to Z or _'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return `.${value.toLowerCase()}`
|
return `.${value.toLowerCase()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user