Skip to content

Conversation

@arjav1528
Copy link
Contributor

@arjav1528 arjav1528 commented Jan 6, 2026

Description

This PR adds two new React components that restore the task group marking functionality:

  1. MarkGroupTaskInstanceAsButton - A dropdown button component that allows users to select a state (success/failed) and opens a dialog for confirmation
  2. MarkGroupTaskInstanceAsDialog - A dialog component that provides:
    • Options to include past/future/upstream/downstream task instances
    • Dry-run preview showing which task instances will be affected
    • Ability to add notes to the state change
    • Bulk update using the task instance bulk API for efficiency

The button is integrated into the Group Task Instance page header, alongside the existing Clear button.

Screenshots

image image

Solves #60121

Closes: #56103

@boring-cyborg boring-cyborg bot added the area:UI Related to UI/UX. For Frontend Developers. label Jan 6, 2026
@bohdan-pd
Copy link

@arjav1528 Thank you for working on that! 👍

@arjav1528 arjav1528 force-pushed the main branch 2 times, most recently from 5db3cd8 to c9d177e Compare January 6, 2026 18:50
@arjav1528 arjav1528 marked this pull request as draft January 6, 2026 18:50
@arjav1528 arjav1528 marked this pull request as ready for review January 6, 2026 18:50
@arjav1528
Copy link
Contributor Author

@arjav1528 Thank you for working on that! 👍

is the UI ok or do you want me to refactor that

Copy link
Contributor

@bbovenzi bbovenzi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on! Do you mind doing more manually testing to find where MarkAs isn't quite working? I think there are some issues with the API that we need to tackle before we can merge any UI changes.

@arjav1528 arjav1528 force-pushed the main branch 2 times, most recently from b068b63 to da03292 Compare January 7, 2026 16:39
Copy link
Contributor

@bbovenzi bbovenzi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried using this locally and still had issues. Could you both manually test this out and also add fastapi test for dry runs?

@arjav1528
Copy link
Contributor Author

arjav1528 commented Jan 7, 2026

I tried using this locally and still had issues. Could you both manually test this out and also add fastapi test for dry runs?

Sorry about the trouble. I’ve tested this locally and couldn’t reproduce the issue. I have reproduced the issue, solved it, you can run it locally now

@arjav1528 arjav1528 force-pushed the main branch 3 times, most recently from d24fdae to 34153d8 Compare January 8, 2026 13:12
@arjav1528 arjav1528 requested a review from choo121600 as a code owner January 8, 2026 13:12
arjav1528 and others added 16 commits January 22, 2026 00:31
…ate update logic and removing listener triggers
…ion for improved clarity and reuse in task instance patching
… ID, including dry run functionality and improved state management
…sk_instances.py

Co-authored-by: Jason(Zhe-You) Liu <68415893+jason810496@users.noreply.github.com>
… affected instances and streamline updates for both single task instances and task groups
…points, enhancing dry run functionality and validation logic
…sk_instances.py

Co-authored-by: Jason(Zhe-You) Liu <68415893+jason810496@users.noreply.github.com>
…llection of unique task instances in patching logic
… based on test group and downgrade SQLAlchemy flag
…tances to ensure correct handling of failed states
…up_id over identifier, improving error handling and validation flow
@arjav1528
Copy link
Contributor Author

@jason810496 could you please review the PR and merge if good to go,

Copy link
Member

@jason810496 jason810496 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @arjav1528, thank you for your patience and for the update.

I recommend reviewing the earlier comments, as some of the same issues have not yet been fully resolved. Thanks!

Comment on lines 178 to 180
# Default is 60 minutes; Min SQLAlchemy providers DB runs can exceed that slightly.
TOTAL_TEST_TIMEOUT: "${{ (inputs.test-group == 'providers' && inputs.downgrade-sqlalchemy == 'true') && '3900' || '3600' }}"
if: inputs.test-group == 'core' || inputs.skip-providers-tests != 'true'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thinks this is a bad rebase. The change doesn't seem to be related to the PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still exist, needs another rebase.

…ce function to simplify code and improve readability
…fier for task group, enhancing clarity in API requests
… for task group, improving API request structure
…bulk update logic to remove dry_run parameter for improved clarity
@arjav1528
Copy link
Contributor Author

Thanks for the reviews @jason810496 @pierrejeambrun @bbovenzi! I've addressed all the feedback:

Backend fixes:

✅ Removed the unreachable duplicate return statement in patch_task_instance
✅ Created a separate bulk_task_instances_dry_run endpoint instead of using a dry_run query parameter - this avoids the union return type (BulkResponse | TaskInstanceCollectionResponse) that was causing client-side confusion
✅ Removed the unused _validate_patch_request_body function (dead code)
✅ Reverted the unrelated CI timeout change in run-unit-tests.yml
Frontend fixes: 5. ✅ Fixed usePatchTaskGroup.ts to call the correct endpoint {identifier}?task_group_id=... instead of the non-existent {task_group_id} 6. ✅ Fixed usePatchTaskGroupDryRun.ts to call dry_run?task_group_id=... instead of dry_run

