sadpandajoe commented on code in PR #44406:
URL: https://github.com/apache/superset/pull/44406#discussion_r4246802793


##########
superset/common/query_context_processor.py:
##########
@@ -937,6 +1119,51 @@ def get_native_annotation_data(query_obj: QueryObject) -> 
dict[str, Any]:
             annotation_data[layer_name] = result
         return annotation_data
 
+    @staticmethod
+    def _apply_annotation_overrides(
+        query_context: QueryContext, annotation_layer: dict[str, Any]
+    ) -> None:
+        """
+        Apply a chart-backed annotation layer's per-request overrides (time
+        grain, time range) to its saved query context's queries, in place.
+
+        Shared by :meth:`get_viz_annotation_data` (which executes the
+        overridden query) and :meth:`_annotation_source_scope` (which derives
+        its cache key from it), so the key always reflects the query that
+        actually runs: an override can introduce per-user Jinja/RLS material
+        the saved, un-overridden query lacks (e.g. a template that only calls
+        ``current_user_id()`` at a finer time grain), which deriving the key
+        from the un-overridden query would silently miss.
+        """
+        # Only the source chart's rows are used. Dropping its own annotation
+        # layers also stops charts that annotate each other (A -> B -> A)
+        # from recursing.
+        for query_object in query_context.queries:
+            query_object.annotation_layers = []
+
+        if not (overrides := annotation_layer.get("overrides")):
+            return
+
+        if time_grain_sqla := overrides.get("time_grain_sqla"):
+            for query_object in query_context.queries:
+                query_object.extras["time_grain_sqla"] = time_grain_sqla
+
+        if time_range := overrides.get("time_range"):
+            from_dttm, to_dttm = get_since_until_from_time_range(time_range)
+
+            for query_object in query_context.queries:
+                query_object.from_dttm = from_dttm
+                query_object.to_dttm = to_dttm
+                # Keep the logical range alongside the resolved bounds:
+                # ``QueryObject.cache_key`` keys on ``time_range`` when set
+                # (dropping the bounds), so a relative override such as
+                # "Last week" keeps one key as time passes instead of
+                # contributing freshly resolved timestamps on every request
+                # (which would defeat cache and force-nonce reuse), and an
+                # override on a saved query without a range of its own is
+                # still told apart from a different override.
+                query_object.time_range = time_range

Review Comment:
   Assigning `query_object.time_range` here makes `QueryObject.cache_key()` 
drop the resolved bounds, but the bounds above are computed without the saved 
query's `time_shift`. For a saved annotation chart with `time_shift="1 year 
ago"`, a `2025-01-01 : 2025-02-01` override executes January 2025 data, while a 
normal run of that chart with the same range and shift executes January 2024; 
both now produce the same query cache key. Whichever runs first populates the 
entry the other reads, so the annotation can show the wrong year's rows (or the 
chart can show the annotation's). Should the override keep the explicit bounds 
in the key whenever the query carries a `time_shift`, or apply the shift to the 
override range the way the query factory does?



##########
tests/unit_tests/semantic_layers/simple_cache_clear_test.py:
##########
@@ -307,32 +317,24 @@ def 
test_annotation_result_key_uses_saved_source_generation(
     from superset.common.query_context_processor import QueryContextProcessor
 
     view: SemanticView = SemanticView(name="view", semantic_layer=layer)
-    chart: Mock = 
Mock(query_context='{"datasource":{"id":17,"type":"semantic_view"}}')
+    chart: Mock = _saved_context_chart(view)
     query: Mock = Mock(annotation_layers=[{"sourceType": "line", "value": 7}])
     processor: QueryContextProcessor = QueryContextProcessor(Mock())
-    get_datasource: Mock
     with (
         app.app_context(),
         patch(
             "superset.common.query_context_processor.ChartDAO.find_by_id",
             return_value=chart,
         ),
-        patch(
-            "superset.daos.datasource.DatasourceDAO.get_datasource",
-            return_value=view,
-        ) as get_datasource,
-        patch(
-            
"superset.common.query_context_processor.security_manager.get_rls_cache_key",
-            return_value=[],
-        ),
     ):
-        first: dict[str, Any] = processor._annotation_cache_context(query)
+        first: dict[str, Any] | None = 
processor._annotation_cache_context(query)
+        assert first is not None
         layer.cache_version = 1
         session.info.clear()
         view.forget_metadata()
         assert processor._annotation_cache_context(query) != first

Review Comment:
   This inequality also passes if scope derivation starts failing after the 
version rotates: `_annotation_cache_context` returns `None` on any derivation 
error, and `None != first` is true. A regression that silently disables 
annotation caching for semantic views after a metadata change would therefore 
still satisfy this test (the `call_count == 2` check below doesn't distinguish 
it either), and the same applies to the version-rotation assertion in 
`test_cache_keys_compose_metadata_and_producer_versions`. Could the second 
context be captured and asserted non-`None`, with the rotated version field 
compared and the unchanged one asserted equal?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to