@@ -254,3 +254,95 @@ def test_get_url_params(test_client, login_as_admin, chart_id):
254254 "foo" : "bar" ,
255255 "slice_id" : str (chart_id ),
256256 }
257+
258+
259+ def test_granularity_sqla_override_updates_temporal_range_filter_subject (
260+ test_client , login_as_admin , chart_id , admin_id , dataset
261+ ):
262+ """
263+ Test that extra_form_data.granularity_sqla overrides TEMPORAL_RANGE filter subject.
264+
265+ The flow is:
266+ 1. Chart has TEMPORAL_RANGE adhoc filters on various columns
267+ 2. Dashboard applies Time Column native filter selecting 'year' via extra_form_data
268+ 3. Explore API processes form_data through merge_extra_filters/merge_extra_form_data
269+ 4. All TEMPORAL_RANGE filter subjects should be updated to 'year'
270+ """
271+
272+ form_data_with_temporal_filter = {
273+ "datasource" : f"{ dataset .id } __{ dataset .type } " ,
274+ "viz_type" : "country_map" ,
275+ "time_range" : "Last week" ,
276+ "adhoc_filters" : [
277+ {
278+ "clause" : "WHERE" ,
279+ "comparator" : "No filter" ,
280+ "expressionType" : "SIMPLE" ,
281+ "operator" : "TEMPORAL_RANGE" ,
282+ "subject" : "some_other_time_col" ,
283+ },
284+ {
285+ "clause" : "WHERE" ,
286+ "comparator" : "foo" ,
287+ "expressionType" : "SIMPLE" ,
288+ "operator" : "==" ,
289+ "subject" : "non_temporal_col" ,
290+ },
291+ {
292+ "clause" : "WHERE" ,
293+ "comparator" : "Last year" ,
294+ "expressionType" : "SIMPLE" ,
295+ "operator" : "TEMPORAL_RANGE" ,
296+ "subject" : "another_time_col" ,
297+ },
298+ ],
299+ "extra_form_data" : {
300+ "granularity_sqla" : "year" ,
301+ "time_range" : "Last month" ,
302+ },
303+ }
304+
305+ test_form_data_key = f"test_granularity_override_key_{ chart_id } _{ dataset .id } "
306+ entry : TemporaryExploreState = {
307+ "owner" : admin_id ,
308+ "datasource_id" : dataset .id ,
309+ "datasource_type" : dataset .type ,
310+ "chart_id" : chart_id ,
311+ "form_data" : json .dumps (form_data_with_temporal_filter ),
312+ }
313+ cache_manager .explore_form_data_cache .set (test_form_data_key , entry )
314+
315+ try :
316+ resp = test_client .get (
317+ f"api/v1/explore/?form_data_key={ test_form_data_key } "
318+ f"&datasource_id={ dataset .id } &datasource_type={ dataset .type } "
319+ )
320+ assert resp .status_code == 200
321+
322+ data = json .loads (resp .data .decode ("utf-8" ))
323+ result = data .get ("result" )
324+ form_data = result ["form_data" ]
325+
326+ adhoc_filters = form_data .get ("adhoc_filters" , [])
327+ temporal_range_filters = [
328+ f
329+ for f in adhoc_filters
330+ if f .get ("operator" ) == "TEMPORAL_RANGE"
331+ and f .get ("expressionType" ) == "SIMPLE"
332+ ]
333+
334+ assert len (temporal_range_filters ) == 2 , "Expected two TEMPORAL_RANGE filters"
335+ for temporal_filter in temporal_range_filters :
336+ assert temporal_filter ["subject" ] == "year" , (
337+ "Time Column native filter (granularity_sqla) should override "
338+ "TEMPORAL_RANGE filter subject for all matching filters"
339+ )
340+
341+ non_temporal_filters = [f for f in adhoc_filters if f .get ("operator" ) == "==" ]
342+ assert len (non_temporal_filters ) == 1
343+ assert non_temporal_filters [0 ]["subject" ] == "non_temporal_col"
344+
345+ assert form_data ["time_range" ] == "Last month"
346+ assert form_data .get ("granularity" ) == "year"
347+ finally :
348+ cache_manager .explore_form_data_cache .delete (test_form_data_key )
0 commit comments