This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 5b5f8f4650e AIP-84 De-nest Dag Tags endpoint (#44608)
5b5f8f4650e is described below
commit 5b5f8f4650e52a5a6c7e1ed6fb2cbbca1145d14e
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Wed Dec 4 20:26:16 2024 +0800
AIP-84 De-nest Dag Tags endpoint (#44608)
* AIP-84 De-nest Tag Tags endpoint
* Fix CI
---
.../api_fastapi/core_api/datamodels/dag_tags.py | 27 +++
airflow/api_fastapi/core_api/datamodels/dags.py | 7 -
.../api_fastapi/core_api/openapi/v1-generated.yaml | 128 +++++------
.../api_fastapi/core_api/routes/public/__init__.py | 2 +
.../api_fastapi/core_api/routes/public/dag_tags.py | 71 ++++++
airflow/api_fastapi/core_api/routes/public/dags.py | 38 +---
airflow/ui/openapi-gen/queries/common.ts | 50 ++---
airflow/ui/openapi-gen/queries/prefetch.ts | 70 +++---
airflow/ui/openapi-gen/queries/queries.ts | 88 ++++----
airflow/ui/openapi-gen/queries/suspense.ts | 88 ++++----
airflow/ui/openapi-gen/requests/services.gen.ts | 66 +++---
airflow/ui/openapi-gen/requests/types.gen.ts | 64 +++---
.../core_api/routes/public/test_dag_tags.py | 250 +++++++++++++++++++++
.../core_api/routes/public/test_dags.py | 114 ----------
14 files changed, 629 insertions(+), 434 deletions(-)
diff --git a/airflow/api_fastapi/core_api/datamodels/dag_tags.py
b/airflow/api_fastapi/core_api/datamodels/dag_tags.py
new file mode 100644
index 00000000000..9e67e1ce7b1
--- /dev/null
+++ b/airflow/api_fastapi/core_api/datamodels/dag_tags.py
@@ -0,0 +1,27 @@
+# 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
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from pydantic import BaseModel
+
+
+class DAGTagCollectionResponse(BaseModel):
+ """DAG Tags Collection serializer for responses."""
+
+ tags: list[str]
+ total_entries: int
diff --git a/airflow/api_fastapi/core_api/datamodels/dags.py
b/airflow/api_fastapi/core_api/datamodels/dags.py
index f1dd7bd7980..eddf0e1be22 100644
--- a/airflow/api_fastapi/core_api/datamodels/dags.py
+++ b/airflow/api_fastapi/core_api/datamodels/dags.py
@@ -154,10 +154,3 @@ class DAGDetailsResponse(DAGResponse):
def concurrency(self) -> int:
"""Return max_active_tasks as concurrency."""
return self.max_active_tasks
-
-
-class DAGTagCollectionResponse(BaseModel):
- """DAG Tags Collection serializer for responses."""
-
- tags: list[str]
- total_entries: int
diff --git a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
index 30a89a589f9..d6ea8dcec50 100644
--- a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
+++ b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
@@ -2636,70 +2636,6 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
- /public/dags/tags:
- get:
- tags:
- - DAG
- summary: Get Dag Tags
- description: Get all DAG tags.
- operationId: get_dag_tags
- parameters:
- - name: limit
- in: query
- required: false
- schema:
- type: integer
- minimum: 0
- default: 100
- title: Limit
- - name: offset
- in: query
- required: false
- schema:
- type: integer
- minimum: 0
- default: 0
- title: Offset
- - name: order_by
- in: query
- required: false
- schema:
- type: string
- default: name
- title: Order By
- - name: tag_name_pattern
- in: query
- required: false
- schema:
- anyOf:
- - type: string
- - type: 'null'
- title: Tag Name Pattern
- responses:
- '200':
- description: Successful Response
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DAGTagCollectionResponse'
- '401':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/HTTPExceptionResponse'
- description: Unauthorized
- '403':
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/HTTPExceptionResponse'
- description: Forbidden
- '422':
- description: Validation Error
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/HTTPValidationError'
/public/dags/{dag_id}:
get:
tags:
@@ -5702,6 +5638,70 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
+ /public/dagTags:
+ get:
+ tags:
+ - DAG
+ summary: Get Dag Tags
+ description: Get all DAG tags.
+ operationId: get_dag_tags
+ parameters:
+ - name: limit
+ in: query
+ required: false
+ schema:
+ type: integer
+ minimum: 0
+ default: 100
+ title: Limit
+ - name: offset
+ in: query
+ required: false
+ schema:
+ type: integer
+ minimum: 0
+ default: 0
+ title: Offset
+ - name: order_by
+ in: query
+ required: false
+ schema:
+ type: string
+ default: name
+ title: Order By
+ - name: tag_name_pattern
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ - type: 'null'
+ title: Tag Name Pattern
+ responses:
+ '200':
+ description: Successful Response
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/DAGTagCollectionResponse'
+ '401':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/HTTPExceptionResponse'
+ description: Unauthorized
+ '403':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/HTTPExceptionResponse'
+ description: Forbidden
+ '422':
+ description: Validation Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/HTTPValidationError'
/public/monitor/health:
get:
tags:
diff --git a/airflow/api_fastapi/core_api/routes/public/__init__.py
b/airflow/api_fastapi/core_api/routes/public/__init__.py
index fd9cfa1a2d3..2a7c81782a9 100644
--- a/airflow/api_fastapi/core_api/routes/public/__init__.py
+++ b/airflow/api_fastapi/core_api/routes/public/__init__.py
@@ -29,6 +29,7 @@ from airflow.api_fastapi.core_api.routes.public.dag_parsing
import dag_parsing_r
from airflow.api_fastapi.core_api.routes.public.dag_run import dag_run_router
from airflow.api_fastapi.core_api.routes.public.dag_sources import
dag_sources_router
from airflow.api_fastapi.core_api.routes.public.dag_stats import
dag_stats_router
+from airflow.api_fastapi.core_api.routes.public.dag_tags import dag_tags_router
from airflow.api_fastapi.core_api.routes.public.dag_warning import
dag_warning_router
from airflow.api_fastapi.core_api.routes.public.dags import dags_router
from airflow.api_fastapi.core_api.routes.public.event_logs import
event_logs_router
@@ -75,6 +76,7 @@ authenticated_router.include_router(tasks_router)
authenticated_router.include_router(variables_router)
authenticated_router.include_router(task_instances_log_router)
authenticated_router.include_router(dag_parsing_router)
+authenticated_router.include_router(dag_tags_router)
# Include authenticated router in public router
diff --git a/airflow/api_fastapi/core_api/routes/public/dag_tags.py
b/airflow/api_fastapi/core_api/routes/public/dag_tags.py
new file mode 100644
index 00000000000..73714177d0b
--- /dev/null
+++ b/airflow/api_fastapi/core_api/routes/public/dag_tags.py
@@ -0,0 +1,71 @@
+# 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
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from typing import Annotated
+
+from fastapi import Depends
+from sqlalchemy import select
+
+from airflow.api_fastapi.common.db.common import (
+ SessionDep,
+ paginated_select,
+)
+from airflow.api_fastapi.common.parameters import (
+ QueryDagTagPatternSearch,
+ QueryLimit,
+ QueryOffset,
+ SortParam,
+)
+from airflow.api_fastapi.common.router import AirflowRouter
+from airflow.api_fastapi.core_api.datamodels.dag_tags import
DAGTagCollectionResponse
+from airflow.models.dag import DagTag
+
+dag_tags_router = AirflowRouter(tags=["DAG"], prefix="/dagTags")
+
+
+@dag_tags_router.get(
+ "",
+)
+def get_dag_tags(
+ limit: QueryLimit,
+ offset: QueryOffset,
+ order_by: Annotated[
+ SortParam,
+ Depends(
+ SortParam(
+ ["name"],
+ DagTag,
+ ).dynamic_depends()
+ ),
+ ],
+ tag_name_pattern: QueryDagTagPatternSearch,
+ session: SessionDep,
+) -> DAGTagCollectionResponse:
+ """Get all DAG tags."""
+ query = select(DagTag.name).group_by(DagTag.name)
+ dag_tags_select, total_entries = paginated_select(
+ statement=query,
+ filters=[tag_name_pattern],
+ order_by=order_by,
+ offset=offset,
+ limit=limit,
+ session=session,
+ )
+ dag_tags = session.execute(dag_tags_select).scalars().all()
+ return DAGTagCollectionResponse(tags=[x for x in dag_tags],
total_entries=total_entries)
diff --git a/airflow/api_fastapi/core_api/routes/public/dags.py
b/airflow/api_fastapi/core_api/routes/public/dags.py
index 9663c0f8662..395855a0a05 100644
--- a/airflow/api_fastapi/core_api/routes/public/dags.py
+++ b/airflow/api_fastapi/core_api/routes/public/dags.py
@@ -20,7 +20,7 @@ from __future__ import annotations
from typing import Annotated
from fastapi import Depends, HTTPException, Query, Request, Response, status
-from sqlalchemy import select, update
+from sqlalchemy import update
from airflow.api.common import delete_dag as delete_dag_module
from airflow.api_fastapi.common.db.common import (
@@ -32,7 +32,6 @@ from airflow.api_fastapi.common.parameters import (
QueryDagDisplayNamePatternSearch,
QueryDagIdPatternSearch,
QueryDagIdPatternSearchWithNone,
- QueryDagTagPatternSearch,
QueryLastDagRunStateFilter,
QueryLimit,
QueryOffset,
@@ -48,11 +47,10 @@ from airflow.api_fastapi.core_api.datamodels.dags import (
DAGDetailsResponse,
DAGPatchBody,
DAGResponse,
- DAGTagCollectionResponse,
)
from airflow.api_fastapi.core_api.openapi.exceptions import
create_openapi_http_exception_doc
from airflow.exceptions import AirflowException, DagNotFound
-from airflow.models import DAG, DagModel, DagTag
+from airflow.models import DAG, DagModel
from airflow.models.dagrun import DagRun
dags_router = AirflowRouter(tags=["DAG"], prefix="/dags")
@@ -107,38 +105,6 @@ def get_dags(
)
-@dags_router.get(
- "/tags",
-)
-def get_dag_tags(
- limit: QueryLimit,
- offset: QueryOffset,
- order_by: Annotated[
- SortParam,
- Depends(
- SortParam(
- ["name"],
- DagTag,
- ).dynamic_depends()
- ),
- ],
- tag_name_pattern: QueryDagTagPatternSearch,
- session: SessionDep,
-) -> DAGTagCollectionResponse:
- """Get all DAG tags."""
- query = select(DagTag.name).group_by(DagTag.name)
- dag_tags_select, total_entries = paginated_select(
- statement=query,
- filters=[tag_name_pattern],
- order_by=order_by,
- offset=offset,
- limit=limit,
- session=session,
- )
- dag_tags = session.execute(dag_tags_select).scalars().all()
- return DAGTagCollectionResponse(tags=[x for x in dag_tags],
total_entries=total_entries)
-
-
@dags_router.get(
"/{dag_id}",
responses=create_openapi_http_exception_doc(
diff --git a/airflow/ui/openapi-gen/queries/common.ts
b/airflow/ui/openapi-gen/queries/common.ts
index 766c08ae909..c8bc4d06729 100644
--- a/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow/ui/openapi-gen/queries/common.ts
@@ -656,31 +656,6 @@ export const UseDagServiceGetDagsKeyFn = (
},
]),
];
-export type DagServiceGetDagTagsDefaultResponse = Awaited<
- ReturnType<typeof DagService.getDagTags>
->;
-export type DagServiceGetDagTagsQueryResult<
- TData = DagServiceGetDagTagsDefaultResponse,
- TError = unknown,
-> = UseQueryResult<TData, TError>;
-export const useDagServiceGetDagTagsKey = "DagServiceGetDagTags";
-export const UseDagServiceGetDagTagsKeyFn = (
- {
- limit,
- offset,
- orderBy,
- tagNamePattern,
- }: {
- limit?: number;
- offset?: number;
- orderBy?: string;
- tagNamePattern?: string;
- } = {},
- queryKey?: Array<unknown>,
-) => [
- useDagServiceGetDagTagsKey,
- ...(queryKey ?? [{ limit, offset, orderBy, tagNamePattern }]),
-];
export type DagServiceGetDagDefaultResponse = Awaited<
ReturnType<typeof DagService.getDag>
>;
@@ -713,6 +688,31 @@ export const UseDagServiceGetDagDetailsKeyFn = (
},
queryKey?: Array<unknown>,
) => [useDagServiceGetDagDetailsKey, ...(queryKey ?? [{ dagId }])];
+export type DagServiceGetDagTagsDefaultResponse = Awaited<
+ ReturnType<typeof DagService.getDagTags>
+>;
+export type DagServiceGetDagTagsQueryResult<
+ TData = DagServiceGetDagTagsDefaultResponse,
+ TError = unknown,
+> = UseQueryResult<TData, TError>;
+export const useDagServiceGetDagTagsKey = "DagServiceGetDagTags";
+export const UseDagServiceGetDagTagsKeyFn = (
+ {
+ limit,
+ offset,
+ orderBy,
+ tagNamePattern,
+ }: {
+ limit?: number;
+ offset?: number;
+ orderBy?: string;
+ tagNamePattern?: string;
+ } = {},
+ queryKey?: Array<unknown>,
+) => [
+ useDagServiceGetDagTagsKey,
+ ...(queryKey ?? [{ limit, offset, orderBy, tagNamePattern }]),
+];
export type EventLogServiceGetEventLogDefaultResponse = Awaited<
ReturnType<typeof EventLogService.getEventLog>
>;
diff --git a/airflow/ui/openapi-gen/queries/prefetch.ts
b/airflow/ui/openapi-gen/queries/prefetch.ts
index aa5edc206be..40e7006e8f8 100644
--- a/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -850,41 +850,6 @@ export const prefetchUseDagServiceGetDags = (
tags,
}),
});
-/**
- * Get Dag Tags
- * Get all DAG tags.
- * @param data The data for the request.
- * @param data.limit
- * @param data.offset
- * @param data.orderBy
- * @param data.tagNamePattern
- * @returns DAGTagCollectionResponse Successful Response
- * @throws ApiError
- */
-export const prefetchUseDagServiceGetDagTags = (
- queryClient: QueryClient,
- {
- limit,
- offset,
- orderBy,
- tagNamePattern,
- }: {
- limit?: number;
- offset?: number;
- orderBy?: string;
- tagNamePattern?: string;
- } = {},
-) =>
- queryClient.prefetchQuery({
- queryKey: Common.UseDagServiceGetDagTagsKeyFn({
- limit,
- offset,
- orderBy,
- tagNamePattern,
- }),
- queryFn: () =>
- DagService.getDagTags({ limit, offset, orderBy, tagNamePattern }),
- });
/**
* Get Dag
* Get basic information about a DAG.
@@ -925,6 +890,41 @@ export const prefetchUseDagServiceGetDagDetails = (
queryKey: Common.UseDagServiceGetDagDetailsKeyFn({ dagId }),
queryFn: () => DagService.getDagDetails({ dagId }),
});
+/**
+ * Get Dag Tags
+ * Get all DAG tags.
+ * @param data The data for the request.
+ * @param data.limit
+ * @param data.offset
+ * @param data.orderBy
+ * @param data.tagNamePattern
+ * @returns DAGTagCollectionResponse Successful Response
+ * @throws ApiError
+ */
+export const prefetchUseDagServiceGetDagTags = (
+ queryClient: QueryClient,
+ {
+ limit,
+ offset,
+ orderBy,
+ tagNamePattern,
+ }: {
+ limit?: number;
+ offset?: number;
+ orderBy?: string;
+ tagNamePattern?: string;
+ } = {},
+) =>
+ queryClient.prefetchQuery({
+ queryKey: Common.UseDagServiceGetDagTagsKeyFn({
+ limit,
+ offset,
+ orderBy,
+ tagNamePattern,
+ }),
+ queryFn: () =>
+ DagService.getDagTags({ limit, offset, orderBy, tagNamePattern }),
+ });
/**
* Get Event Log
* @param data The data for the request.
diff --git a/airflow/ui/openapi-gen/queries/queries.ts
b/airflow/ui/openapi-gen/queries/queries.ts
index efaaf0002d8..624b9c38be8 100644
--- a/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow/ui/openapi-gen/queries/queries.ts
@@ -1046,50 +1046,6 @@ export const useDagServiceGetDags = <
}) as TData,
...options,
});
-/**
- * Get Dag Tags
- * Get all DAG tags.
- * @param data The data for the request.
- * @param data.limit
- * @param data.offset
- * @param data.orderBy
- * @param data.tagNamePattern
- * @returns DAGTagCollectionResponse Successful Response
- * @throws ApiError
- */
-export const useDagServiceGetDagTags = <
- TData = Common.DagServiceGetDagTagsDefaultResponse,
- TError = unknown,
- TQueryKey extends Array<unknown> = unknown[],
->(
- {
- limit,
- offset,
- orderBy,
- tagNamePattern,
- }: {
- limit?: number;
- offset?: number;
- orderBy?: string;
- tagNamePattern?: string;
- } = {},
- queryKey?: TQueryKey,
- options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
-) =>
- useQuery<TData, TError>({
- queryKey: Common.UseDagServiceGetDagTagsKeyFn(
- { limit, offset, orderBy, tagNamePattern },
- queryKey,
- ),
- queryFn: () =>
- DagService.getDagTags({
- limit,
- offset,
- orderBy,
- tagNamePattern,
- }) as TData,
- ...options,
- });
/**
* Get Dag
* Get basic information about a DAG.
@@ -1142,6 +1098,50 @@ export const useDagServiceGetDagDetails = <
queryFn: () => DagService.getDagDetails({ dagId }) as TData,
...options,
});
+/**
+ * Get Dag Tags
+ * Get all DAG tags.
+ * @param data The data for the request.
+ * @param data.limit
+ * @param data.offset
+ * @param data.orderBy
+ * @param data.tagNamePattern
+ * @returns DAGTagCollectionResponse Successful Response
+ * @throws ApiError
+ */
+export const useDagServiceGetDagTags = <
+ TData = Common.DagServiceGetDagTagsDefaultResponse,
+ TError = unknown,
+ TQueryKey extends Array<unknown> = unknown[],
+>(
+ {
+ limit,
+ offset,
+ orderBy,
+ tagNamePattern,
+ }: {
+ limit?: number;
+ offset?: number;
+ orderBy?: string;
+ tagNamePattern?: string;
+ } = {},
+ queryKey?: TQueryKey,
+ options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+ useQuery<TData, TError>({
+ queryKey: Common.UseDagServiceGetDagTagsKeyFn(
+ { limit, offset, orderBy, tagNamePattern },
+ queryKey,
+ ),
+ queryFn: () =>
+ DagService.getDagTags({
+ limit,
+ offset,
+ orderBy,
+ tagNamePattern,
+ }) as TData,
+ ...options,
+ });
/**
* Get Event Log
* @param data The data for the request.
diff --git a/airflow/ui/openapi-gen/queries/suspense.ts
b/airflow/ui/openapi-gen/queries/suspense.ts
index e1349743f2b..250b2038a8c 100644
--- a/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow/ui/openapi-gen/queries/suspense.ts
@@ -1021,50 +1021,6 @@ export const useDagServiceGetDagsSuspense = <
}) as TData,
...options,
});
-/**
- * Get Dag Tags
- * Get all DAG tags.
- * @param data The data for the request.
- * @param data.limit
- * @param data.offset
- * @param data.orderBy
- * @param data.tagNamePattern
- * @returns DAGTagCollectionResponse Successful Response
- * @throws ApiError
- */
-export const useDagServiceGetDagTagsSuspense = <
- TData = Common.DagServiceGetDagTagsDefaultResponse,
- TError = unknown,
- TQueryKey extends Array<unknown> = unknown[],
->(
- {
- limit,
- offset,
- orderBy,
- tagNamePattern,
- }: {
- limit?: number;
- offset?: number;
- orderBy?: string;
- tagNamePattern?: string;
- } = {},
- queryKey?: TQueryKey,
- options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
-) =>
- useSuspenseQuery<TData, TError>({
- queryKey: Common.UseDagServiceGetDagTagsKeyFn(
- { limit, offset, orderBy, tagNamePattern },
- queryKey,
- ),
- queryFn: () =>
- DagService.getDagTags({
- limit,
- offset,
- orderBy,
- tagNamePattern,
- }) as TData,
- ...options,
- });
/**
* Get Dag
* Get basic information about a DAG.
@@ -1117,6 +1073,50 @@ export const useDagServiceGetDagDetailsSuspense = <
queryFn: () => DagService.getDagDetails({ dagId }) as TData,
...options,
});
+/**
+ * Get Dag Tags
+ * Get all DAG tags.
+ * @param data The data for the request.
+ * @param data.limit
+ * @param data.offset
+ * @param data.orderBy
+ * @param data.tagNamePattern
+ * @returns DAGTagCollectionResponse Successful Response
+ * @throws ApiError
+ */
+export const useDagServiceGetDagTagsSuspense = <
+ TData = Common.DagServiceGetDagTagsDefaultResponse,
+ TError = unknown,
+ TQueryKey extends Array<unknown> = unknown[],
+>(
+ {
+ limit,
+ offset,
+ orderBy,
+ tagNamePattern,
+ }: {
+ limit?: number;
+ offset?: number;
+ orderBy?: string;
+ tagNamePattern?: string;
+ } = {},
+ queryKey?: TQueryKey,
+ options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+ useSuspenseQuery<TData, TError>({
+ queryKey: Common.UseDagServiceGetDagTagsKeyFn(
+ { limit, offset, orderBy, tagNamePattern },
+ queryKey,
+ ),
+ queryFn: () =>
+ DagService.getDagTags({
+ limit,
+ offset,
+ orderBy,
+ tagNamePattern,
+ }) as TData,
+ ...options,
+ });
/**
* Get Event Log
* @param data The data for the request.
diff --git a/airflow/ui/openapi-gen/requests/services.gen.ts
b/airflow/ui/openapi-gen/requests/services.gen.ts
index b4952122c0e..4bec27ae465 100644
--- a/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -88,8 +88,6 @@ import type {
GetDagsResponse,
PatchDagsData,
PatchDagsResponse,
- GetDagTagsData,
- GetDagTagsResponse,
GetDagData,
GetDagResponse,
PatchDagData,
@@ -98,6 +96,8 @@ import type {
DeleteDagResponse,
GetDagDetailsData,
GetDagDetailsResponse,
+ GetDagTagsData,
+ GetDagTagsResponse,
GetEventLogData,
GetEventLogResponse,
GetEventLogsData,
@@ -1511,37 +1511,6 @@ export class DagService {
});
}
- /**
- * Get Dag Tags
- * Get all DAG tags.
- * @param data The data for the request.
- * @param data.limit
- * @param data.offset
- * @param data.orderBy
- * @param data.tagNamePattern
- * @returns DAGTagCollectionResponse Successful Response
- * @throws ApiError
- */
- public static getDagTags(
- data: GetDagTagsData = {},
- ): CancelablePromise<GetDagTagsResponse> {
- return __request(OpenAPI, {
- method: "GET",
- url: "/public/dags/tags",
- query: {
- limit: data.limit,
- offset: data.offset,
- order_by: data.orderBy,
- tag_name_pattern: data.tagNamePattern,
- },
- errors: {
- 401: "Unauthorized",
- 403: "Forbidden",
- 422: "Validation Error",
- },
- });
- }
-
/**
* Get Dag
* Get basic information about a DAG.
@@ -1654,6 +1623,37 @@ export class DagService {
},
});
}
+
+ /**
+ * Get Dag Tags
+ * Get all DAG tags.
+ * @param data The data for the request.
+ * @param data.limit
+ * @param data.offset
+ * @param data.orderBy
+ * @param data.tagNamePattern
+ * @returns DAGTagCollectionResponse Successful Response
+ * @throws ApiError
+ */
+ public static getDagTags(
+ data: GetDagTagsData = {},
+ ): CancelablePromise<GetDagTagsResponse> {
+ return __request(OpenAPI, {
+ method: "GET",
+ url: "/public/dagTags",
+ query: {
+ limit: data.limit,
+ offset: data.offset,
+ order_by: data.orderBy,
+ tag_name_pattern: data.tagNamePattern,
+ },
+ errors: {
+ 401: "Unauthorized",
+ 403: "Forbidden",
+ 422: "Validation Error",
+ },
+ });
+ }
}
export class EventLogService {
diff --git a/airflow/ui/openapi-gen/requests/types.gen.ts
b/airflow/ui/openapi-gen/requests/types.gen.ts
index 9fc97c3adeb..e5aed24a98c 100644
--- a/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -1636,15 +1636,6 @@ export type PatchDagsData = {
export type PatchDagsResponse = DAGCollectionResponse;
-export type GetDagTagsData = {
- limit?: number;
- offset?: number;
- orderBy?: string;
- tagNamePattern?: string | null;
-};
-
-export type GetDagTagsResponse = DAGTagCollectionResponse;
-
export type GetDagData = {
dagId: string;
};
@@ -1671,6 +1662,15 @@ export type GetDagDetailsData = {
export type GetDagDetailsResponse = DAGDetailsResponse;
+export type GetDagTagsData = {
+ limit?: number;
+ offset?: number;
+ orderBy?: string;
+ tagNamePattern?: string | null;
+};
+
+export type GetDagTagsResponse = DAGTagCollectionResponse;
+
export type GetEventLogData = {
eventLogId: number;
};
@@ -3188,29 +3188,6 @@ export type $OpenApiTs = {
};
};
};
- "/public/dags/tags": {
- get: {
- req: GetDagTagsData;
- res: {
- /**
- * Successful Response
- */
- 200: DAGTagCollectionResponse;
- /**
- * Unauthorized
- */
- 401: HTTPExceptionResponse;
- /**
- * Forbidden
- */
- 403: HTTPExceptionResponse;
- /**
- * Validation Error
- */
- 422: HTTPValidationError;
- };
- };
- };
"/public/dags/{dag_id}": {
get: {
req: GetDagData;
@@ -3331,6 +3308,29 @@ export type $OpenApiTs = {
};
};
};
+ "/public/dagTags": {
+ get: {
+ req: GetDagTagsData;
+ res: {
+ /**
+ * Successful Response
+ */
+ 200: DAGTagCollectionResponse;
+ /**
+ * Unauthorized
+ */
+ 401: HTTPExceptionResponse;
+ /**
+ * Forbidden
+ */
+ 403: HTTPExceptionResponse;
+ /**
+ * Validation Error
+ */
+ 422: HTTPValidationError;
+ };
+ };
+ };
"/public/eventLogs/{event_log_id}": {
get: {
req: GetEventLogData;
diff --git a/tests/api_fastapi/core_api/routes/public/test_dag_tags.py
b/tests/api_fastapi/core_api/routes/public/test_dag_tags.py
new file mode 100644
index 00000000000..784bb480c43
--- /dev/null
+++ b/tests/api_fastapi/core_api/routes/public/test_dag_tags.py
@@ -0,0 +1,250 @@
+# 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
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from datetime import datetime, timezone
+
+import pendulum
+import pytest
+
+from airflow.models.dag import DagModel, DagTag
+from airflow.models.dagrun import DagRun
+from airflow.operators.empty import EmptyOperator
+from airflow.utils.session import provide_session
+from airflow.utils.state import DagRunState
+from airflow.utils.types import DagRunTriggeredByType, DagRunType
+
+from tests_common.test_utils.db import clear_db_dags, clear_db_runs,
clear_db_serialized_dags
+
+pytestmark = pytest.mark.db_test
+
+DAG1_ID = "test_dag1"
+DAG1_DISPLAY_NAME = "display1"
+DAG2_ID = "test_dag2"
+DAG2_START_DATE = datetime(2021, 6, 15, tzinfo=timezone.utc)
+DAG3_ID = "test_dag3"
+DAG4_ID = "test_dag4"
+DAG4_DISPLAY_NAME = "display4"
+DAG5_ID = "test_dag5"
+DAG5_DISPLAY_NAME = "display5"
+TASK_ID = "op1"
+UTC_JSON_REPR = "UTC" if pendulum.__version__.startswith("3") else
"Timezone('UTC')"
+API_PREFIX = "/public/dags"
+
+
+class TestDagEndpoint:
+ """Common class for /public/dags related unit tests."""
+
+ @staticmethod
+ def _clear_db():
+ clear_db_runs()
+ clear_db_dags()
+ clear_db_serialized_dags()
+
+ def _create_deactivated_paused_dag(self, session=None):
+ dag_model = DagModel(
+ dag_id=DAG3_ID,
+ fileloc="/tmp/dag_del_1.py",
+ timetable_summary="2 2 * * *",
+ is_active=False,
+ is_paused=True,
+ owners="test_owner,another_test_owner",
+ next_dagrun=datetime(2021, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
+ )
+
+ dagrun_failed = DagRun(
+ dag_id=DAG3_ID,
+ run_id="run1",
+ logical_date=datetime(2018, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
+ start_date=datetime(2018, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
+ run_type=DagRunType.SCHEDULED,
+ state=DagRunState.FAILED,
+ triggered_by=DagRunTriggeredByType.TEST,
+ )
+
+ dagrun_success = DagRun(
+ dag_id=DAG3_ID,
+ run_id="run2",
+ logical_date=datetime(2019, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
+ start_date=datetime(2019, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
+ run_type=DagRunType.MANUAL,
+ state=DagRunState.SUCCESS,
+ triggered_by=DagRunTriggeredByType.TEST,
+ )
+
+ session.add(dag_model)
+ session.add(dagrun_failed)
+ session.add(dagrun_success)
+
+ def _create_dag_tags(self, session=None):
+ session.add(DagTag(dag_id=DAG1_ID, name="tag_2"))
+ session.add(DagTag(dag_id=DAG2_ID, name="tag_1"))
+ session.add(DagTag(dag_id=DAG3_ID, name="tag_1"))
+
+ @pytest.fixture(autouse=True)
+ @provide_session
+ def setup(self, dag_maker, session=None) -> None:
+ self._clear_db()
+
+ with dag_maker(
+ DAG1_ID,
+ dag_display_name=DAG1_DISPLAY_NAME,
+ schedule=None,
+ start_date=datetime(2018, 6, 15, 0, 0, tzinfo=timezone.utc),
+ doc_md="details",
+ params={"foo": 1},
+ tags=["example"],
+ ):
+ EmptyOperator(task_id=TASK_ID)
+
+ dag_maker.create_dagrun(state=DagRunState.FAILED)
+
+ with dag_maker(
+ DAG2_ID,
+ schedule=None,
+ start_date=DAG2_START_DATE,
+ doc_md="details",
+ params={"foo": 1},
+ max_active_tasks=16,
+ max_active_runs=16,
+ ):
+ EmptyOperator(task_id=TASK_ID)
+
+ self._create_deactivated_paused_dag(session)
+ self._create_dag_tags(session)
+
+ dag_maker.dagbag.sync_to_db()
+ dag_maker.dag_model.has_task_concurrency_limits = True
+ session.merge(dag_maker.dag_model)
+ session.commit()
+
+ def teardown_method(self) -> None:
+ self._clear_db()
+
+
+class TestDagTags(TestDagEndpoint):
+ """Unit tests for Get DAG Tags."""
+
+ @pytest.mark.parametrize(
+ "query_params, expected_status_code, expected_dag_tags,
expected_total_entries",
+ [
+ # test with offset, limit, and without any tag_name_pattern
+ (
+ {},
+ 200,
+ [
+ "example",
+ "tag_1",
+ "tag_2",
+ ],
+ 3,
+ ),
+ (
+ {"offset": 1},
+ 200,
+ [
+ "tag_1",
+ "tag_2",
+ ],
+ 3,
+ ),
+ (
+ {"limit": 2},
+ 200,
+ [
+ "example",
+ "tag_1",
+ ],
+ 3,
+ ),
+ (
+ {"offset": 1, "limit": 2},
+ 200,
+ [
+ "tag_1",
+ "tag_2",
+ ],
+ 3,
+ ),
+ # test with tag_name_pattern
+ (
+ {"tag_name_pattern": "invalid"},
+ 200,
+ [],
+ 0,
+ ),
+ (
+ {"tag_name_pattern": "1"},
+ 200,
+ ["tag_1"],
+ 1,
+ ),
+ (
+ {"tag_name_pattern": "tag%"},
+ 200,
+ ["tag_1", "tag_2"],
+ 2,
+ ),
+ # test order_by
+ (
+ {"order_by": "-name"},
+ 200,
+ ["tag_2", "tag_1", "example"],
+ 3,
+ ),
+ # test all query params
+ (
+ {"tag_name_pattern": "t%", "order_by": "-name", "offset": 1,
"limit": 1},
+ 200,
+ ["tag_1"],
+ 2,
+ ),
+ (
+ {"tag_name_pattern": "~", "offset": 1, "limit": 2},
+ 200,
+ ["tag_1", "tag_2"],
+ 3,
+ ),
+ # test invalid query params
+ (
+ {"order_by": "dag_id"},
+ 400,
+ None,
+ None,
+ ),
+ (
+ {"order_by": "-dag_id"},
+ 400,
+ None,
+ None,
+ ),
+ ],
+ )
+ def test_get_dag_tags(
+ self, test_client, query_params, expected_status_code,
expected_dag_tags, expected_total_entries
+ ):
+ response = test_client.get("/public/dagTags", params=query_params)
+ assert response.status_code == expected_status_code
+ if expected_status_code != 200:
+ return
+
+ res_json = response.json()
+ expected = {
+ "tags": expected_dag_tags,
+ "total_entries": expected_total_entries,
+ }
+ assert res_json == expected
diff --git a/tests/api_fastapi/core_api/routes/public/test_dags.py
b/tests/api_fastapi/core_api/routes/public/test_dags.py
index daa680b42d1..05a49c44dfe 100644
--- a/tests/api_fastapi/core_api/routes/public/test_dags.py
+++ b/tests/api_fastapi/core_api/routes/public/test_dags.py
@@ -387,120 +387,6 @@ class TestGetDag(TestDagEndpoint):
assert res_json == expected
-class TestGetDagTags(TestDagEndpoint):
- """Unit tests for Get DAG Tags."""
-
- @pytest.mark.parametrize(
- "query_params, expected_status_code, expected_dag_tags,
expected_total_entries",
- [
- # test with offset, limit, and without any tag_name_pattern
- (
- {},
- 200,
- [
- "example",
- "tag_1",
- "tag_2",
- ],
- 3,
- ),
- (
- {"offset": 1},
- 200,
- [
- "tag_1",
- "tag_2",
- ],
- 3,
- ),
- (
- {"limit": 2},
- 200,
- [
- "example",
- "tag_1",
- ],
- 3,
- ),
- (
- {"offset": 1, "limit": 2},
- 200,
- [
- "tag_1",
- "tag_2",
- ],
- 3,
- ),
- # test with tag_name_pattern
- (
- {"tag_name_pattern": "invalid"},
- 200,
- [],
- 0,
- ),
- (
- {"tag_name_pattern": "1"},
- 200,
- ["tag_1"],
- 1,
- ),
- (
- {"tag_name_pattern": "tag%"},
- 200,
- ["tag_1", "tag_2"],
- 2,
- ),
- # test order_by
- (
- {"order_by": "-name"},
- 200,
- ["tag_2", "tag_1", "example"],
- 3,
- ),
- # test all query params
- (
- {"tag_name_pattern": "t%", "order_by": "-name", "offset": 1,
"limit": 1},
- 200,
- ["tag_1"],
- 2,
- ),
- (
- {"tag_name_pattern": "~", "offset": 1, "limit": 2},
- 200,
- ["tag_1", "tag_2"],
- 3,
- ),
- # test invalid query params
- (
- {"order_by": "dag_id"},
- 400,
- None,
- None,
- ),
- (
- {"order_by": "-dag_id"},
- 400,
- None,
- None,
- ),
- ],
- )
- def test_get_dag_tags(
- self, test_client, query_params, expected_status_code,
expected_dag_tags, expected_total_entries
- ):
- response = test_client.get("/public/dags/tags", params=query_params)
- assert response.status_code == expected_status_code
- if expected_status_code != 200:
- return
-
- res_json = response.json()
- expected = {
- "tags": expected_dag_tags,
- "total_entries": expected_total_entries,
- }
- assert res_json == expected
-
-
class TestDeleteDAG(TestDagEndpoint):
"""Unit tests for Delete DAG."""