The UI was previously calling endpoints that didn't exist in the backend, which would have caused all task group mark-as operations to fail with 404 errors.

Ready for another review when you have time!

Copy link
Member

@guan404ming guan404ming left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still lots of function in backend is incompleted. How about let keep only backend changes in this pr to make it more easily to review and implement? Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think mostly tests mock wiring, not real logic here. Maybe we could just test here by e2e test instead of unit tests. We could handle in a follow up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

}
};

// Use direct API call until OpenAPI types are generated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not really sure why we need to call it directly here.

Comment on lines 178 to 180
# Default is 60 minutes; Min SQLAlchemy providers DB runs can exceed that slightly.
TOTAL_TEST_TIMEOUT: "${{ (inputs.test-group == 'providers' && inputs.downgrade-sqlalchemy == 'true') && '3900' || '3600' }}"
if: inputs.test-group == 'core' || inputs.skip-providers-tests != 'true'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still exist, needs another rebase.

Copy link
Member

@pierrejeambrun pierrejeambrun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall direction looks good. A few comments/suggestions, thanks for the PR.

(feel free to directly resolve comments you have addressed so we can easily identify where works remains)

Comment on lines +1000 to +1003
service = BulkTaskInstanceService(
session=session, request=request, dag_id=dag_id, dag_run_id=dag_run_id, dag_bag=dag_bag, user=user
).handle_request()
)
return service.handle_request()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change doesn't seem related and should be removed.

Comment on lines +967 to +984
@task_instances_router.patch(
task_instances_prefix + "/dry_run",
dependencies=[Depends(requires_access_dag(method="PUT", access_entity=DagAccessEntity.TASK_INSTANCE))],
operation_id="bulk_task_instances_dry_run",
)
def bulk_task_instances_dry_run(
request: BulkBody[BulkTaskInstanceBody],
session: SessionDep,
dag_id: str,
dag_bag: DagBagDep,
dag_run_id: str,
user: GetUserDep,
) -> TaskInstanceCollectionResponse:
"""Bulk update task instances dry run - returns affected task instances without making changes."""
service = BulkTaskInstanceService(
session=session, request=request, dag_id=dag_id, dag_run_id=dag_run_id, dag_bag=dag_bag, user=user
)
return service.handle_request_dry_run()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change deosn't seem related and should be removed.

const downstream = selectedOptions.includes("downstream");

// eslint-disable-next-line unicorn/no-null -- DAGRunResponse["note"] type requires null, not undefined
const [note, setNote] = useState<string | null>(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialise with "" and replace by null at the backend call so you don't need that eslint-disable-next-line unicorn/no-null.

Check other calls for [note, setNote] in the code base we never have to ignore this.

)
assert response.status_code == 200, response.text
response_data = response.json()
assert response_data["total_entries"] == 4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert Task id ans state of the response tasks

Comment on lines +5188 to +5204
def test_dry_run_should_return_affected_task_instances(self, test_client, session):
"""Test dry run returns affected task instances without making changes."""
self.create_task_instances(session, dag_id=self.DAG_ID)

response = test_client.patch(
f"/dags/{self.DAG_ID}/dagRuns/{self.RUN_ID}/taskInstances/{self.TASK_ID}/dry_run",
params={"task_group_id": self.TASK_GROUP_ID},
json={"new_state": self.NEW_STATE},
)
assert response.status_code == 200, response.text
response_data = response.json()
# section_1 has 3 tasks
assert response_data["total_entries"] == 3

# Verify task_ids belong to section_1
for ti in response_data["task_instances"]:
assert ti["task_id"].startswith("section_1.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge this with the previous test

assert response.status_code == 422


class TestBulkTaskInstancesDryRun(TestTaskInstanceEndpoint):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bulk is not appropriate, it qualifies the bulk update endpoint which is another endpoint, That's TestPatchTaskInstanceDryRunByTaskGroupId And it should probably be merged into the existing class for testing TestPatchTaskInstanceDryRun that already exist

assert response.json() == {"task_instances": [], "total_entries": 0}


class TestPatchTaskInstanceByTaskGroupId(TestTaskInstanceEndpoint):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible Merge this into TestPatchTaskInstance. 1 test class = 1 endpoint. It's the same test endpoint with different parameter.

TASK_ID = "start"
NEW_STATE = "failed"

def test_should_update_task_group_instances_state(self, test_client, session):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need to add test for updating the 'note'. All tis should have the same note. It would be confusing for users to have the 'note' work only for single TI and not for groups.

readonly onSuccess?: () => void;
};

export const useBulkUpdateTaskInstances = ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is that for ?

Comment on lines +1 to +6
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this file for? Seems unrelated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:UI Related to UI/UX. For Frontend Developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing the ability to mark all tasks in a Task Group Success/Failed in AF3

8 participants