Skip to content

fix(query): align granularity with temporal x-axis and preserve secondary TEMPORAL_RANGE filters#39683

Open
ishimasar wants to merge 8 commits into
apache:masterfrom
ishimasar:fix/temporal-xaxis-granularity
Open

fix(query): align granularity with temporal x-axis and preserve secondary TEMPORAL_RANGE filters#39683
ishimasar wants to merge 8 commits into
apache:masterfrom
ishimasar:fix/temporal-xaxis-granularity

Conversation

@ishimasar
Copy link
Copy Markdown

@ishimasar ishimasar commented Apr 27, 2026

SUMMARY

Fixes incorrect time_range / granularity alignment when a dataset has multiple datetime columns and native filters attach TEMPORAL_RANGE to a non–x-axis column. Also improves adhoc column (sqlExpression / label) matching and avoids stripping legitimate secondary temporal filters.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

TESTING INSTRUCTIONS

  1. Dataset with two datetime columns (e.g. event_a, event_b).
  2. Chart with x-axis on event_a.
  3. Dashboard native time filter on event_b only.
  4. Verify query time bounds and grouping follow the chart x-axis; secondary temporal filter behavior remains sensible.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI:
  • Includes DB Migration (follow approval process):
  • Introduces new feature flags:
  • Changes the superset-ui contract:

Related context

Complements recent work around temporal column resolution (e.g. timeshift / native-filters / MCP chart generation). This PR specifically tightens:

  • BASE_AXIS–aware _process_time_range (no wrong fallback to the first temporal filter when the axis is known),
  • x-axis–anchored granularity in QueryContextFactory,
  • dedupe / time_range propagation that preserves unrelated TEMPORAL_RANGE clauses,
  • string vs adhoc dict column identity when matching filters to the x-axis.

Refs discussion with @dosu on the issue thread.

I manually checked the relevant chart before merging.

…dary dttm filters

X軸が日時のとき granularity を x_axis に固定し、別日時列の TEMPORAL_RANGE を消さない/time_range の上書き対象を主軸列に限定。time_range 推定はX軸に一致する TEMPORAL_RANGE を優先
Document why we prefer TEMPORAL_RANGE on BASE_AXIS and avoid falling back to temporal_flt[0] when the x-axis is known but has no matching filter (prevents another datetime column from driving from_dttm/to_dttm).

Add unit tests for: no fallback when only a secondary column has TEMPORAL_RANGE; legacy use of the first temporal when no BASE_AXIS; choosing the x-axis filter when multiple temporals exist; adhoc filter column dicts matched via sqlExpression.
@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented Apr 27, 2026

Code Review Agent Run #5e3aa5

Actionable Suggestions - 0
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • superset/common/query_object_factory.py - 1
    • Logic Inconsistency in Dict Handling · Line 45-45
Review Details
  • Files reviewed - 3 · Commit Range: 899307b..1e56790
    • superset/common/query_context_factory.py
    • superset/common/query_object_factory.py
    • tests/unit_tests/common/test_query_object_factory.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@dosubot dosubot Bot added change:backend Requires changing the backend explore:filter Related to filters in Explore explore:time Related to the time filters in Explore labels Apr 27, 2026
Comment thread tests/unit_tests/common/test_query_object_factory.py
Comment thread tests/unit_tests/common/test_query_object_factory.py
Comment thread tests/unit_tests/common/test_query_object_factory.py
@dosubot
Copy link
Copy Markdown

dosubot Bot commented Apr 27, 2026

Thanks for the PR, @ishimasar — the problem statement and approach look well-thought-out. This directly addresses the gaps we discussed on #39681 [1].

A few observations and things to watch for:

Adhoc column normalization — this is the most impactful part of the fix. The current _process_time_range does flt.get("col") == x_axis_label as a plain string comparison [2], which silently fails when col is an adhoc dict. Making sure the normalization is applied consistently across _process_time_range, _apply_granularity, and _apply_filters will be key — a mismatch in any one of those paths can re-introduce the bug.

Fallback behavior change — returning NO_TIME_RANGE instead of falling back to the first temporal filter when BASE_AXIS has no matching filter is the correct fix, but it's worth noting this could surface previously-hidden misconfigurations in existing dashboards. Charts that "worked" only because the fallback happened to grab a reasonable range will now show unbounded results. That's arguably the right thing to do (make the misconfiguration visible), but it may generate user reports.

Secondary filter preservation in _apply_filters — the current code at [3] overwrites all TEMPORAL_RANGE filter values with query_object.time_range. Narrowing that to only update the filter matching the granularity column is the right approach. Worth verifying that the preserved secondary TEMPORAL_RANGE clauses still make it through to SQL generation as WHERE constraints — there may be other points in the pipeline that assume a single temporal filter.

