moomindani commented on code in PR #69998:
URL: https://github.com/apache/airflow/pull/69998#discussion_r3835874904


##########
providers/databricks/src/airflow/providers/databricks/plugins/databricks_workflow.py:
##########
@@ -471,13 +499,38 @@ class WorkflowJobRepairSingleTaskLink(BaseOperatorLink, 
LoggingMixin):
 
     name = "Repair a single task"
 
+    @property
+    def operators(self):
+        # Declared so deserialization keeps this plugin link instead of 
replacing it with
+        # XComOperatorLink. Lazy import avoids a circular import with the 
operator module.
+        from airflow.providers.databricks.operators.databricks import (
+            DatabricksNotebookOperator,
+            DatabricksTaskOperator,
+        )
+
+        return [DatabricksNotebookOperator, DatabricksTaskOperator]

Review Comment:
   This makes the plugin link match every `DatabricksNotebookOperator` / 
`DatabricksTaskOperator`, and
   plugin links take precedence over the operator's own serialized links ("If 
OperatorLinks with the same
   name exists, Links via Plugin have higher precedence", 
`serialization/serialized_objects.py:1117-1120`).
   That is what fixes the workflow case, but it also overrides 
`operators/databricks.py:1690-1697`, where
   the operator deliberately picks its link set per case:
   
   ```python
   else:
       # Databricks does not support repair for non-workflow tasks, hence do 
not show the repair link.
       self.operator_extra_links = (DatabricksJobRunLink(),)
   ```
   
   On Airflow 3 that is harmless, and I measured it: a standalone 
`DatabricksNotebookOperator` gains a
   `"Repair a single task"` entry that resolves to `None` — 
`_get_launch_task_id_v3` finds no launch task
   and `get_link` returns `""` — and the UI drops null URLs, so only the 
`/links` response gains a null key.
   
   On Airflow 2 it is not harmless. The provider still declares 
`apache-airflow>=2.11.0`
   (`pyproject.toml:62`); 2.11's deserializer has the same plugin matching and 
the same precedence merge
   (`serialized_objects.py:1266-1272` and `:1310-1315`); and the pre-3.0 branch 
of `get_link` degrades
   differently from the 3.x branch above. For a top-level task 
`operator.task_group` is the Dag's root
   group, so the `if not task_group` guard does not fire, and 
`get_launch_task_id` then raises
   `AirflowException("No launch task can be found in the task group.")`. 
Airflow 2.11's extra-links view
   catches only `ValueError` (`airflow/www/views.py:3249-3252`), so this 
surfaces as a 500 behind a button
   the UI has already rendered — on the plain notebook operator, which is the 
common case.
   
   I cannot run 2.11 here, so that last chain is code reading rather than 
measurement, but every step of it
   is in the released 2.11 source. Suggested fix in the next comment.
   
   `WorkflowJobRepairAllFailedLink` does not have this problem — 
`_CreateDatabricksWorkflowOperator` only
   ever exists inside a workflow task group.
   
   ---
   Drafted-by: Claude Code (Opus 5); reviewed by @moomindani before posting



##########
providers/databricks/src/airflow/providers/databricks/plugins/databricks_workflow.py:
##########
@@ -471,13 +499,38 @@ class WorkflowJobRepairSingleTaskLink(BaseOperatorLink, 
LoggingMixin):
 
     name = "Repair a single task"
 
+    @property
+    def operators(self):
+        # Declared so deserialization keeps this plugin link instead of 
replacing it with
+        # XComOperatorLink. Lazy import avoids a circular import with the 
operator module.
+        from airflow.providers.databricks.operators.databricks import (
+            DatabricksNotebookOperator,
+            DatabricksTaskOperator,
+        )
+
+        return [DatabricksNotebookOperator, DatabricksTaskOperator]
+
     def get_link(  # type: ignore[override]  # Signature intentionally kept 
this way for Airflow 2.x compatibility
         self,
         operator,
         dttm=None,
         *,
         ti_key: TaskInstanceKey | None = None,
     ) -> str:
+        if AIRFLOW_V_3_0_PLUS:
+            if not AIRFLOW_V_3_1_PLUS or ti_key is None:
+                # The Airflow-3 repair backend requires 3.1+ (see 
DatabricksWorkflowPlugin).
+                return ""
+            launch_task_id = _get_launch_task_id_v3(operator, ti_key)
+            if not launch_task_id:
+                return ""

Review Comment:
   This is the graceful degradation the Airflow 2 branch lacks. Now that the 
link is attached to
   non-workflow tasks as well, the cheapest fix is to mirror this shape below: 
when the launch task cannot
   be resolved, return `""` instead of letting `get_launch_task_id(task_group)` 
raise — i.e. around
   
   ```python
   if ".launch" not in ti_key.task_id:
       launch_task_id = get_launch_task_id(task_group)
   ```
   
   catch the "no launch task in this group" case and return `""`, so a 
standalone Databricks task renders
   no URL rather than a 500. That keeps the behaviour of the two branches 
aligned and leaves the workflow
   case untouched.
   
   ---
   Drafted-by: Claude Code (Opus 5); reviewed by @moomindani before posting



-- 
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]

Reply via email to