From 17fbd65f519de6bc02d1df5f50cb3bf62555774d Mon Sep 17 00:00:00 2001 From: Steven Gresh Date: Fri, 8 May 2026 12:26:43 -0700 Subject: [PATCH 1/2] format-fix --- .../src/index.test.ts | 47 +++++++++++++++++++ .../destination-google-sheets/src/writer.ts | 12 +++-- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/packages/destination-google-sheets/src/index.test.ts b/packages/destination-google-sheets/src/index.test.ts index 06d18b79..8ab7b2f0 100644 --- a/packages/destination-google-sheets/src/index.test.ts +++ b/packages/destination-google-sheets/src/index.test.ts @@ -10,6 +10,8 @@ import { } from './index.js' import { applyBatch, + displayToField, + fieldToDisplay, MAX_CELLS_PER_SPREADSHEET, readEnumValidations, readSheet, @@ -2051,3 +2053,48 @@ describe('enum constraints on any column', () => { ).toBeUndefined() }) }) + +describe('fieldToDisplay / displayToField', () => { + it('roundtrip — lowercase snake_case fields survive display conversion', () => { + const fields = ['id', 'created_at', 'customer_id', 'object', 'invoice_item_id'] + for (const f of fields) { + expect(displayToField(fieldToDisplay(f))).toBe(f) + } + }) + + it('fieldToDisplay — single word gets sentence case', () => { + expect(fieldToDisplay('id')).toBe('Id') + expect(fieldToDisplay('object')).toBe('Object') + }) + + it('fieldToDisplay — multi-word snake_case becomes sentence case with spaces', () => { + expect(fieldToDisplay('created_at')).toBe('Created at') + expect(fieldToDisplay('customer_id')).toBe('Customer id') + }) + + it('fieldToDisplay — uppercase in input is normalised to lowercase (no case leakage)', () => { + expect(fieldToDisplay('API_version')).toBe('Api version') + expect(fieldToDisplay('UPPER_case')).toBe('Upper case') + }) + + it('fieldToDisplay — system fields (leading underscore) are returned as-is', () => { + expect(fieldToDisplay('_idx')).toBe('_idx') + expect(fieldToDisplay('_updated_at')).toBe('_updated_at') + }) + + it('displayToField — reverses sentence case label back to snake_case', () => { + expect(displayToField('Created at')).toBe('created_at') + expect(displayToField('Customer id')).toBe('customer_id') + expect(displayToField('Id')).toBe('id') + }) + + it('displayToField — system fields (leading underscore) are returned as-is', () => { + expect(displayToField('_idx')).toBe('_idx') + expect(displayToField('_updated_at')).toBe('_updated_at') + }) + + it('displayToField — idempotent on already-snake_case strings', () => { + expect(displayToField('customer_id')).toBe('customer_id') + expect(displayToField('id')).toBe('id') + }) +}) diff --git a/packages/destination-google-sheets/src/writer.ts b/packages/destination-google-sheets/src/writer.ts index 0fe4a35f..67e0053d 100644 --- a/packages/destination-google-sheets/src/writer.ts +++ b/packages/destination-google-sheets/src/writer.ts @@ -53,7 +53,7 @@ function colIndexToLetter(idx: number): string { /** snake_case field name → "Sentence case" display label. System fields (_x) are returned as-is. */ export function fieldToDisplay(field: string): string { if (field.startsWith('_')) return field - const words = field.split('_') + const words = field.split('_').map((w) => w.toLowerCase()) words[0] = words[0].charAt(0).toUpperCase() + words[0].slice(1) return words.join(' ') } @@ -763,10 +763,12 @@ export function buildExampleSections( for (let i = 5; i >= 0; i--) { // EDATE shifts the first-of-month by N months; negative = past const monthLabel = `=TEXT(EDATE(DATE(YEAR(TODAY()),MONTH(TODAY()),1),${-i}),"YYYY-MM")` - // Unix timestamp boundaries for each month - const startUnix = `(EDATE(DATE(YEAR(TODAY()),MONTH(TODAY()),1),${-i})-DATE(1970,1,1))*86400` - const endUnix = `(EDATE(DATE(YEAR(TODAY()),MONTH(TODAY()),1),${-(i - 1)})-DATE(1970,1,1))*86400` - rows.push([monthLabel, `=COUNTIFS(${createdRange},">="&${startUnix},${createdRange},"<"&${endUnix})`]) + // COUNTIFS >=/< only works on numbers/dates, not text strings. + // DATEVALUE(LEFT(cell,10)) parses the "yyyy-mm-dd" prefix of the ISO string into a Sheets date serial. + const start = `EDATE(DATE(YEAR(TODAY()),MONTH(TODAY()),1),${-i})` + const end = `EDATE(DATE(YEAR(TODAY()),MONTH(TODAY()),1),${-(i - 1)})` + const parsed = `IFERROR(DATEVALUE(LEFT(${createdRange},10)),0)` + rows.push([monthLabel, `=SUMPRODUCT((${parsed}>=${start})*(${parsed}<${end}))`]) } sections.push({ title: 'New Customers by Month', From 98eb874e5148af314360496ad365f2d36e6e80e6 Mon Sep 17 00:00:00 2001 From: Steven Gresh Date: Mon, 11 May 2026 18:53:43 -0700 Subject: [PATCH 2/2] test --- packages/destination-google-sheets/__tests__/examples.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/destination-google-sheets/__tests__/examples.test.ts b/packages/destination-google-sheets/__tests__/examples.test.ts index 3c2353a1..d8c0b8a3 100644 --- a/packages/destination-google-sheets/__tests__/examples.test.ts +++ b/packages/destination-google-sheets/__tests__/examples.test.ts @@ -41,9 +41,9 @@ describe('buildExampleSections', () => { expect(s.rows).toHaveLength(6) // last 6 months // Formulas should reference customers.created (column C = index 2) expect(s.rows[0][1]).toContain("'customers'!C2:C") - // Formula should use EDATE for Unix timestamp conversion + // Formula should use EDATE for month boundaries and DATEVALUE for ISO string parsing expect(s.rows[0][1]).toContain('EDATE') - expect(s.rows[0][1]).toContain('DATE(1970,1,1)') + expect(s.rows[0][1]).toContain('DATEVALUE') }) it('includes payment volume section when payment_intents has status and amount', () => {