Backward compatibility — since this changes observable behavior (granularity locking, no fallback, preserved secondary filters), it might be worth adding a note in the PR description about what dashboards might see differently after the fix, even if the new behavior is strictly more correct.

Overall the direction looks solid and complements the prior work in #36309, #36985, and #38978 well. Looking forward to reviewing the code changes.

To reply, just mention @dosu.


Share context across your team and agents. Try Dosu.

Leave Feedback Learn about superset with Dosu Add Dosu to your team

@ishimasar
Copy link
Copy Markdown
Author

2e6dc09

@dosu
I've pushed the corrected commit.
Please check it.

@dosubot
Copy link
Copy Markdown

dosubot Bot commented Apr 27, 2026

Thanks @ishimasar, I've reviewed the corrected commit (2e6dc09). The overall approach is solid — the three core fixes (_process_time_range no-wrong-fallback, x-axis granularity locking, and scoped _apply_filters) all look correct. A few observations:

1. Duplicate helpers — consider consolidating. _temporal_filter_col_name in query_object_factory.py and _filter_clause_col_name in query_context_factory.py are functionally identical [1]. It would be cleaner to extract a single shared utility (e.g., in superset/utils/core.py) and import it from both modules.

2. Adhoc column check inconsistency. In _apply_granularity, the adhoc check was changed from is_adhoc_column(x_axis) to isinstance(x_axis, dict) and "sqlExpression" in x_axis. The existing is_adhoc_column requires both label and sqlExpression keys, so the new check is looser — it would accept a dict with only sqlExpression but no label. Consider using is_adhoc_column consistently to avoid matching malformed column dicts.

3. _apply_filters scoping looks correct. query_object.granularity is typed as str | None [2] and schema-validated [3], so comparing _filter_clause_col_name(filter_object.get("col")) == query_object.granularity is safe — both sides are strings at this point.

4. filter_to_remove usage is safe. By the time it's used in the comparison _filter_clause_col_name(flt.get("col")) != filter_to_remove, the value is always a plain string — either set directly from x_axis (already string-extracted from any adhoc dict) or from the is_adhoc_column.get("sqlExpression") guard. The new _filter_clause_col_name on the left side correctly normalizes filter columns that might be adhoc dicts, which fixes the original matching bug.

5. Test coverage. The four new test cases cover the key scenarios well (no fallback when x-axis has no matching temporal filter, legacy fallback when no x-axis, preference for x-axis match, and adhoc dict matching). The bot flagged a missing docstring on test_process_time_range_no_fallback_when_xaxis_has_no_matching_temporal — looks like the other test methods do have docstrings, so adding one there would keep things consistent.

Nice work overall — the most impactful suggestion would be consolidating the duplicate helpers and using is_adhoc_column consistently.

To reply, just mention @dosu.


Share context across your team and agents. Try Dosu.

Leave Feedback Learn about superset with Dosu Add Dosu to your team

@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented Apr 27, 2026

Code Review Agent Run #72ca74

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 1e56790..2e6dc09
    • tests/unit_tests/common/test_query_object_factory.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Add get_query_object_filter_column_name in superset/utils/core.py and use it
from QueryObjectFactory and QueryContextFactory instead of duplicate helpers.
Clarify unit test docstrings for _process_time_range scenarios.

Made-with: Cursor
@ishimasar
Copy link
Copy Markdown
Author

ishimasar commented Apr 28, 2026

@dosu Thanks for the detailed review.

Duplicate helpers: Consolidated into get_query_object_filter_column_name in superset/utils/core.py, imported from both query_object_factory and query_context_factory.
is_adhoc_column: _apply_granularity already normalizes x_axis with is_adhoc_column before the temporal-column checks; I did not find a looser isinstance(..., dict) path in the current branch.
Docstring on the no-fallback test: Extended to state the expected NO_TIME_RANGE outcome.

Pushed in the latest commit on the PR branch.

b2d590e

@dosubot
Copy link
Copy Markdown

dosubot Bot commented Apr 28, 2026

Thanks @ishimasar — the latest commit (b2d590e) addresses all three points cleanly:

  1. Shared utilityget_query_object_filter_column_name is now in superset/utils/core.py with a clear docstring, and both query_object_factory.py and query_context_factory.py import it from there [1]. No more duplication. 👍

  2. is_adhoc_column usage — I see the _apply_granularity diff still shows isinstance(x_axis, dict) and "sqlExpression" in x_axis on the removal side, but you're right that the first adhoc extraction (the one that feeds into the temporal-column check and granularity locking) still uses is_adhoc_column(x_axis) at the top of the method [1]. So by the time the code reaches the filter-removal block, x_axis is already a plain string — the isinstance check there is effectively a no-op guard rather than a looser replacement. That makes sense.

  3. Docstring — the no-fallback test now explicitly states the expected NO_TIME_RANGE outcome [1].

