From a70721531783276528b128e69f1d24fbae610fac Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 11 Nov 2026 07:05:30 -0600 Subject: [PATCH 1/2] fix(filters): relative dates in an analysis filter no longer answer HTTP 500 A filter criterion whose value is a filter expression (NowWithOffset, ThisOrLastPeriod, Now) on a date or date-time field failed in DrillFilters.restoreTypes with 'Unsupported class ...NowWithOffset for converting to Instant': the value was converted to the field's type before the query. Expressions are now passed through for core to evaluate, so a pivot 'created in the last day' works directly, after the JSON round trip of the UI API and of a saved definition, and its drill filters carry the expression. PivotContractTest.pivotWithRelativeDateFilter fails without the change on the memory, H2 and PostgreSQL engines. Co-Authored-By: Claude Fable 5.2 --- CHANGELOG.md | 4 ++++ .../dataanalysis/engine/DrillFilters.java | 10 +++++++++ .../qbits/dataanalysis/PivotContractTest.java | 22 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b6d2b1..a8920c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/). +## Unreleased - Relative dates in analysis filters + +- Fixed HTTP 500 ("Unsupported class ...NowWithOffset for converting to Instant") from every analysis whose filter holds a relative date (a filter expression such as "now minus one day" or "this month") on a date or date-time field: the pivot, profiles, comparisons and the stand-in backend converted filter values to the field's type before querying and did not expect an expression. Expressions are now left for core to evaluate when the query runs, so "printed in the last day" works from the Analyses screens, over the UI API (`POST /qbit/data-analysis/tables/{table}/pivot`) and in a saved or scheduled analysis, where it means the last day on every run. Drill-through filters carry the expression. + ## [Unreleased] ### Changed diff --git a/src/main/java/com/kingsrook/qbits/dataanalysis/engine/DrillFilters.java b/src/main/java/com/kingsrook/qbits/dataanalysis/engine/DrillFilters.java index 51a81a4..9789f35 100644 --- a/src/main/java/com/kingsrook/qbits/dataanalysis/engine/DrillFilters.java +++ b/src/main/java/com/kingsrook/qbits/dataanalysis/engine/DrillFilters.java @@ -302,9 +302,19 @@ public final class DrillFilters /******************************************************************************* ** String decimal operands preserve exact scale and precision across JSON round trips. + ** Filter expressions (relative dates) are left for core to evaluate. *******************************************************************************/ private static Serializable restoreValue(com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType type, Serializable value) { + if(value instanceof com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression) + { + /////////////////////////////////////////////////////////////////////////// + // a relative date ("now minus one day", "this month") is not a value to // + // convert: core evaluates it when the query runs, so a saved or // + // scheduled analysis means "the last day" on every run // + /////////////////////////////////////////////////////////////////////////// + return (value); + } if(type == com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType.DECIMAL) { return (value == null ? null : value.toString()); diff --git a/src/test/java/com/kingsrook/qbits/dataanalysis/PivotContractTest.java b/src/test/java/com/kingsrook/qbits/dataanalysis/PivotContractTest.java index fc85569..eeabb0a 100644 --- a/src/test/java/com/kingsrook/qbits/dataanalysis/PivotContractTest.java +++ b/src/test/java/com/kingsrook/qbits/dataanalysis/PivotContractTest.java @@ -118,6 +118,28 @@ abstract class PivotContractTest extends EngineFixture } } + /******************************************************************************* + ** A relative date in the filter ("created in the last N days") is evaluated + ** by core when the pivot runs, also after the JSON round trip of an HTTP + ** request or a saved definition, and the drills carry it. + *******************************************************************************/ + @Test + void pivotWithRelativeDateFilter() throws Exception + { + PivotRequest everything = pivot(List.of("status"), List.of()).withFilter(new com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter() + .withCriteria("created", QCriteriaOperator.GREATER_THAN_OR_EQUALS, com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.NowWithOffset.minus(36500, java.time.temporal.ChronoUnit.DAYS))); + for(PivotRequest request : List.of(everything, JsonUtils.toObject(JsonUtils.toJson(everything), PivotRequest.class))) + { + PivotResult result = service.pivot(request); + equalNumber(result.getGrandTotals().getFirst(), "12"); + countDrills(result); + } + + PivotRequest lastDay = pivot(List.of("status"), List.of()).withFilter(new com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter() + .withCriteria("created", QCriteriaOperator.GREATER_THAN_OR_EQUALS, com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.NowWithOffset.minus(1, java.time.temporal.ChronoUnit.DAYS))); + assertThat(service.pivot(JsonUtils.toObject(JsonUtils.toJson(lastDay), PivotRequest.class)).getCells()).isEmpty(); + } + /******************************************************************************* ** Verify or support the pivot and field-access contract. *******************************************************************************/ -- 2.54.0 From 472fcbf8dd986d7c26987538c8dce5ae98ecced0 Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 11 Nov 2026 07:05:46 -0600 Subject: [PATCH 2/2] docs(changelog): file the relative-date fix under Unreleased / Fixed Co-Authored-By: Claude Fable 5.2 --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8920c5..fd0798e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,6 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/). -## Unreleased - Relative dates in analysis filters - -- Fixed HTTP 500 ("Unsupported class ...NowWithOffset for converting to Instant") from every analysis whose filter holds a relative date (a filter expression such as "now minus one day" or "this month") on a date or date-time field: the pivot, profiles, comparisons and the stand-in backend converted filter values to the field's type before querying and did not expect an expression. Expressions are now left for core to evaluate when the query runs, so "printed in the last day" works from the Analyses screens, over the UI API (`POST /qbit/data-analysis/tables/{table}/pivot`) and in a saved or scheduled analysis, where it means the last day on every run. Drill-through filters carry the expression. - ## [Unreleased] ### Changed @@ -14,6 +10,7 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic ### Fixed +- Fixed HTTP 500 ("Unsupported class ...NowWithOffset for converting to Instant") from every analysis whose filter holds a relative date (a filter expression such as "now minus one day" or "this month") on a date or date-time field: the pivot, profiles, comparisons and the stand-in backend converted filter values to the field's type before querying and did not expect an expression. Expressions are now left for core to evaluate when the query runs, so "printed in the last day" works from the Analyses screens, over the UI API (`POST /qbit/data-analysis/tables/{table}/pivot`) and in a saved or scheduled analysis, where it means the last day on every run. Drill-through filters carry the expression. - Reject saved analyses with a NULL or false shared flag and no read owner in the generated schema. Existing hosts must apply the documented constraint correction through a new migration. - Scheduled analyses and watches work under OAuth2. When the host declares the `data-analysis` service principal (QQQ service identities, `DataAnalysisQBitConfig.withServicePrincipal`), schedule and watch inventory runs as that principal, and owner and recipient runs go on their behalf through it: their current access intersected with the principal's. The self-made `data-analysis-scheduler` session was rejected under OAuth2 (`Invalid session in request` on every `data_analysis_run_schedules` run). With no principal declared, behavior is unchanged. Needs the QQQ release with service identities (qqq#1255, #1256). - QBit metadata now reports the Maven artifact version, including release versions, from a filtered build resource. -- 2.54.0