Coerce custom CSV column values to boolean during import
Custom CSV columns with values like "true"/"false" were stored as strings, which made `ContactService.getAvailableFields()` report them as string fields and lost the boolean toggle in the dashboard's segment-filter UI. Only the reserved `subscribed` column was coerced. Mirror the truthy keyword set already used for `subscribed` (`true`/`1`/`yes`) and add its symmetric falsy counterpart (`false`/`0`/`no`) for custom columns. Values outside that keyword set are returned unchanged so names, IDs, or arbitrary strings are not corrupted into `false`. Coercion runs over `Object.entries(customData)` right before the `upsert()` call; no other code path needs to change because `ContactService.upsert()` already accepts `Record<string, unknown>` and `mergeContactData()` accepts mixed JSON primitives. Post-import inference via `jsonb_typeof()` then classifies the stored JSON boolean as type `boolean` automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
32dd7bba46
commit
523bcad602
@@ -1,6 +1,7 @@
|
||||
import {beforeEach, describe, expect, it} from 'vitest';
|
||||
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
||||
import {ContactService} from '../../services/ContactService.js';
|
||||
import {coerceCustomValue} from '../import-processor.js';
|
||||
|
||||
/**
|
||||
* Tests for Contact Import Processor - Subscription Status Preservation
|
||||
@@ -279,3 +280,25 @@ describe('Contact Import - Subscription Status Preservation', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('coerceCustomValue', () => {
|
||||
describe('boolean coercion', () => {
|
||||
it.each(['true', 'TRUE', 'True', ' true ', '1', 'yes', 'YES', 'Yes'])('coerces %j to true', value => {
|
||||
expect(coerceCustomValue(value)).toBe(true);
|
||||
});
|
||||
|
||||
it.each(['false', 'FALSE', 'False', ' false ', '0', 'no', 'NO', 'No'])('coerces %j to false', value => {
|
||||
expect(coerceCustomValue(value)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('passthrough', () => {
|
||||
it.each(['Alice', 'true!', 'yesno', 'maybe', '42', '3.14', '01234'])('leaves %j as a string', value => {
|
||||
expect(coerceCustomValue(value)).toBe(value);
|
||||
});
|
||||
|
||||
it('leaves empty string as empty string', () => {
|
||||
expect(coerceCustomValue('')).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -123,7 +123,11 @@ export function createImportWorker() {
|
||||
|
||||
// Extract custom data (all fields except email and subscribed)
|
||||
const {email: _, subscribed: __, ...customData} = record;
|
||||
const data = Object.keys(customData).length > 0 ? customData : undefined;
|
||||
const customEntries = Object.entries(customData);
|
||||
const data =
|
||||
customEntries.length > 0
|
||||
? Object.fromEntries(customEntries.map(([k, v]) => [k, coerceCustomValue(v)]))
|
||||
: undefined;
|
||||
|
||||
// Check if contact exists before upserting
|
||||
const existingContact = await ContactService.findByEmail(projectId, email);
|
||||
@@ -216,3 +220,19 @@ function isValidEmail(email: string): boolean {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
const BOOLEAN_TRUE = new Set(['true', '1', 'yes']);
|
||||
const BOOLEAN_FALSE = new Set(['false', '0', 'no']);
|
||||
|
||||
/**
|
||||
* Coerce a raw CSV cell to its natural JSON primitive so post-import type
|
||||
* inference (ContactService.getAvailableFields) can detect booleans on custom
|
||||
* fields the same way it already does for the reserved `subscribed` column.
|
||||
* Values outside the recognised keyword set are returned unchanged.
|
||||
*/
|
||||
export function coerceCustomValue(value: string): string | boolean {
|
||||
const lower = value.trim().toLowerCase();
|
||||
if (BOOLEAN_TRUE.has(lower)) return true;
|
||||
if (BOOLEAN_FALSE.has(lower)) return false;
|
||||
return value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user