Everything looks good to me. The changes are well-scoped and the test coverage hits the key scenarios (no wrong fallback, legacy fallback, x-axis preference, adhoc dict matching).

To reply, just mention @dosu.


Share context across your team and agents. Try Dosu.

Leave Feedback Learn about superset with Dosu Add Dosu to your team

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 28, 2026

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit b2d590e
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/69effb97278e32000829b564
😎 Deploy Preview https://deploy-preview-39683--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented Apr 28, 2026

Code Review Agent Run #813f98

Actionable Suggestions - 0
Review Details
  • Files reviewed - 4 · Commit Range: 2e6dc09..6b0db8c
    • superset/common/query_context_factory.py
    • superset/common/query_object_factory.py
    • superset/utils/core.py
    • tests/unit_tests/common/test_query_object_factory.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@ishimasar
Copy link
Copy Markdown
Author

ishimasar commented Apr 28, 2026

@dosu
While the review has addressed the concerns, how will the decision to incorporate the PR into the main project be made?

@dosubot
Copy link
Copy Markdown

dosubot Bot commented Apr 28, 2026

The merge decision follows the standard Apache Superset contribution process:

  1. CI must pass first — reviewers won't review code until all CI tests pass [1]. The PR should include tests and not decrease code coverage [2].
  2. At least one approval from a project committer is required [3]. PRs are typically left open for at least 72 hours to give others a chance to review [4].
  3. A committer mergescommitters are individuals elected by the PMC with write access to the repository. They are the ones who accept and merge contributions.

So the next steps for this PR would be ensuring CI is green, and then waiting for a committer to review and approve. If the PR stays inactive for more than 30 days without review, it may be closed automatically [5], so it can help to tag a relevant committer or mention it in the dev mailing list or Slack to get attention.

To reply, just mention @dosu.


Docs are dead. Just use Dosu.

Leave Feedback Ask Dosu about superset Share Dosu with your team

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 29, 2026

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 961da2f
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a04b3cffca18f0008df1958
😎 Deploy Preview https://deploy-preview-39683--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@rusackas
Copy link
Copy Markdown
Member

Looking good to me! Kicking off CI, and planning to spin up a test env.

Upon review it looks like the NO_TIME_RANGE fallback change is a subtle breaking behavior change. When BASE_AXIS is set but no matching filter exists, the old code would silently fall back to the first temporal filter's range. The new code returns NO_TIME_RANGE, resulting in an unbounded query. It's arguably more correct, but it will visibly affect existing dashboards where the old behavior happened to work. There should at minimum be a note in the description, and ideally this should be called out in an UPDATING.md entry.

Adding @sadpandajoe, since this is something we'll definitely want to test thoroughly.

@rusackas rusackas requested review from rusackas and sadpandajoe May 12, 2026 17:10
@codecov
Copy link
Copy Markdown

codecov Bot commented May 12, 2026

Codecov Report

❌ Patch coverage is 33.33333% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.34%. Comparing base (5b5dd01) to head (cb17b98).
⚠️ Report is 73 commits behind head on master.

Files with missing lines Patch % Lines
superset/common/query_context_factory.py 31.81% 8 Missing and 7 partials ⚠️
superset/utils/core.py 33.33% 2 Missing and 2 partials ⚠️
superset/common/query_object_factory.py 50.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #39683      +/-   ##
==========================================
- Coverage   63.88%   63.34%   -0.55%     
==========================================
  Files        2583     2583              
  Lines      136604   136603       -1     
  Branches    31502    31500       -2     
==========================================
- Hits        87276    86536     -740     
- Misses      47812    48549     +737     
- Partials     1516     1518       +2     
Flag Coverage Δ
hive 39.39% <6.66%> (+<0.01%) ⬆️
mysql 59.06% <33.33%> (+<0.01%) ⬆️
postgres 59.14% <33.33%> (+<0.01%) ⬆️
presto 41.08% <16.66%> (+<0.01%) ⬆️
python 59.38% <33.33%> (-1.19%) ⬇️
sqlite 58.78% <33.33%> (+<0.01%) ⬆️
unit ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:backend Requires changing the backend explore:filter Related to filters in Explore explore:time Related to the time filters in Explore size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants