Skip to content

bug: DateTime attribute range filtering (__after / __before) doesn't reach GraphQL — UI was removed in 1.9 but underlying gap predates it #9073

@FragmentedPacket

Description

@FragmentedPacket

Summary

__after / __before filtering for attribute fields of kind DateTime doesn't work end-to-end on either 1.8.x or 1.9.x. The frontend UI for it was removed in 1.9 (PR #8793), but the GraphQL schema never exposed those filter inputs for attribute fields in the first place. Only node_metadata__created_at / node_metadata__updated_at get range filtering today.

We'd like to bring before / after / between back as first-class filters on any DateTime attribute (not just metadata), with the same picker UX 1.9 introduced for metadata.

State in 1.8.x

  • AttributeFilterForm rendered an inline DateRangeFilterForm with two DatePicker inputs when attributeSchema.kind === "DateTime".
  • Selecting a date wrote <attr>__after=<ISO> / <attr>__before=<ISO> to the URL via useFilters.
  • But the round-trip was broken:
    1. addFiltersToRequest in frontend/app/src/shared/api/graphql/utils.ts had a switch on the filter suffix with cases for value/values/isnull/ids only — no before/after cases. Filters with those suffixes were silently dropped before the GraphQL request.
    2. Even if (1) had been fixed, the GraphQL schema never declared <attr>__after / <attr>__before as input arguments for DateTime attributes. InfrahubDataType.get_graphql_filters in backend/infrahub/types.py emits only __value/__values/__isnull/property filters, regardless of underlying scalar type.
    3. Even if (2) had been fixed, the Cypher attribute filter path (default_attribute_query_filter in backend/infrahub/core/query/attribute.py) rejects datetime values at its type guard and has no branch for before/after filter names — they fall through with no WHERE clause emitted.

Net effect on 1.8.x: pick a date, the URL updates, the chip shows, but the table never filters.

What changed in 1.9.x

Two distinct PRs touched this area:

  • PR Update filters UI and add metadata filters #8793 ("Update filters UI and add node metadata filtering") restructured the filter UI: introduced new metadata-date and metadata-user filter definitions, added a react-datepicker-based DateMetadataFilterForm, added a BETWEEN condition, and removed BEFORE / AFTER from DATETIME_FILTER_CONDITION_OPTIONS. After this change, attribute DateTime fields can only be filtered by is empty / is not empty. The before/after capability now exists only on the two built-in audit timestamps node_metadata__created_at and node_metadata__updated_at.
  • PR support timezones in graphql filters #8815 ("support timezones in graphql filters") fixed the metadata filter path to accept non-UTC offsets in input values by switching Timestamp(ZonedDateTime.from_py_datetime(value)) to Timestamp(value.isoformat()).

The underlying frontend addFiltersToRequest was also fixed in 1.9 (it now switches on parts.at(-1) instead of destructuring the first two segments, and adds explicit before / after cases). That alone makes URL-level __after / __before filters reach the GraphQL request — but they still hit a schema with no such input declared for non-metadata attributes.

What we want in 1.10+

For every node attribute of kind DateTime, the same filter UX that 1.9 added for metadata:

  • before, after, between conditions in the filter picker (alongside is empty / is not empty).
  • Same DateTimePicker (inline calendar + time-of-day, 1-min intervals).
  • URL keys <attr>__after=<ISO> and <attr>__before=<ISO>.
  • Backend GraphQL schema exposes <attr>__after: DateTime / <attr>__before: DateTime input arguments on every node kind that has a DateTime attribute.
  • Cypher path emits WHERE av.value > $param (for after) / < $param (for before), with the filter value normalized to canonical UTC via Timestamp(value.isoformat()).to_string() — mirroring how _build_metadata_filter_requirement already handles metadata filters.

Proposed implementation sketch

Three layers, none of them large:

  1. Backend GraphQL schema — override get_graphql_filters on the DateTime data type in backend/infrahub/types.py to also emit <name>__after and <name>__before: graphene.DateTime. Calls super() so existing __value / __values / __isnull are preserved.
  2. Backend Cypher path — in default_attribute_query_filter (backend/infrahub/core/query/attribute.py):
    • Relax the type guard at the top to accept datetime in addition to str | bool | int | list.
    • Add a before / after branch (alongside the existing value/values/isnull branches) that matches the value node, emits WHERE av.value > $param (or <), and normalizes datetime filter values to canonical UTC strings.
  3. Frontend UI — extend DATETIME_FILTER_CONDITION_OPTIONS with BEFORE / AFTER / BETWEEN, extract the existing metadata picker block (DateMetadataFilterForm) into a shared DateRangePickerFields component, and route AttributeFilterForm to it when attributeSchema.kind === "DateTime". The 1.9 addFiltersToRequest already handles the URL→variable mapping correctly; no change needed there. Existing getCurrentFilterCondition already maps __after / __before suffixes to AFTER / BEFORE; existing chip rendering in active-filter-tags.tsx already handles those suffixes for non-relationship definitions.

Caveat: stored value timezone canonicalization

DateTime attribute values are user-supplied strings stored verbatim — BaseAttribute.serialize_value() doesn't normalize to UTC at write time. Lexicographic Cypher comparison (< / >) is only correct when stored values are also in canonical form. In practice values written via the standard graphene DateTime ingestion path are canonical, but values written via direct DB writes or non-standard paths could produce incorrect range results. This matches an existing limitation in the metadata filtering path and is worth documenting; a separate hardening effort could either (a) normalize DateTime attribute values at write time, or (b) cast both sides via Cypher datetime() in the WHERE clause.

Reproducing the 1.8.x silent-drop bug

On 1.8.6:

  1. Find any node kind with a DateTime attribute (e.g., CoreValidator.completed_at).
  2. Open its list page in the UI.
  3. Filter picker → select the DateTime attribute → pick "after" + a date → Apply.
  4. Observe URL contains ?completed_at__after=2026-01-01T00:00:00Z.
  5. Open browser network tab — the GraphQL request body has no completed_at__after variable.
  6. Table is unchanged.

Out of scope

  • Range filtering on other types (Number, Bandwidth, etc.). Same machinery would apply via per-data-type overrides of get_graphql_filters.
  • A schema migration to UTC-normalize existing DateTime attribute values.

Happy to open a PR with the implementation if helpful — we have a working branch ready.

Metadata

Metadata

Assignees

No one assigned

    Labels

    group/backendIssue related to the backend (API Server, Git Agent)group/frontendIssue related to the frontend (React)state/need-triageThis issue needs to be triagedtype/bugSomething isn't working as expected

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions