This is an automated email from the ASF dual-hosted git repository. vatsrahul1001 pushed a commit to branch v3-2-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 2debfd2c3b20e77a381a30a97cf49f26b23da90a Author: Rahul Vats <[email protected]> AuthorDate: Thu Apr 16 21:13:24 2026 +0530 [v3-2-test] Fix: PATCH /dags pagination bug and document wildcard dag_id_pattern (#65309) * [v3-2-test] Bump actions/github-script in the github-actions-updates group (#65150) (#65160) Bumps the github-actions-updates group with 1 update: [actions/github-script](https://github.com/actions/github-script). Updates `actions/github-script` from 8.0.0 to 9.0.0 - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/ed597411d8f924073f98dfc5c65a23a2325f34cd...3a2844b7e9c422d3c10d287c895573f7108da1b3) (cherry picked from commit e5a047ca8d23614cefeaad89e08eb1e3b0482e60) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions-updates ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [v3-2-test] Added breeze generate issue content for airflow-ctl (#65042) (#65241) * Add breeze generate issue content for airflow-ctl * add new command to doc (cherry picked from commit b24538b0bc3a53d8c666b62e52e023a17d102467) Co-authored-by: Justin Pakzad <[email protected]> * [v3-2-test] Run release calendar verification on its own schedule (#65118) (#65242) * Move release calendar verification to its own scheduled workflow Run dev/verify_release_calendar.py from a dedicated daily scheduled workflow instead of as a canary job in the main CI pipeline, and notify the #release-management Slack channel when the check fails so the issue is surfaced to release managers directly. * Include wiki and calendar links in release calendar Slack alert (cherry picked from commit 048e9a191b4a6625bb4bbad23fad537b256f4062) * Fix: PATCH /dags pagination bug and document wildcard dag_id_pattern (#63665) * fixed pagination bug and updated docstring to clarify dag_id_pattern wildcard usage * removed batch loop to update all dags in one shot and added additional test case * Fixed MySQL subquery issue (cherry picked from commit 9504886608f9b54398f757b4eb6fc3442141d28a) --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jarek Potiuk <[email protected]> Co-authored-by: Justin Pakzad <[email protected]> --- .../core_api/openapi/v2-rest-api-generated.yaml | 9 +++- .../api_fastapi/core_api/routes/public/dags.py | 29 ++++++++--- .../src/airflow/ui/openapi-gen/queries/queries.ts | 4 ++ .../ui/openapi-gen/requests/services.gen.ts | 4 ++ .../core_api/routes/public/test_dags.py | 60 ++++++++++++++++++++++ 5 files changed, 98 insertions(+), 8 deletions(-) diff --git a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml index 6ed47e9fc2f..24ab32f7113 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml +++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml @@ -3301,7 +3301,14 @@ paths: tags: - DAG summary: Patch Dags - description: Patch multiple DAGs. + description: 'Patch multiple DAGs. + + + If `dag_id_pattern` is not provided, no DAGs will be matched regardless + + of other filters. To match all DAGs, pass a wildcard value such as `~` + + or `%` for `dag_id_pattern`.' operationId: patch_dags security: - OAuth2PasswordBearer: [] diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dags.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dags.py index 6fbb7c831e9..b83cc1ef223 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dags.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dags.py @@ -26,10 +26,7 @@ from sqlalchemy import delete, func, insert, select, update from airflow.api.common import delete_dag as delete_dag_module from airflow.api_fastapi.common.dagbag import DagBagDep, get_latest_version_of_dag -from airflow.api_fastapi.common.db.common import ( - SessionDep, - paginated_select, -) +from airflow.api_fastapi.common.db.common import SessionDep, apply_filters_to_select, paginated_select from airflow.api_fastapi.common.db.dags import generate_dag_with_latest_run_query from airflow.api_fastapi.common.parameters import ( FilterOptionEnum, @@ -328,7 +325,13 @@ def patch_dags( session: SessionDep, update_mask: list[str] | None = Query(None), ) -> DAGCollectionResponse: - """Patch multiple DAGs.""" + """ + Patch multiple DAGs. + + If `dag_id_pattern` is not provided, no DAGs will be matched regardless + of other filters. To match all DAGs, pass a wildcard value such as `~` + or `%` for `dag_id_pattern`. + """ if update_mask: if update_mask != ["is_paused"]: raise HTTPException( @@ -356,10 +359,22 @@ def patch_dags( session=session, ) dags = session.scalars(dags_select).all() - dags_to_update = {dag.dag_id for dag in dags} + + filtered_dag_ids = apply_filters_to_select( + statement=select(DagModel.dag_id), + filters=[ + exclude_stale, + paused, + dag_id_pattern, + tags, + owners, + editable_dags_filter, + ], + ).subquery() + session.execute( update(DagModel) - .where(DagModel.dag_id.in_(dags_to_update)) + .where(DagModel.dag_id.in_(select(filtered_dag_ids.c.dag_id))) .values(is_paused=patch_body.is_paused) .execution_options(synchronize_session="fetch") ) diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts index 019d3ce4a94..1af349e1689 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts @@ -2117,6 +2117,10 @@ export const useDagRunServicePatchDagRun = <TData = Common.DagRunServicePatchDag /** * Patch Dags * Patch multiple DAGs. +* +* If `dag_id_pattern` is not provided, no DAGs will be matched regardless +* of other filters. To match all DAGs, pass a wildcard value such as `~` +* or `%` for `dag_id_pattern`. * @param data The data for the request. * @param data.requestBody * @param data.updateMask diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts index 1fbb2db01f5..a23f5dbd012 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts @@ -1452,6 +1452,10 @@ export class DagService { /** * Patch Dags * Patch multiple DAGs. + * + * If `dag_id_pattern` is not provided, no DAGs will be matched regardless + * of other filters. To match all DAGs, pass a wildcard value such as `~` + * or `%` for `dag_id_pattern`. * @param data The data for the request. * @param data.requestBody * @param data.updateMask diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py index 55515b76931..434cd9e07f3 100644 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py +++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py @@ -790,6 +790,66 @@ class TestPatchDags(TestDagEndpoint): assert paused_dag_ids == set(expected_paused_ids) check_last_log(session, dag_id=DAG1_ID, event="patch_dag", logical_date=None) + @pytest.mark.parametrize( + ("tags_to_add", "query_params", "body", "expected_ids", "expected_paused_ids"), + [ + ( + [(DAG1_ID, "tag_1"), (DAG2_ID, "tag_1")], + {"dag_id_pattern": "~", "tags": ["tag_1"], "tags_match_mode": "any"}, + {"is_paused": True}, + [DAG1_ID, DAG2_ID], + [DAG1_ID, DAG2_ID], + ), + ( + [(DAG1_ID, "tag_1"), (DAG2_ID, "tag_2")], + {"dag_id_pattern": "~", "tags": ["tag_1", "tag_2"], "tags_match_mode": "any"}, + {"is_paused": True}, + [DAG1_ID, DAG2_ID], + [DAG1_ID, DAG2_ID], + ), + ( + [(DAG1_ID, "tag_1"), (DAG1_ID, "tag_3"), (DAG2_ID, "tag_1"), (DAG2_ID, "tag_3")], + {"dag_id_pattern": "~", "tags": ["tag_1", "tag_3"], "tags_match_mode": "all"}, + {"is_paused": True}, + [DAG1_ID, DAG2_ID], + [DAG1_ID, DAG2_ID], + ), + ], + ) + def test_patch_dags_by_tags( + self, + test_client, + tags_to_add, + query_params, + body, + expected_ids, + expected_paused_ids, + session, + ): + for dag_id, tag_name in tags_to_add: + session.add(DagTag(dag_id=dag_id, name=tag_name)) + session.commit() + + response = test_client.patch("/dags", json=body, params=query_params) + assert response.status_code == 200 + resp_body = response.json() + assert {dag["dag_id"] for dag in resp_body["dags"]} == set(expected_ids) + paused_dag_ids = {dag["dag_id"] for dag in resp_body["dags"] if dag["is_paused"]} + assert paused_dag_ids == set(expected_paused_ids) + + def test_patch_dags_updates_all_beyond_limit(self, test_client, session): + response = test_client.patch( + "/dags", + json={"is_paused": True}, + params={"dag_id_pattern": "~", "limit": 1}, + ) + assert response.status_code == 200 + assert len(response.json()["dags"]) == 1 + paused_dags = session.scalars( + select(DagModel.dag_id).where(DagModel.is_paused, ~DagModel.is_stale) + ).all() + assert set(paused_dags) == {DAG1_ID, DAG2_ID} + @mock.patch("airflow.api_fastapi.auth.managers.base_auth_manager.BaseAuthManager.get_authorized_dag_ids") def test_patch_dags_should_call_authorized_dag_ids(self, mock_get_authorized_dag_ids, test_client): mock_get_authorized_dag_ids.return_value = {DAG1_ID, DAG2_ID}
