Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 4 additions & 14 deletions packages/jsonforms-renderers/src/renderers/SpreadsheetControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { formatBytes, formatAccept } from '../utils/format'
import {
parseWorkbookToMatrix,
columnLetter,
evaluateExpressions,
processMatrix,
SheetParseError,
type CellValue,
type FormulaConfigEntry,
type FormulaResult,
type SpreadsheetValue,
} from '../utils/spreadsheet'

interface XSpreadsheetOptions {
Expand All @@ -37,13 +37,6 @@ type SpreadsheetControlProps = ControlProps & {
schema: JsonSchema & { 'x-spreadsheet'?: XSpreadsheetOptions; 'x-evaluate'?: FormulaConfigEntry[] }
}

// Persisted value shape — the field's data is an object, not a string key.
// No `fileName`: there's no storage service to name a retrievable file for.
interface SpreadsheetValue {
sheet?: CellValue[][]
derivations: FormulaResult[]
}

type Status = 'empty' | 'parsing' | 'ready' | 'error'

const DEFAULT_ACCEPT =
Expand Down Expand Up @@ -150,11 +143,8 @@ const SpreadsheetControl = ({
}

setLocalMatrix(parsedMatrix)
const newDerivations = await evaluateExpressions(parsedMatrix, xEvaluate)
handleChange(
path,
persistSheet ? { sheet: parsedMatrix, derivations: newDerivations } : { derivations: newDerivations },
)
const value = await processMatrix(parsedMatrix, xEvaluate, { persistSheet })
handleChange(path, value)
setStatus('ready')
},
[accept, maxSize, persistSheet, sheetName, xEvaluate, path, handleChange],
Expand Down
12 changes: 11 additions & 1 deletion packages/jsonforms-renderers/src/utils/spreadsheet/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export { parseWorkbookToMatrix, columnLetter, SheetParseError } from './parse'
export { evaluateExpression, evaluateExpressions } from './expression'
export type { CellValue, Matrix, ParsedSheet, FormulaConfigEntry, FormulaResult, FormulaErrorCode } from './types'
export { processMatrix } from './process'
export type { ProcessMatrixOptions } from './process'
export type {
CellValue,
Matrix,
ParsedSheet,
FormulaConfigEntry,
FormulaResult,
FormulaErrorCode,
SpreadsheetValue,
} from './types'
47 changes: 47 additions & 0 deletions packages/jsonforms-renderers/src/utils/spreadsheet/process.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { describe, expect, it } from 'vitest'
import { processMatrix } from './process'
import type { CellValue } from './types'

const matrix: CellValue[][] = [
['Item', 'Qty'],
['Widget', 10],
['Gadget', 20],
]

describe('processMatrix', () => {
it('includes the raw sheet by default (persistSheet omitted)', async () => {
const result = await processMatrix(matrix, [{ label: 'Total', expression: '=SUM(B2:B3)' }])
expect(result).toEqual({ sheet: matrix, derivations: [{ label: 'Total', value: 30 }] })
})

it('includes the raw sheet when persistSheet is explicitly true', async () => {
const result = await processMatrix(matrix, [{ label: 'Total', expression: '=SUM(B2:B3)' }], {
persistSheet: true,
})
expect(result).toEqual({ sheet: matrix, derivations: [{ label: 'Total', value: 30 }] })
})

it('omits the raw sheet when persistSheet is false', async () => {
const result = await processMatrix(matrix, [{ label: 'Total', expression: '=SUM(B2:B3)' }], {
persistSheet: false,
})
expect(result).toEqual({ derivations: [{ label: 'Total', value: 30 }] })
expect(result).not.toHaveProperty('sheet')
})

it('yields an empty derivations array for an empty formulas config', async () => {
const result = await processMatrix(matrix, [])
expect(result).toEqual({ sheet: matrix, derivations: [] })
})

it('surfaces an evaluateExpressions error entry unchanged, alongside a good one', async () => {
const result = await processMatrix(matrix, [
{ label: 'Good', expression: '=SUM(B2:B3)' },
{ label: 'Bad', expression: '=FOO(B2)' },
])
expect(result.derivations).toEqual([
{ label: 'Good', value: 30 },
{ label: 'Bad', value: null, error: '#NAME?' },
])
})
})
18 changes: 18 additions & 0 deletions packages/jsonforms-renderers/src/utils/spreadsheet/process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { evaluateExpressions } from './expression'
import type { CellValue, FormulaConfigEntry, SpreadsheetValue } from './types'

export interface ProcessMatrixOptions {
persistSheet?: boolean
}

// Matrix-in, persisted-value-out. Deliberately format-agnostic: doesn't care
// whether the matrix came from an xlsx/csv upload or (in the future) an XML
// one — this is the reusable seam for both.
export async function processMatrix(
matrix: CellValue[][],
formulas: FormulaConfigEntry[],
options: ProcessMatrixOptions = {},
): Promise<SpreadsheetValue> {
const derivations = await evaluateExpressions(matrix, formulas)
return options.persistSheet !== false ? { sheet: matrix, derivations } : { derivations }
}
10 changes: 10 additions & 0 deletions packages/jsonforms-renderers/src/utils/spreadsheet/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,13 @@ export interface FormulaResult {
// class; ERROR is our own catch-all for anything that doesn't map cleanly
// (a syntax error, or a wrapped cause we don't otherwise recognize).
export type FormulaErrorCode = 'REF' | 'VALUE' | 'DIV0' | 'NAME' | 'ERROR' | 'NA' | 'NUM' | 'NULL'

// Persisted value shape for SpreadsheetControl — the field's data is an
// object, not a string key. No `fileName`: there's no storage service to
// name a retrievable file for. Shared here (rather than kept private to
// SpreadsheetControl.tsx) so sibling renderers, like ComputedControl, can
// read a SpreadsheetControl's persisted `derivations` with the same type.
export interface SpreadsheetValue {
sheet?: CellValue[][]
derivations: FormulaResult[]
}
Loading