Temporal filters and granularity can diverge from the chart x-axis when multiple datetime columns exist
- Dominant language
- Python
- Stars
- 74.8k
- Forks
- 18.3k
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 685
Description
### Bug description
## Summary
Charts backed by datasets with multiple datetime columns can end up with `time_range` / `from_dttm`–`to_dttm` and `granularity` driven by the wrong column when dashboard native filters apply `TEMPORAL_RANGE` to a non–x-axis datetime (or when several temporal filters are present).
That yields queries where the visible x-axis does not match the time bounds or grouping actually used.
## Root cause (high level)
1. `QueryObjectFactory._process_time_range` could fall back to the first `TEMPORAL_RANGE` clause even when the chart’s BASE_AXIS was a different datetime column, so another column’s bounds could become the main `time_range`.
2. `QueryContextFactory` could let `granularity` be inferred from dashboard context in a way that rewrote grouping / series away from the x-axis temporal column, and could strip or overwrite temporal filters in ways that dropped legitimate secondary datetime filters.
## Intended behavior
- Prefer `TEMPORAL_RANGE` on the chart x-axis (BASE_AXIS) when resolving `time_range`.
- If the x-axis is known but no `TEMPORAL_RANGE` targets that axis, do not adopt another column’s `TEMPORAL_RANGE` as the main chart time range.
- When the x-axis is a temporal column, pin `granularity` to that x-axis column so grouping stays aligned with the axis.
- When deduplicating temporal filters for the main axis, preserve `TEMPORAL_RANGE` on other datetime columns (secondary time dimensions).
- When propagating `time_range` into existing filters, only update the `TEMPORAL_RANGE` row that matches the active granularity, and compare filter `col` values whether they are plain strings or adhoc column dicts (`sqlExpression` / `label`).
## Implementation reference
- `superset/common/query_object_factory.py`(vscode-file://vscode-app/Applications/Cursor.app/Contents/Resources/app/out/vs/code/electron-sandbox/workbench/superset/common/query_object_factory.py): x-axis–aware `_process_time_range`, adhoc-safe column matching.
- `superset/common/query_context_factory.py`(vscode-file://vscode-app/Applications/Cursor.app/Contents/Resources/app/out/vs/code/electron-sandbox/workbench/superset/common/query_context_factory.py): lock granularity to x-axis temporal column, narrow filter removal / `time_range` application, adhoc-safe column matching.
- `tests/unit_tests/common/test_query_object_factory.py`(vscode-file://vscode-app/Applications/Cursor.app/Contents/Resources/app/out/vs/code/electron-sandbox/workbench/tests/unit_tests/common/test_query_object_factory.py): unit tests for `_process_time_range` (no wrong fallback, legacy fallback when no axis, multi-filter preference, adhoc `col` dict).
## Commits
- `fix: align granularity with x-axis temporal column and preserve secondary dttm filters`
- `test: cover QueryObjectFactory._process_time_range x-axis selection`
## How to reproduce (click-by-click template)
> Fill in the bracketed placeholders for your environment. Remove steps that do not apply (e.g. if you reproduce via API only).
1. Dataset: Create or use a virtual / physical dataset with at least two datetime columns, e.g. `[ColumnA_dttm]` and `[ColumnB_dttm]` (replace with real names).
2. Chart
- Create a time-series (or mixed) chart that uses `[ColumnA_dttm]` as the x-axis / time column (BASE_AXIS).
-Save the chart as `[Chart name]`.
3. Dashboard
-Create a dashboard `[Dashboard name]` and add `[Chart name]`.
4. Native filter (critical)
- Add a dashboard native filter of type Time column / Time range (wording depends on Superset version) scoped to `[ColumnB_dttm]` (the non–x-axis datetime).
- Set an explicit range, e.g. `[Start]` to `[End]` that is narrower or different from any range you might set on `ColumnA`.
5. Observe
- With no separate native filter on `[ColumnA_dttm]`, open the chart on the dashboard.
- Note: axis label / grain, data span, and SQL or query payload (if available) for `time_range`, `granularity,` and `filters`.
6. Expected
- Main query time bounds and grouping should follow `[ColumnA_dttm]` (the chart x-axis), not `[ColumnB_dttm]`’s dashboard filter.
Filters on `[ColumnB_dttm]` should still apply as secondary temporal constraints where applicable, without replacing the x-axis time range.
7. Actual (before fix)
- Describe what you see, e.g.:
- Query uses `[ColumnB_dttm]` range for `from_dttm` / `to_dttm`, or
- `granularity` / GROUP BY does not match `[ColumnA_dttm]` on the x-axis, or
- Secondary temporal filter incorrectly removed.
8. Environment (fill in)
- Superset version: `[e.g. 4.x.x]`
- Chart plugin / viz type: `[e.g. ECharts Timeseries]`
- Database engine: `[e.g. Postgres / BigQuery / …]`
- Feature flags (if any): `[list]`
- Browser: `[e.g. Chrome 123]`
## Verification checklist (for reviewers)
- [ ] Multiple datetime columns on one dataset.
- [ ] Native TEMPORAL_RANGE only on non–x-axis column.
- [ ] X-axis remains correct; secondary datetime filter behavior is sensible.
- [ ] Adhoc x-axis / filter column definitions still match correctly.
### Screenshots/recordings
_No response_
### Superset version
master / latest-dev
### Python version
3.11
### Node version
18 or greater
### Browser
Chrome
### Additional context
_No response_
### Checklist
- [ ] I have searched Superset docs and Slack and didn't find a solution to my problem.
- [ ] I have searched the GitHub issue tracker and didn't find a similar bug report.
- [ ] I have checked Superset's logs for errors and if I found a relevant Python stacktrace, I included it here as text in the "additional context" section.
Contributor guide
Research direction
Start with tests/unit_tests/common/test_query_object_factory.py and the _process_time_range cases, then read superset/common/query_object_factory.py and superset/common/query_context_factory.py. Trace how BASE_AXIS, granularity, and TEMPORAL_RANGE filters are selected, including adhoc column dictionaries. Done means x-axis time bounds and grouping remain aligned while secondary datetime filters are preserved, with the listed unit tests covering the behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend-api-design, data-visualization
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100