diff --git a/src/components/side_panel/pivot/pivot_side_panel/pivot_side_panel_store.ts b/src/components/side_panel/pivot/pivot_side_panel/pivot_side_panel_store.ts index 078926cbac..4ec56585a2 100644 --- a/src/components/side_panel/pivot/pivot_side_panel/pivot_side_panel_store.ts +++ b/src/components/side_panel/pivot/pivot_side_panel/pivot_side_panel_store.ts @@ -263,11 +263,14 @@ export class PivotSidePanelStore extends SpreadsheetStore { fields: PivotFields, definition: PivotCoreDefinition ): Record> { - const { columns, rows } = definition; - const dateFields = columns.concat(rows).filter((dimension) => { - const fieldType = fields[dimension.fieldName]?.type; - return fieldType === "date" || fieldType === "datetime"; - }); + const { columns, rows, measures } = definition; + const dateFields = columns + .concat(rows) + .concat(measures) + .filter((dimension) => { + const fieldType = fields[dimension.fieldName]?.type; + return fieldType === "date" || fieldType === "datetime"; + }); const granularitiesPerFields = {}; for (const field of dateFields) { granularitiesPerFields[field.fieldName] = new Set( diff --git a/src/helpers/pivot/pivot_helpers.ts b/src/helpers/pivot/pivot_helpers.ts index 6bdd784f26..05f2f32779 100644 --- a/src/helpers/pivot/pivot_helpers.ts +++ b/src/helpers/pivot/pivot_helpers.ts @@ -43,6 +43,7 @@ const AGGREGATORS_BY_FIELD_TYPE = { integer: NUMBER_CHAR_AGGREGATORS, char: NUMBER_CHAR_AGGREGATORS, boolean: ["count_distinct", "count", "bool_and", "bool_or"], + datetime: ["max", "min", "count_distinct", "count"], }; export const AGGREGATORS = {}; diff --git a/src/helpers/pivot/pivot_registry.ts b/src/helpers/pivot/pivot_registry.ts index 4237f3ce98..b89d3109c5 100644 --- a/src/helpers/pivot/pivot_registry.ts +++ b/src/helpers/pivot/pivot_registry.ts @@ -55,6 +55,6 @@ pivotRegistry.add("SPREADSHEET", { onIterationEndEvaluation: (pivot: SpreadsheetPivot) => pivot.markAsDirtyForEvaluation(), dateGranularities: [...dateGranularities], datetimeGranularities: [...dateGranularities, "hour_number", "minute_number", "second_number"], - isMeasureCandidate: (field: PivotField) => !["datetime", "boolean"].includes(field.type), + isMeasureCandidate: (field: PivotField) => field.type !== "boolean", isGroupable: () => true, }); diff --git a/tests/pivots/spreadsheet_pivot/spreadsheet_pivot.test.ts b/tests/pivots/spreadsheet_pivot/spreadsheet_pivot.test.ts index c382e94d00..e07929aa8f 100644 --- a/tests/pivots/spreadsheet_pivot/spreadsheet_pivot.test.ts +++ b/tests/pivots/spreadsheet_pivot/spreadsheet_pivot.test.ts @@ -1615,6 +1615,35 @@ describe("Spreadsheet Pivot", () => { ); }); + test("Pivot with date and datetime measures", () => { + const model = createModelFromGrid({ + A1: "Date", + B1: "Datetime", + A2: "2024/02/03", + B2: "2024/02/03 12:34:56", + A3: "2022/04/14", + B3: "2022/04/14 01:02:03", + }); + addPivot(model, "A1:B3", { + columns: [], + rows: [], + measures: [ + { id: "Date:max", fieldName: "Date", aggregator: "max" }, + { id: "Datetime:max", fieldName: "Datetime", aggregator: "max" }, + { id: "Date:min", fieldName: "Date", aggregator: "min" }, + { id: "Datetime:min", fieldName: "Datetime", aggregator: "min" }, + ], + }); + setCellContent(model, "A26", '=PIVOT.VALUE(1,"Date:max")'); + setCellContent(model, "B26", '=PIVOT.VALUE(1,"Datetime:max")'); + setCellContent(model, "A27", '=PIVOT.VALUE(1,"Date:min")'); + setCellContent(model, "B27", '=PIVOT.VALUE(1,"Datetime:min")'); + expect(getEvaluatedCell(model, "A26").formattedValue).toBe("2024/02/03"); + expect(getEvaluatedCell(model, "B26").formattedValue).toBe("2024/02/03 12:34:56"); + expect(getEvaluatedCell(model, "A27").formattedValue).toBe("2022/04/14"); + expect(getEvaluatedCell(model, "B27").formattedValue).toBe("2022/04/14 01:02:03"); + }); + test("Pivot with measure AVG on text values does not crash", () => { const model = createModelFromGrid({ A1: "Customer", A2: "Jean", A3: "Marc" }); addPivot(model, "A1:A3", { diff --git a/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts b/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts index be12c909b4..b0a52d4407 100644 --- a/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts +++ b/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts @@ -390,7 +390,7 @@ describe("Spreadsheet pivot side panel", () => { const measures = [...fixture.querySelectorAll(".o-autocomplete-value")].map( (el) => el.textContent ); - expect(measures).toEqual(["Count", "float", "integer", "text"]); + expect(measures).toEqual(["Count", "date", "datetime", "float", "integer", "text"]); }); test("defer update option is persistent", async () => { @@ -431,6 +431,21 @@ describe("Spreadsheet pivot side panel", () => { ]); }); + test("can add a datetime measure", async () => { + setCellContent(model, "A1", "name"); + setCellContent(model, "A2", "Alice"); + setCellContent(model, "B1", "birthdate"); + setCellContent(model, "B2", "1995/12/15"); + addPivot(model, "A1:B2", {}, "3"); + env.openSidePanel("PivotSidePanel", { pivotId: "3" }); + await nextTick(); + await click(fixture.querySelector(".o-pivot-measure .add-dimension")!); + await click(fixture.querySelectorAll(".o-autocomplete-value")[0]); + expect(model.getters.getPivotCoreDefinition("3").measures).toEqual([ + { id: "birthdate:count", fieldName: "birthdate", aggregator: "count" }, + ]); + }); + test("should preserve the sorting of the dimension after ordering is changed", async () => { mockGetBoundingClientRect({ /**