diff --git a/src/functions/module_lookup.ts b/src/functions/module_lookup.ts index 0ada5f127e..969aa1451a 100644 --- a/src/functions/module_lookup.ts +++ b/src/functions/module_lookup.ts @@ -698,7 +698,7 @@ export const PIVOT_VALUE = { arg("pivot_id (number,string)", _t("ID of the pivot.")), arg("measure_name (string)", _t("Name of the measure.")), arg("domain_field_name (string,optional,repeating)", _t("Field name.")), - arg("domain_value (number,string,boolean,optional,repeating)", _t("Value.")), + arg("domain_value (number,string,boolean,any,optional,repeating)", _t("Value.")), ], compute: function ( formulaId: Maybe, @@ -746,7 +746,7 @@ export const PIVOT_HEADER = { args: [ arg("pivot_id (number,string)", _t("ID of the pivot.")), arg("domain_field_name (string,optional,repeating)", _t("Field name.")), - arg("domain_value (number,string,value,optional,repeating)", _t("Value.")), + arg("domain_value (number,string,any,optional,repeating)", _t("Value.")), ], compute: function ( pivotId: Maybe, diff --git a/src/helpers/pivot/pivot_helpers.ts b/src/helpers/pivot/pivot_helpers.ts index 6bdd784f26..ed282e89e0 100644 --- a/src/helpers/pivot/pivot_helpers.ts +++ b/src/helpers/pivot/pivot_helpers.ts @@ -12,7 +12,7 @@ import { Matrix, Pivot, } from "../../types"; -import { EvaluationError } from "../../types/errors"; +import { CellErrorType, EvaluationError } from "../../types/errors"; import { Granularity, PivotCoreDimension, @@ -195,16 +195,18 @@ export function toNormalizedPivotValue( dimension: Pick, groupValue ) { - if (groupValue === null || groupValue === "null") { + if ( + groupValue === null || + groupValue?.value === null || + groupValue === CellErrorType.NotAvailable || + groupValue?.value === CellErrorType.NotAvailable + ) { return null; } const groupValueString = typeof groupValue === "boolean" ? toString(groupValue).toLocaleLowerCase() : toString(groupValue); - if (groupValueString === "null") { - return null; - } if (!pivotNormalizationValueRegistry.contains(dimension.type)) { throw new EvaluationError( _t("Field %(field)s is not supported because of its type (%(type)s)", { @@ -233,7 +235,7 @@ export function toFunctionPivotValue( dimension: Pick ) { if (value === null) { - return `"null"`; + return "NA()"; } if (!pivotToFunctionValueRegistry.contains(dimension.type)) { return `"${value}"`; diff --git a/src/helpers/pivot/spreadsheet_pivot/data_entry_spreadsheet_pivot.ts b/src/helpers/pivot/spreadsheet_pivot/data_entry_spreadsheet_pivot.ts index 8997a01d70..8f57d7235e 100644 --- a/src/helpers/pivot/spreadsheet_pivot/data_entry_spreadsheet_pivot.ts +++ b/src/helpers/pivot/spreadsheet_pivot/data_entry_spreadsheet_pivot.ts @@ -1,4 +1,5 @@ import { CellValue, EvaluatedCell } from "../../../types"; +import { CellErrorType } from "../../../types/errors"; import { DimensionTree, PivotDimension, @@ -220,7 +221,7 @@ export function groupPivotDataEntriesBy(dataEntries: DataEntries, dimension: Piv function keySelector(dimension: PivotDimension): (item: DataEntry, index: number) => string { const name = dimension.nameWithGranularity; return (item) => { - return `${item[name]?.value ?? null}`; + return `${item[name]?.value ?? CellErrorType.NotAvailable}`; }; } @@ -245,10 +246,10 @@ export function orderDataEntriesKeys( * Used to order two values */ function compareDimensionValues(dimension: PivotDimension, a: string, b: string): number { - if (a === "null") { + if (a === CellErrorType.NotAvailable) { return dimension.order === "asc" ? 1 : -1; } - if (b === "null") { + if (b === CellErrorType.NotAvailable) { return dimension.order === "asc" ? -1 : 1; } if (dimension.type === "integer" || dimension.type === "datetime") { diff --git a/src/helpers/pivot/spreadsheet_pivot/spreadsheet_pivot.ts b/src/helpers/pivot/spreadsheet_pivot/spreadsheet_pivot.ts index 5194c84084..ac83508e66 100644 --- a/src/helpers/pivot/spreadsheet_pivot/spreadsheet_pivot.ts +++ b/src/helpers/pivot/spreadsheet_pivot/spreadsheet_pivot.ts @@ -269,6 +269,7 @@ export class SpreadsheetPivot implements Pivot { ["", "=PIVOT.HEADER(1)"], ["", '=PIVOT.HEADER(1,"measure","Price:sum")'], ['=PIVOT.HEADER(1,"Customer","Alice")', '=PIVOT.VALUE(1,"Price:sum","Customer","Alice")'], - ['=PIVOT.HEADER(1,"Customer","null")', '=PIVOT.VALUE(1,"Price:sum","Customer","null")'], + ['=PIVOT.HEADER(1,"Customer",NA())', '=PIVOT.VALUE(1,"Price:sum","Customer",NA())'], ]); }); diff --git a/tests/model/model_import_export.test.ts b/tests/model/model_import_export.test.ts index 8253b4e2ef..49bc2df615 100644 --- a/tests/model/model_import_export.test.ts +++ b/tests/model/model_import_export.test.ts @@ -586,6 +586,19 @@ describe("Migrations", () => { }); expect(getCellContent(model, "A1")).toBe("Hello"); }); + + test("migrate version 25: flatten cell object", () => { + const model = new Model({ + version: 24, + sheets: [ + { + id: "1", + cells: { A1: '=PIVOT.VALUE(1, "revenue", "customer", "null")' }, + }, + ], + }); + expect(getCell(model, "A1")?.content).toBe('=PIVOT.VALUE(1, "revenue", "customer", NA())'); + }); }); describe("Import", () => { diff --git a/tests/pivots/pivot_menu_items.test.ts b/tests/pivots/pivot_menu_items.test.ts index aad4bf84fb..c84702aabf 100644 --- a/tests/pivots/pivot_menu_items.test.ts +++ b/tests/pivots/pivot_menu_items.test.ts @@ -216,7 +216,7 @@ describe("Pivot fix formula menu item", () => { model.dispatch("SET_FORMULA_VISIBILITY", { show: true }); expect(getEvaluatedGrid(model, "C3:D4")).toEqual([ [`=PIVOT.HEADER(1,"Customer","Alice")`, `=PIVOT.VALUE(1,"Amount:sum","Customer","Alice")`], - [`=PIVOT.HEADER(1,"Customer","null")`, `=PIVOT.VALUE(1,"Amount:sum","Customer","null")`], + [`=PIVOT.HEADER(1,"Customer",NA())`, `=PIVOT.VALUE(1,"Amount:sum","Customer",NA())`], ]); }); diff --git a/tests/pivots/spreadsheet_pivot/spreadsheet_pivot.test.ts b/tests/pivots/spreadsheet_pivot/spreadsheet_pivot.test.ts index c382e94d00..8e2a555054 100644 --- a/tests/pivots/spreadsheet_pivot/spreadsheet_pivot.test.ts +++ b/tests/pivots/spreadsheet_pivot/spreadsheet_pivot.test.ts @@ -696,6 +696,29 @@ describe("Spreadsheet Pivot", () => { ]); }); + test("null string can be used as a value", () => { + // prettier-ignore + const grid = { + A1: "status", B1: "value", C1: "=PIVOT(1)", + A2: "null", B2: "10", + A3: "", B3: "20", + A4: "won", B4: "30", + }; + const model = createModelFromGrid(grid); + addPivot(model, "A1:B4", { + rows: [{ fieldName: "status" }], + measures: [{ id: "value:sum", fieldName: "value", aggregator: "sum" }], + }); + // prettier-ignore + expect(getEvaluatedGrid(model, "C1:D5")).toEqual([ + ["(#1) Pivot", "Total"], + ["", "value"], + ["null", "10"], + ["(Undefined)","20"], + ["won", "30"], + ]); + }); + test("PIVOT row headers are indented relative to the groupBy depth.", () => { // prettier-ignore const grid = {