This is an automated email from the ASF dual-hosted git repository.
msyavuz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 2dfc770b0f7 fix(native-filters): update TEMPORAL_RANGE filter subject
when Time Column filter is applied (#36985)
2dfc770b0f7 is described below
commit 2dfc770b0f71d73b65081a8c3473a6ddb62d0101
Author: Jamile Celento <[email protected]>
AuthorDate: Wed Feb 4 06:37:17 2026 -0300
fix(native-filters): update TEMPORAL_RANGE filter subject when Time Column
filter is applied (#36985)
---
superset/utils/core.py | 66 +++-
tests/integration_tests/explore/api_tests.py | 92 +++++
tests/unit_tests/utils/test_core.py | 508 +++++++++++++++++++++++++++
3 files changed, 662 insertions(+), 4 deletions(-)
diff --git a/superset/utils/core.py b/superset/utils/core.py
index a5c69554559..23a3017bf2c 100644
--- a/superset/utils/core.py
+++ b/superset/utils/core.py
@@ -1011,6 +1011,39 @@ def form_data_to_adhoc(form_data: dict[str, Any],
clause: str) -> AdhocFilterCla
return result
+def _update_existing_temporal_filter(
+ temporal_filter: AdhocFilterClause,
+ granularity_sqla_override: str | None,
+ time_range: str | None,
+ chart_has_granularity_sqla: bool,
+) -> None:
+ """Update an existing temporal filter with new subject/comparator."""
+ if (
+ granularity_sqla_override is not None
+ and temporal_filter.get("expressionType") == "SIMPLE"
+ ):
+ temporal_filter["subject"] = granularity_sqla_override
+ if time_range and not chart_has_granularity_sqla:
+ temporal_filter["comparator"] = time_range
+
+
+def _create_temporal_filter(
+ granularity_sqla: str,
+ time_range: str,
+) -> AdhocFilterClause:
+ """Create a new TEMPORAL_RANGE adhoc filter."""
+ new_filter: AdhocFilterClause = {
+ "clause": "WHERE",
+ "expressionType": "SIMPLE",
+ "operator": FilterOperator.TEMPORAL_RANGE,
+ "subject": granularity_sqla,
+ "comparator": time_range,
+ "isExtra": True,
+ }
+ new_filter["filterOptionName"] = hash_from_dict(cast(dict[Any, Any],
new_filter))
+ return new_filter
+
+
def merge_extra_form_data(form_data: dict[str, Any]) -> None: # noqa: C901
"""
Merge extra form data (appends and overrides) into the main payload
@@ -1059,10 +1092,35 @@ def merge_extra_form_data(form_data: dict[str, Any]) ->
None: # noqa: C901
for fltr in append_filters
if fltr
)
- if form_data.get("time_range") and not form_data.get("granularity_sqla"):
- for adhoc_filter in form_data.get("adhoc_filters", []):
- if adhoc_filter.get("operator") == "TEMPORAL_RANGE":
- adhoc_filter["comparator"] = form_data["time_range"]
+
+ granularity_sqla_override = extra_form_data.get("granularity_sqla")
+ time_range = form_data.get("time_range")
+ chart_has_granularity_sqla = bool(form_data.get("granularity_sqla"))
+
+ temporal_filters = [
+ adhoc_filter
+ for adhoc_filter in adhoc_filters
+ if adhoc_filter.get("operator") == FilterOperator.TEMPORAL_RANGE
+ ]
+
+ for temporal_filter in temporal_filters:
+ _update_existing_temporal_filter(
+ temporal_filter,
+ granularity_sqla_override,
+ time_range,
+ chart_has_granularity_sqla,
+ )
+
+ if (
+ not temporal_filters
+ and granularity_sqla_override is not None
+ and time_range is not None
+ ):
+ new_temporal_filter = _create_temporal_filter(
+ granularity_sqla_override,
+ cast(str, time_range),
+ )
+ adhoc_filters.append(new_temporal_filter)
def merge_extra_filters(form_data: dict[str, Any]) -> None: # noqa: C901
diff --git a/tests/integration_tests/explore/api_tests.py
b/tests/integration_tests/explore/api_tests.py
index cc65870ca61..4c1d357462f 100644
--- a/tests/integration_tests/explore/api_tests.py
+++ b/tests/integration_tests/explore/api_tests.py
@@ -254,3 +254,95 @@ def test_get_url_params(test_client, login_as_admin,
chart_id):
"foo": "bar",
"slice_id": str(chart_id),
}
+
+
+def test_granularity_sqla_override_updates_temporal_range_filter_subject(
+ test_client, login_as_admin, chart_id, admin_id, dataset
+):
+ """
+ Test that extra_form_data.granularity_sqla overrides TEMPORAL_RANGE filter
subject.
+
+ The flow is:
+ 1. Chart has TEMPORAL_RANGE adhoc filters on various columns
+ 2. Dashboard applies Time Column native filter selecting 'year' via
extra_form_data
+ 3. Explore API processes form_data through
merge_extra_filters/merge_extra_form_data
+ 4. All TEMPORAL_RANGE filter subjects should be updated to 'year'
+ """
+
+ form_data_with_temporal_filter = {
+ "datasource": f"{dataset.id}__{dataset.type}",
+ "viz_type": "country_map",
+ "time_range": "Last week",
+ "adhoc_filters": [
+ {
+ "clause": "WHERE",
+ "comparator": "No filter",
+ "expressionType": "SIMPLE",
+ "operator": "TEMPORAL_RANGE",
+ "subject": "some_other_time_col",
+ },
+ {
+ "clause": "WHERE",
+ "comparator": "foo",
+ "expressionType": "SIMPLE",
+ "operator": "==",
+ "subject": "non_temporal_col",
+ },
+ {
+ "clause": "WHERE",
+ "comparator": "Last year",
+ "expressionType": "SIMPLE",
+ "operator": "TEMPORAL_RANGE",
+ "subject": "another_time_col",
+ },
+ ],
+ "extra_form_data": {
+ "granularity_sqla": "year",
+ "time_range": "Last month",
+ },
+ }
+
+ test_form_data_key =
f"test_granularity_override_key_{chart_id}_{dataset.id}"
+ entry: TemporaryExploreState = {
+ "owner": admin_id,
+ "datasource_id": dataset.id,
+ "datasource_type": dataset.type,
+ "chart_id": chart_id,
+ "form_data": json.dumps(form_data_with_temporal_filter),
+ }
+ cache_manager.explore_form_data_cache.set(test_form_data_key, entry)
+
+ try:
+ resp = test_client.get(
+ f"api/v1/explore/?form_data_key={test_form_data_key}"
+ f"&datasource_id={dataset.id}&datasource_type={dataset.type}"
+ )
+ assert resp.status_code == 200
+
+ data = json.loads(resp.data.decode("utf-8"))
+ result = data.get("result")
+ form_data = result["form_data"]
+
+ adhoc_filters = form_data.get("adhoc_filters", [])
+ temporal_range_filters = [
+ f
+ for f in adhoc_filters
+ if f.get("operator") == "TEMPORAL_RANGE"
+ and f.get("expressionType") == "SIMPLE"
+ ]
+
+ assert len(temporal_range_filters) == 2, "Expected two TEMPORAL_RANGE
filters"
+ for temporal_filter in temporal_range_filters:
+ assert temporal_filter["subject"] == "year", (
+ "Time Column native filter (granularity_sqla) should override "
+ "TEMPORAL_RANGE filter subject for all matching filters"
+ )
+
+ non_temporal_filters = [f for f in adhoc_filters if f.get("operator")
== "=="]
+ assert len(non_temporal_filters) == 1
+ assert non_temporal_filters[0]["subject"] == "non_temporal_col"
+
+ assert form_data["time_range"] == "Last month"
+ assert form_data.get("granularity") == "year"
+ finally:
+ cache_manager.explore_form_data_cache.delete(test_form_data_key)
diff --git a/tests/unit_tests/utils/test_core.py
b/tests/unit_tests/utils/test_core.py
index fe1b3311ef2..463ee32180a 100644
--- a/tests/unit_tests/utils/test_core.py
+++ b/tests/unit_tests/utils/test_core.py
@@ -31,6 +31,7 @@ from superset.utils.core import (
cast_to_boolean,
check_is_safe_zip,
DateColumn,
+ FilterOperator,
generic_find_constraint_name,
generic_find_fk_constraint_name,
get_datasource_full_name,
@@ -39,6 +40,7 @@ from superset.utils.core import (
get_user_agent,
is_test,
merge_extra_filters,
+ merge_extra_form_data,
merge_request_params,
normalize_dttm_col,
parse_boolean_string,
@@ -1089,6 +1091,512 @@ def
test_merge_extra_filters_when_applied_time_extras_predefined():
}
+def test_merge_extra_form_data_updates_temporal_range_subject():
+ """
+ Test that when extra_form_data contains granularity_sqla, it should update
+ the subject of any TEMPORAL_RANGE adhoc filter to use the new time column.
+ """
+ form_data = {
+ "adhoc_filters": [
+ {
+ "clause": "WHERE",
+ "comparator": "No filter",
+ "expressionType": "SIMPLE",
+ "operator": "TEMPORAL_RANGE",
+ "subject": "created_at",
+ }
+ ],
+ "extra_form_data": {
+ "granularity_sqla": "event_date",
+ "time_range": "Last week",
+ },
+ }
+ merge_extra_form_data(form_data)
+
+ assert form_data["adhoc_filters"][0]["subject"] == "event_date"
+ assert form_data["time_range"] == "Last week"
+ assert form_data["granularity"] == "event_date"
+ assert "extra_form_data" not in form_data
+
+
+def test_time_column_filter_with_multiple_temporal_range_filters():
+ """
+ Test that Time Column native filter updates ALL TEMPORAL_RANGE filters
+ in the adhoc_filters list, but leaves non-TEMPORAL_RANGE filters unchanged.
+ """
+ form_data = {
+ "adhoc_filters": [
+ {
+ "clause": "WHERE",
+ "comparator": "Last week",
+ "expressionType": "SIMPLE",
+ "operator": "TEMPORAL_RANGE",
+ "subject": "default_time_col",
+ },
+ {
+ "clause": "WHERE",
+ "comparator": "foo",
+ "expressionType": "SIMPLE",
+ "operator": "==",
+ "subject": "some_column",
+ },
+ {
+ "clause": "WHERE",
+ "comparator": "Last month",
+ "expressionType": "SIMPLE",
+ "operator": "TEMPORAL_RANGE",
+ "subject": "another_time_col",
+ },
+ ],
+ "extra_form_data": {
+ "granularity_sqla": "selected_time_col",
+ },
+ }
+ merge_extra_form_data(form_data)
+
+ assert form_data["adhoc_filters"][0]["subject"] == "selected_time_col"
+ assert form_data["adhoc_filters"][2]["subject"] == "selected_time_col"
+ assert form_data["adhoc_filters"][1]["subject"] == "some_column"
+ assert "extra_form_data" not in form_data
+
+
+def test_merge_extra_form_data_skips_sql_expression_filters():
+ """
+ Test that SQL expression type filters are not modified when
granularity_sqla
+ is provided. Only SIMPLE expression type filters should have their subject
updated.
+ """
+ form_data = {
+ "adhoc_filters": [
+ {
+ "clause": "WHERE",
+ "expressionType": "SQL",
+ "operator": "TEMPORAL_RANGE",
+ "sqlExpression": "created_at > '2020-01-01'",
+ },
+ {
+ "clause": "WHERE",
+ "comparator": "Last week",
+ "expressionType": "SIMPLE",
+ "operator": "TEMPORAL_RANGE",
+ "subject": "created_at",
+ },
+ ],
+ "extra_form_data": {
+ "granularity_sqla": "event_date",
+ },
+ }
+ merge_extra_form_data(form_data)
+
+ assert "subject" not in form_data["adhoc_filters"][0]
+ assert form_data["adhoc_filters"][1]["subject"] == "event_date"
+ assert "extra_form_data" not in form_data
+
+
+def test_merge_extra_form_data_time_range_without_granularity_sqla():
+ """
+ Test that when form_data has time_range but no granularity_sqla,
+ the TEMPORAL_RANGE filter comparator is updated to match time_range.
+ """
+ form_data = {
+ "time_range": "Last month",
+ "adhoc_filters": [
+ {
+ "clause": "WHERE",
+ "comparator": "No filter",
+ "expressionType": "SIMPLE",
+ "operator": "TEMPORAL_RANGE",
+ "subject": "created_at",
+ }
+ ],
+ "extra_form_data": {},
+ }
+ merge_extra_form_data(form_data)
+
+ assert form_data["adhoc_filters"][0]["comparator"] == "Last month"
+ assert form_data["adhoc_filters"][0]["subject"] == "created_at"
+ assert "extra_form_data" not in form_data
+
+
+def
test_merge_extra_form_data_no_subject_update_when_granularity_override_none():
+ """
+ Test that when granularity_sqla_override is None, the TEMPORAL_RANGE filter
+ subject should NOT be updated, even if expressionType is SIMPLE.
+ """
+ original_subject = "original_time_col"
+ form_data = {
+ "adhoc_filters": [
+ {
+ "clause": "WHERE",
+ "comparator": "Last week",
+ "expressionType": "SIMPLE",
+ "operator": "TEMPORAL_RANGE",
+ "subject": original_subject,
+ }
+ ],
+ "extra_form_data": {},
+ }
+ merge_extra_form_data(form_data)
+
+ assert form_data["adhoc_filters"][0]["subject"] == original_subject
+ assert "extra_form_data" not in form_data
+
+
+def
test_merge_extra_form_data_does_not_update_comparator_when_time_range_is_falsy():
+ """
+ Test that when time_range is falsy (None, empty string, or False),
+ the TEMPORAL_RANGE filter comparator should NOT be updated.
+ """
+ original_comparator = "Original time range"
+ form_data = {
+ "time_range": None,
+ "adhoc_filters": [
+ {
+ "clause": "WHERE",
+ "comparator": original_comparator,
+ "expressionType": "SIMPLE",
+ "operator": "TEMPORAL_RANGE",
+ "subject": "created_at",
+ }
+ ],
+ "extra_form_data": {},
+ }
+ merge_extra_form_data(form_data)
+
+ assert form_data["adhoc_filters"][0]["comparator"] == original_comparator
+ assert "extra_form_data" not in form_data
+
+
+def test_merge_extra_form_data_no_comparator_update_when_time_range_empty():
+ """
+ Test that when time_range is an empty string (falsy), the TEMPORAL_RANGE
+ filter comparator should NOT be updated.
+ """
+ original_comparator = "Original time range"
+ form_data = {
+ "time_range": "",
+ "adhoc_filters": [
+ {
+ "clause": "WHERE",
+ "comparator": original_comparator,
+ "expressionType": "SIMPLE",
+ "operator": "TEMPORAL_RANGE",
+ "subject": "created_at",
+ }
+ ],
+ "extra_form_data": {},
+ }
+ merge_extra_form_data(form_data)
+
+ assert form_data["adhoc_filters"][0]["comparator"] == original_comparator
+ assert "extra_form_data" not in form_data
+
+
+def
test_merge_extra_form_data_does_not_update_comparator_when_has_granularity_sqla():
+ """
+ Test that when form_data has granularity_sqla (truthy), the TEMPORAL_RANGE
+ filter comparator should NOT be updated, even if time_range exists.
+
+ When granularity_sqla is present in form_data, it indicates a legacy chart
+ where granularity_sqla and time_range are separate form_data attributes.
+ """
+ original_comparator = "Original time range"
+ form_data = {
+ "time_range": "Last month",
+ "granularity_sqla": "event_date",
+ "adhoc_filters": [
+ {
+ "clause": "WHERE",
+ "comparator": original_comparator,
+ "expressionType": "SIMPLE",
+ "operator": "TEMPORAL_RANGE",
+ "subject": "created_at",
+ }
+ ],
+ "extra_form_data": {},
+ }
+ merge_extra_form_data(form_data)
+
+ assert form_data["adhoc_filters"][0]["comparator"] == original_comparator
+ assert "extra_form_data" not in form_data
+
+
+def test_merge_extra_form_data_removes_extra_form_data():
+ """
+ Test that merge_extra_form_data removes extra_form_data from form_data
+ after processing (it calls form_data.pop("extra_form_data", {})).
+ """
+ form_data = {
+ "adhoc_filters": [],
+ "extra_form_data": {
+ "granularity_sqla": "event_date",
+ "time_range": "Last week",
+ },
+ }
+ merge_extra_form_data(form_data)
+
+ assert "extra_form_data" not in form_data
+ assert form_data["granularity"] == "event_date"
+ assert form_data["time_range"] == "Last week"
+
+
+def test_merge_extra_form_data_creates_temporal_filter_when_none_exists():
+ """
+ Test that when granularity_sqla_override and time_range are provided
+ but no TEMPORAL_RANGE filter exists, a new temporal filter is created.
+ """
+ form_data = {
+ "time_range": "2022-01-22 : 2025-01-22",
+ "adhoc_filters": [
+ {
+ "clause": "WHERE",
+ "comparator": "foo",
+ "expressionType": "SIMPLE",
+ "operator": "==",
+ "subject": "some_column",
+ }
+ ],
+ "extra_form_data": {
+ "granularity_sqla": "updated_at",
+ },
+ }
+ merge_extra_form_data(form_data)
+
+ temporal_filters = [
+ f
+ for f in form_data["adhoc_filters"]
+ if f.get("operator") == FilterOperator.TEMPORAL_RANGE
+ ]
+ assert len(temporal_filters) == 1
+ assert temporal_filters[0]["subject"] == "updated_at"
+ assert temporal_filters[0]["comparator"] == "2022-01-22 : 2025-01-22"
+ assert temporal_filters[0]["expressionType"] == "SIMPLE"
+ assert temporal_filters[0]["clause"] == "WHERE"
+ assert temporal_filters[0]["isExtra"] is True
+ assert "filterOptionName" in temporal_filters[0]
+ assert "extra_form_data" not in form_data
+
+
+def
test_merge_extra_form_data_creates_temporal_filter_with_empty_adhoc_filters():
+ """
+ Test that a temporal filter is created when adhoc_filters is empty
+ and granularity_sqla_override and time_range are provided.
+ """
+ form_data = {
+ "time_range": "Last month",
+ "adhoc_filters": [],
+ "extra_form_data": {
+ "granularity_sqla": "created_at",
+ },
+ }
+ merge_extra_form_data(form_data)
+
+ assert len(form_data["adhoc_filters"]) == 1
+ assert form_data["adhoc_filters"][0]["operator"] ==
FilterOperator.TEMPORAL_RANGE
+ assert form_data["adhoc_filters"][0]["subject"] == "created_at"
+ assert form_data["adhoc_filters"][0]["comparator"] == "Last month"
+ assert "extra_form_data" not in form_data
+
+
+def
test_merge_extra_form_data_creates_temporal_filter_with_missing_adhoc_filters():
+ """
+ Test that a temporal filter is created when adhoc_filters key doesn't exist
+ and granularity_sqla_override and time_range are provided.
+ """
+ form_data = {
+ "time_range": "2024-01-01 : 2024-03-01",
+ "extra_form_data": {
+ "granularity_sqla": "event_date",
+ },
+ }
+ merge_extra_form_data(form_data)
+
+ assert "adhoc_filters" in form_data
+ assert len(form_data["adhoc_filters"]) == 1
+ assert form_data["adhoc_filters"][0]["operator"] ==
FilterOperator.TEMPORAL_RANGE
+ assert form_data["adhoc_filters"][0]["subject"] == "event_date"
+ assert form_data["adhoc_filters"][0]["comparator"] == "2024-01-01 :
2024-03-01"
+ assert "extra_form_data" not in form_data
+
+
+def
test_merge_extra_form_data_does_not_create_filter_when_granularity_missing():
+ """
+ Test that no temporal filter is created when granularity_sqla_override
+ is missing, even if time_range is provided.
+ """
+ form_data = {
+ "time_range": "Last week",
+ "adhoc_filters": [],
+ "extra_form_data": {},
+ }
+ original_filters_count = len(form_data["adhoc_filters"])
+ merge_extra_form_data(form_data)
+
+ assert len(form_data["adhoc_filters"]) == original_filters_count
+ assert "extra_form_data" not in form_data
+
+
+def
test_merge_extra_form_data_does_not_create_filter_when_time_range_missing():
+ """
+ Test that no temporal filter is created when time_range is missing,
+ even if granularity_sqla_override is provided.
+ """
+ form_data = {
+ "adhoc_filters": [],
+ "extra_form_data": {
+ "granularity_sqla": "updated_at",
+ },
+ }
+ original_filters_count = len(form_data["adhoc_filters"])
+ merge_extra_form_data(form_data)
+
+ assert len(form_data["adhoc_filters"]) == original_filters_count
+ assert "extra_form_data" not in form_data
+
+
+def test_merge_extra_form_data_does_not_create_filter_when_time_range_none():
+ """
+ Test that no temporal filter is created when time_range is None,
+ even if granularity_sqla_override is provided.
+ """
+ form_data = {
+ "time_range": None,
+ "adhoc_filters": [],
+ "extra_form_data": {
+ "granularity_sqla": "created_at",
+ },
+ }
+ original_filters_count = len(form_data["adhoc_filters"])
+ merge_extra_form_data(form_data)
+
+ assert len(form_data["adhoc_filters"]) == original_filters_count
+ assert "extra_form_data" not in form_data
+
+
+def test_merge_extra_form_data_does_not_create_filter_when_granularity_none():
+ """
+ Test that no temporal filter is created when granularity_sqla_override
+ is None, even if time_range is provided.
+ """
+ form_data = {
+ "time_range": "Last month",
+ "adhoc_filters": [],
+ "extra_form_data": {
+ "granularity_sqla": None,
+ },
+ }
+ original_filters_count = len(form_data["adhoc_filters"])
+ merge_extra_form_data(form_data)
+
+ assert len(form_data["adhoc_filters"]) == original_filters_count
+ assert "extra_form_data" not in form_data
+
+
+def
test_merge_extra_form_data_creates_filter_with_different_temporal_columns():
+ """
+ Test creating temporal filters for different temporal columns
+ (event_date, created_at, updated_at) to ensure the fix works
+ across multiple column types.
+ """
+ temporal_columns = ["event_date", "created_at", "updated_at"]
+ time_range = "2022-01-22 : 2025-01-22"
+
+ for column in temporal_columns:
+ form_data = {
+ "time_range": time_range,
+ "adhoc_filters": [],
+ "extra_form_data": {
+ "granularity_sqla": column,
+ },
+ }
+ merge_extra_form_data(form_data)
+
+ assert len(form_data["adhoc_filters"]) == 1
+ assert form_data["adhoc_filters"][0]["subject"] == column
+ assert form_data["adhoc_filters"][0]["comparator"] == time_range
+ assert (
+ form_data["adhoc_filters"][0]["operator"] ==
FilterOperator.TEMPORAL_RANGE
+ )
+
+
+def test_merge_extra_form_data_does_not_create_duplicate_when_filter_exists():
+ """
+ Test that when a TEMPORAL_RANGE filter already exists, a new one
+ is NOT created. Instead, the existing filter is updated.
+ """
+ form_data = {
+ "time_range": "Last week",
+ "adhoc_filters": [
+ {
+ "clause": "WHERE",
+ "comparator": "No filter",
+ "expressionType": "SIMPLE",
+ "operator": FilterOperator.TEMPORAL_RANGE,
+ "subject": "original_column",
+ }
+ ],
+ "extra_form_data": {
+ "granularity_sqla": "new_column",
+ },
+ }
+ merge_extra_form_data(form_data)
+
+ temporal_filters = [
+ f
+ for f in form_data["adhoc_filters"]
+ if f.get("operator") == FilterOperator.TEMPORAL_RANGE
+ ]
+ assert len(temporal_filters) == 1
+ assert temporal_filters[0]["subject"] == "new_column"
+ assert temporal_filters[0]["comparator"] == "Last week"
+ assert "extra_form_data" not in form_data
+
+
+def test_merge_extra_form_data_date_format_with_timestamp_column():
+ """
+ Test that date format time_range (YYYY-MM-DD) works correctly
+ when applied to a timestamp column.
+ """
+ form_data = {
+ "time_range": "2022-01-22 : 2025-01-22",
+ "adhoc_filters": [],
+ "extra_form_data": {
+ "granularity_sqla": "created_at",
+ },
+ }
+ merge_extra_form_data(form_data)
+
+ assert len(form_data["adhoc_filters"]) == 1
+ temporal_filter = form_data["adhoc_filters"][0]
+ assert temporal_filter["operator"] == FilterOperator.TEMPORAL_RANGE
+ assert temporal_filter["subject"] == "created_at"
+ assert temporal_filter["comparator"] == "2022-01-22 : 2025-01-22"
+ assert temporal_filter["expressionType"] == "SIMPLE"
+ assert "extra_form_data" not in form_data
+
+
+def test_merge_extra_form_data_timestamp_format_with_date_column():
+ """
+ Test that timestamp format time_range (YYYY-MM-DDTHH:MM:SS) works correctly
+ when applied to a date column.
+ """
+ form_data = {
+ "time_range": "2022-01-22T00:00:00 : 2025-01-22T23:59:59",
+ "adhoc_filters": [],
+ "extra_form_data": {
+ "granularity_sqla": "event_date",
+ },
+ }
+ merge_extra_form_data(form_data)
+
+ assert len(form_data["adhoc_filters"]) == 1
+ temporal_filter = form_data["adhoc_filters"][0]
+ assert temporal_filter["operator"] == FilterOperator.TEMPORAL_RANGE
+ assert temporal_filter["subject"] == "event_date"
+ assert temporal_filter["comparator"] == "2022-01-22T00:00:00 :
2025-01-22T23:59:59"
+ assert temporal_filter["expressionType"] == "SIMPLE"
+ assert "extra_form_data" not in form_data
+
+
def test_merge_request_params_when_url_params_undefined():
form_data = {"since": "2000", "until": "now"}
url_params = {"form_data": form_data, "dashboard_ids": "(1,2,3,4,5)"}