This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-2-test by this push:
new ae83f9bf264 UI: Fix "Mark state as..." buttons grayed out when
task/DAGRun already in target state (#66198) (#66919)
ae83f9bf264 is described below
commit ae83f9bf264618bf66bdf412d58a04c88b8e7d71
Author: Rahul Vats <[email protected]>
AuthorDate: Thu May 14 19:52:40 2026 +0530
UI: Fix "Mark state as..." buttons grayed out when task/DAGRun already in
target state (#66198) (#66919)
* UI: Fix "Mark state as..." buttons grayed out when task/DAGRun already in
target state
Menu items for marking a task instance, task group, or DAG run as
success/failed were disabled whenever the item's current state matched
the target state. This blocked users from re-applying the same state
(e.g. marking an already-succeeded DAG run as success to also flip all
its tasks). The same bug existed in Airflow 2.x (#36219) and regressed
in the Airflow 3.x UI redesign.
Remove the state-equality guards from:
- the `disabled` prop on Menu.Item
- the `onClick` early-return
- the hotkey `enabled` conditions
- the Tooltip `disabled` condition
Add regression tests for MarkRunAsButton to prevent future recurrence.
Fixes #66197
* Replace unit test with inline comments explaining why state-match is not
disabled
Per review feedback: comments in the source communicate the intent more
reliably than a separate test file that could be deleted without context.
https://claude.ai/code/session_012oET1NyiNZe44zWbr9GZnn
---------
(cherry picked from commit 505924feaa4de521c364f24d7562a35d951561f0)
Co-authored-by: internetcoffeephone
<[email protected]>
Co-authored-by: Claude <[email protected]>
---
.../ui/src/components/MarkAs/Run/MarkRunAsButton.tsx | 14 ++++++--------
.../MarkAs/TaskInstance/MarkTaskInstanceAsButton.tsx | 14 ++++++--------
2 files changed, 12 insertions(+), 16 deletions(-)
diff --git
a/airflow-core/src/airflow/ui/src/components/MarkAs/Run/MarkRunAsButton.tsx
b/airflow-core/src/airflow/ui/src/components/MarkAs/Run/MarkRunAsButton.tsx
index a71fc8c2821..47025709a5f 100644
--- a/airflow-core/src/airflow/ui/src/components/MarkAs/Run/MarkRunAsButton.tsx
+++ b/airflow-core/src/airflow/ui/src/components/MarkAs/Run/MarkRunAsButton.tsx
@@ -46,7 +46,7 @@ const MarkRunAsButton = ({ dagRun, isHotkeyEnabled = false }:
Props) => {
setState("failed");
onOpen();
},
- { enabled: isHotkeyEnabled && dagRun.state !== "failed" },
+ { enabled: isHotkeyEnabled },
);
useHotkeys(
@@ -55,7 +55,7 @@ const MarkRunAsButton = ({ dagRun, isHotkeyEnabled = false }:
Props) => {
setState("success");
onOpen();
},
- { enabled: isHotkeyEnabled && dagRun.state !== "success" },
+ { enabled: isHotkeyEnabled },
);
return (
@@ -94,20 +94,18 @@ const MarkRunAsButton = ({ dagRun, isHotkeyEnabled = false
}: Props) => {
<Tooltip
closeDelay={100}
content={content}
- disabled={!isHotkeyEnabled || dagRun.state === menuState}
+ disabled={!isHotkeyEnabled}
key={menuState}
openDelay={100}
>
+ {/* Not disabled when state matches: re-applying lets users
also flip upstream/downstream tasks */}
<Menu.Item
asChild
data-testid={`mark-run-as-${menuState}`}
- disabled={dagRun.state === menuState}
key={menuState}
onClick={() => {
- if (dagRun.state !== menuState) {
- setState(menuState);
- onOpen();
- }
+ setState(menuState);
+ onOpen();
}}
value={menuState}
>
diff --git
a/airflow-core/src/airflow/ui/src/components/MarkAs/TaskInstance/MarkTaskInstanceAsButton.tsx
b/airflow-core/src/airflow/ui/src/components/MarkAs/TaskInstance/MarkTaskInstanceAsButton.tsx
index 8ae616232b9..19d1e8c75c5 100644
---
a/airflow-core/src/airflow/ui/src/components/MarkAs/TaskInstance/MarkTaskInstanceAsButton.tsx
+++
b/airflow-core/src/airflow/ui/src/components/MarkAs/TaskInstance/MarkTaskInstanceAsButton.tsx
@@ -47,7 +47,7 @@ const MarkTaskInstanceAsButton = ({ isHotkeyEnabled = false,
taskInstance }: Pro
setState("failed");
onOpen();
},
- { enabled: isHotkeyEnabled && taskInstance.state !== "failed" },
+ { enabled: isHotkeyEnabled },
);
useHotkeys(
@@ -56,7 +56,7 @@ const MarkTaskInstanceAsButton = ({ isHotkeyEnabled = false,
taskInstance }: Pro
setState("success");
onOpen();
},
- { enabled: isHotkeyEnabled && taskInstance.state !== "success" },
+ { enabled: isHotkeyEnabled },
);
return (
@@ -96,19 +96,17 @@ const MarkTaskInstanceAsButton = ({ isHotkeyEnabled =
false, taskInstance }: Pro
<Tooltip
closeDelay={100}
content={content}
- disabled={!isHotkeyEnabled || taskInstance.state === menuState}
+ disabled={!isHotkeyEnabled}
key={menuState}
openDelay={100}
>
+ {/* Not disabled when state matches: re-applying lets users
also flip upstream/downstream tasks */}
<Menu.Item
asChild
- disabled={taskInstance.state === menuState}
key={menuState}
onClick={() => {
- if (taskInstance.state !== menuState) {
- setState(menuState);
- onOpen();
- }
+ setState(menuState);
+ onOpen();
}}
value={menuState}
>