This is an automated email from the ASF dual-hosted git repository.
jscheffl 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 e9ec8929810 Add run type and triggering user filters to grid view
(#55082)
e9ec8929810 is described below
commit e9ec89298102d2c31bd77f1c2ad1e4e5f2027499
Author: Dheeraj Turaga <[email protected]>
AuthorDate: Mon Sep 8 15:52:25 2025 -0500
Add run type and triggering user filters to grid view (#55082)
* Add run type filter to grid view for DAG run columns
Add a run type filter to the grid view that allows users to filter
DAG run columns by run type (scheduled, manual, backfill,
asset_triggered).
The filter is available in the grid options panel and persists user
selection per DAG using localStorage.
* Fix translations
* Add triggering user filter to grid view
- Add QueryDagRunTriggeringUserSearch parameter to filter by
triggering_user_name
- Update grid API endpoints to accept triggering_user parameter
- Add triggering user search input to grid options panel
- Implement state persistence with localStorage per DAG
- Update OpenAPI generated types and services to support new parameter
- Modify useGridRuns and useGridStructure hooks to pass triggeringUser
- Add English translations for "Triggering User Name" label
- Enable real-time filtering of DAG runs by username
Users can now filter grid view columns to show only runs triggered by
specific users through a search field in the options panel.
* Fix run type filter display showing raw translation keys
- Update Select.ValueText in PanelButtons to use custom render function
- Display translated labels instead of raw keys like
"dags:filters.allRunTypes"
- Add RunTypeIcon integration for selected run types to match DagRuns page
- Properly handle "all" selection with translated "All Run Types" text
- Match visual style and behavior of existing DagRuns.tsx
implementation
* Refactor translation keys to reuse existing common.json entries
* Pierre's Suggestions to use searchbox
* Add tests!
* consolidating test cases with @pytest.mark.parametrize
---
.../src/airflow/api_fastapi/common/parameters.py | 4 +
.../api_fastapi/core_api/openapi/_private_ui.yaml | 40 ++++++++++
.../airflow/api_fastapi/core_api/routes/ui/grid.py | 10 ++-
.../src/airflow/ui/openapi-gen/queries/common.ts | 12 ++-
.../ui/openapi-gen/queries/ensureQueryData.ts | 16 +++-
.../src/airflow/ui/openapi-gen/queries/prefetch.ts | 16 +++-
.../src/airflow/ui/openapi-gen/queries/queries.ts | 16 +++-
.../src/airflow/ui/openapi-gen/queries/suspense.ts | 16 +++-
.../ui/openapi-gen/requests/services.gen.ts | 12 ++-
.../airflow/ui/openapi-gen/requests/types.gen.ts | 10 +++
.../airflow/ui/public/i18n/locales/en/common.json | 3 +-
.../ui/src/layouts/Details/DetailsLayout.tsx | 20 ++++-
.../airflow/ui/src/layouts/Details/Grid/Grid.tsx | 10 ++-
.../ui/src/layouts/Details/PanelButtons.tsx | 86 ++++++++++++++++++++++
.../src/airflow/ui/src/queries/useGridRuns.ts | 13 +++-
.../src/airflow/ui/src/queries/useGridStructure.ts | 7 ++
.../api_fastapi/core_api/routes/ui/test_grid.py | 37 ++++++++++
17 files changed, 297 insertions(+), 31 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/common/parameters.py
b/airflow-core/src/airflow/api_fastapi/common/parameters.py
index cae6bdac0f0..f1d876f29b6 100644
--- a/airflow-core/src/airflow/api_fastapi/common/parameters.py
+++ b/airflow-core/src/airflow/api_fastapi/common/parameters.py
@@ -797,6 +797,10 @@ QueryDagRunRunTypesFilter = Annotated[
),
]
+QueryDagRunTriggeringUserSearch = Annotated[
+ _SearchParam, Depends(search_param_factory(DagRun.triggering_user_name,
"triggering_user"))
+]
+
# DagTags
QueryDagTagPatternSearch = Annotated[
_SearchParam, Depends(search_param_factory(DagTag.name,
"tag_name_pattern"))
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
index d4e9553459f..a4d85b16dd3 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
+++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
@@ -664,6 +664,26 @@ paths:
format: date-time
- type: 'null'
title: Run After Lt
+ - name: run_type
+ in: query
+ required: false
+ schema:
+ type: array
+ items:
+ type: string
+ title: Run Type
+ - name: triggering_user
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ - type: 'null'
+ description: "SQL LIKE expression \u2014 use `%` / `_` wildcards
(e.g. `%customer_%`).\
+ \ Regular expressions are **not** supported."
+ title: Triggering User
+ description: "SQL LIKE expression \u2014 use `%` / `_` wildcards (e.g.
`%customer_%`).\
+ \ Regular expressions are **not** supported."
responses:
'200':
description: Successful Response
@@ -771,6 +791,26 @@ paths:
format: date-time
- type: 'null'
title: Run After Lt
+ - name: run_type
+ in: query
+ required: false
+ schema:
+ type: array
+ items:
+ type: string
+ title: Run Type
+ - name: triggering_user
+ in: query
+ required: false
+ schema:
+ anyOf:
+ - type: string
+ - type: 'null'
+ description: "SQL LIKE expression \u2014 use `%` / `_` wildcards
(e.g. `%customer_%`).\
+ \ Regular expressions are **not** supported."
+ title: Triggering User
+ description: "SQL LIKE expression \u2014 use `%` / `_` wildcards (e.g.
`%customer_%`).\
+ \ Regular expressions are **not** supported."
responses:
'200':
description: Successful Response
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
index 0f9f4023cbc..7ff49ac27c1 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
@@ -27,6 +27,8 @@ from sqlalchemy import select
from airflow.api_fastapi.auth.managers.models.resource_details import
DagAccessEntity
from airflow.api_fastapi.common.db.common import SessionDep, paginated_select
from airflow.api_fastapi.common.parameters import (
+ QueryDagRunRunTypesFilter,
+ QueryDagRunTriggeringUserSearch,
QueryLimit,
QueryOffset,
RangeFilter,
@@ -118,6 +120,8 @@ def get_dag_structure(
Depends(SortParam(["run_after", "logical_date", "start_date",
"end_date"], DagRun).dynamic_depends()),
],
run_after: Annotated[RangeFilter,
Depends(datetime_range_filter_factory("run_after", DagRun))],
+ run_type: QueryDagRunRunTypesFilter,
+ triggering_user: QueryDagRunTriggeringUserSearch,
) -> list[GridNodeResponse]:
"""Return dag structure for grid view."""
latest_serdag = _get_latest_serdag(dag_id, session)
@@ -136,7 +140,7 @@ def get_dag_structure(
statement=base_query,
order_by=order_by,
offset=offset,
- filters=[run_after],
+ filters=[run_after, run_type, triggering_user],
limit=limit,
)
run_ids = list(session.scalars(dag_runs_select_filter))
@@ -214,6 +218,8 @@ def get_grid_runs(
),
],
run_after: Annotated[RangeFilter,
Depends(datetime_range_filter_factory("run_after", DagRun))],
+ run_type: QueryDagRunRunTypesFilter,
+ triggering_user: QueryDagRunTriggeringUserSearch,
) -> list[GridRunsResponse]:
"""Get info about a run for the grid."""
# Retrieve, sort the previous DAG Runs
@@ -241,7 +247,7 @@ def get_grid_runs(
statement=base_query,
order_by=order_by,
offset=offset,
- filters=[run_after],
+ filters=[run_after, run_type, triggering_user],
limit=limit,
)
return session.execute(dag_runs_select_filter)
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
index 6322b959a38..457aac893ee 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -794,7 +794,7 @@ export const UseStructureServiceStructureDataKeyFn = ({
dagId, externalDependenc
export type GridServiceGetDagStructureDefaultResponse =
Awaited<ReturnType<typeof GridService.getDagStructure>>;
export type GridServiceGetDagStructureQueryResult<TData =
GridServiceGetDagStructureDefaultResponse, TError = unknown> =
UseQueryResult<TData, TError>;
export const useGridServiceGetDagStructureKey = "GridServiceGetDagStructure";
-export const UseGridServiceGetDagStructureKeyFn = ({ dagId, limit, offset,
orderBy, runAfterGt, runAfterGte, runAfterLt, runAfterLte }: {
+export const UseGridServiceGetDagStructureKeyFn = ({ dagId, limit, offset,
orderBy, runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType,
triggeringUser }: {
dagId: string;
limit?: number;
offset?: number;
@@ -803,11 +803,13 @@ export const UseGridServiceGetDagStructureKeyFn = ({
dagId, limit, offset, order
runAfterGte?: string;
runAfterLt?: string;
runAfterLte?: string;
-}, queryKey?: Array<unknown>) => [useGridServiceGetDagStructureKey,
...(queryKey ?? [{ dagId, limit, offset, orderBy, runAfterGt, runAfterGte,
runAfterLt, runAfterLte }])];
+ runType?: string[];
+ triggeringUser?: string;
+}, queryKey?: Array<unknown>) => [useGridServiceGetDagStructureKey,
...(queryKey ?? [{ dagId, limit, offset, orderBy, runAfterGt, runAfterGte,
runAfterLt, runAfterLte, runType, triggeringUser }])];
export type GridServiceGetGridRunsDefaultResponse = Awaited<ReturnType<typeof
GridService.getGridRuns>>;
export type GridServiceGetGridRunsQueryResult<TData =
GridServiceGetGridRunsDefaultResponse, TError = unknown> =
UseQueryResult<TData, TError>;
export const useGridServiceGetGridRunsKey = "GridServiceGetGridRuns";
-export const UseGridServiceGetGridRunsKeyFn = ({ dagId, limit, offset,
orderBy, runAfterGt, runAfterGte, runAfterLt, runAfterLte }: {
+export const UseGridServiceGetGridRunsKeyFn = ({ dagId, limit, offset,
orderBy, runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType,
triggeringUser }: {
dagId: string;
limit?: number;
offset?: number;
@@ -816,7 +818,9 @@ export const UseGridServiceGetGridRunsKeyFn = ({ dagId,
limit, offset, orderBy,
runAfterGte?: string;
runAfterLt?: string;
runAfterLte?: string;
-}, queryKey?: Array<unknown>) => [useGridServiceGetGridRunsKey, ...(queryKey
?? [{ dagId, limit, offset, orderBy, runAfterGt, runAfterGte, runAfterLt,
runAfterLte }])];
+ runType?: string[];
+ triggeringUser?: string;
+}, queryKey?: Array<unknown>) => [useGridServiceGetGridRunsKey, ...(queryKey
?? [{ dagId, limit, offset, orderBy, runAfterGt, runAfterGte, runAfterLt,
runAfterLte, runType, triggeringUser }])];
export type GridServiceGetGridTiSummariesDefaultResponse =
Awaited<ReturnType<typeof GridService.getGridTiSummaries>>;
export type GridServiceGetGridTiSummariesQueryResult<TData =
GridServiceGetGridTiSummariesDefaultResponse, TError = unknown> =
UseQueryResult<TData, TError>;
export const useGridServiceGetGridTiSummariesKey =
"GridServiceGetGridTiSummaries";
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
index 1f3e5dc4e3e..75803561cfa 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -1510,10 +1510,12 @@ export const ensureUseStructureServiceStructureDataData
= (queryClient: QueryCli
* @param data.runAfterGt
* @param data.runAfterLte
* @param data.runAfterLt
+* @param data.runType
+* @param data.triggeringUser SQL LIKE expression — use `%` / `_` wildcards
(e.g. `%customer_%`). Regular expressions are **not** supported.
* @returns GridNodeResponse Successful Response
* @throws ApiError
*/
-export const ensureUseGridServiceGetDagStructureData = (queryClient:
QueryClient, { dagId, limit, offset, orderBy, runAfterGt, runAfterGte,
runAfterLt, runAfterLte }: {
+export const ensureUseGridServiceGetDagStructureData = (queryClient:
QueryClient, { dagId, limit, offset, orderBy, runAfterGt, runAfterGte,
runAfterLt, runAfterLte, runType, triggeringUser }: {
dagId: string;
limit?: number;
offset?: number;
@@ -1522,7 +1524,9 @@ export const ensureUseGridServiceGetDagStructureData =
(queryClient: QueryClient
runAfterGte?: string;
runAfterLt?: string;
runAfterLte?: string;
-}) => queryClient.ensureQueryData({ queryKey:
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }), queryFn: () =>
GridService.getDagStructure({ dagId, limit, offset, orderBy, runAfterGt,
runAfterGte, runAfterLt, runAfterLte }) });
+ runType?: string[];
+ triggeringUser?: string;
+}) => queryClient.ensureQueryData({ queryKey:
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }),
queryFn: () => GridService.getDagStructure({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }) });
/**
* Get Grid Runs
* Get info about a run for the grid.
@@ -1535,10 +1539,12 @@ export const ensureUseGridServiceGetDagStructureData =
(queryClient: QueryClient
* @param data.runAfterGt
* @param data.runAfterLte
* @param data.runAfterLt
+* @param data.runType
+* @param data.triggeringUser SQL LIKE expression — use `%` / `_` wildcards
(e.g. `%customer_%`). Regular expressions are **not** supported.
* @returns GridRunsResponse Successful Response
* @throws ApiError
*/
-export const ensureUseGridServiceGetGridRunsData = (queryClient: QueryClient,
{ dagId, limit, offset, orderBy, runAfterGt, runAfterGte, runAfterLt,
runAfterLte }: {
+export const ensureUseGridServiceGetGridRunsData = (queryClient: QueryClient,
{ dagId, limit, offset, orderBy, runAfterGt, runAfterGte, runAfterLt,
runAfterLte, runType, triggeringUser }: {
dagId: string;
limit?: number;
offset?: number;
@@ -1547,7 +1553,9 @@ export const ensureUseGridServiceGetGridRunsData =
(queryClient: QueryClient, {
runAfterGte?: string;
runAfterLt?: string;
runAfterLte?: string;
-}) => queryClient.ensureQueryData({ queryKey:
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }), queryFn: () =>
GridService.getGridRuns({ dagId, limit, offset, orderBy, runAfterGt,
runAfterGte, runAfterLt, runAfterLte }) });
+ runType?: string[];
+ triggeringUser?: string;
+}) => queryClient.ensureQueryData({ queryKey:
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }),
queryFn: () => GridService.getGridRuns({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }) });
/**
* Get Grid Ti Summaries
* Get states for TIs / "groups" of TIs.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
index 304b6e37d17..180eb826210 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -1510,10 +1510,12 @@ export const prefetchUseStructureServiceStructureData =
(queryClient: QueryClien
* @param data.runAfterGt
* @param data.runAfterLte
* @param data.runAfterLt
+* @param data.runType
+* @param data.triggeringUser SQL LIKE expression — use `%` / `_` wildcards
(e.g. `%customer_%`). Regular expressions are **not** supported.
* @returns GridNodeResponse Successful Response
* @throws ApiError
*/
-export const prefetchUseGridServiceGetDagStructure = (queryClient:
QueryClient, { dagId, limit, offset, orderBy, runAfterGt, runAfterGte,
runAfterLt, runAfterLte }: {
+export const prefetchUseGridServiceGetDagStructure = (queryClient:
QueryClient, { dagId, limit, offset, orderBy, runAfterGt, runAfterGte,
runAfterLt, runAfterLte, runType, triggeringUser }: {
dagId: string;
limit?: number;
offset?: number;
@@ -1522,7 +1524,9 @@ export const prefetchUseGridServiceGetDagStructure =
(queryClient: QueryClient,
runAfterGte?: string;
runAfterLt?: string;
runAfterLte?: string;
-}) => queryClient.prefetchQuery({ queryKey:
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }), queryFn: () =>
GridService.getDagStructure({ dagId, limit, offset, orderBy, runAfterGt,
runAfterGte, runAfterLt, runAfterLte }) });
+ runType?: string[];
+ triggeringUser?: string;
+}) => queryClient.prefetchQuery({ queryKey:
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }),
queryFn: () => GridService.getDagStructure({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }) });
/**
* Get Grid Runs
* Get info about a run for the grid.
@@ -1535,10 +1539,12 @@ export const prefetchUseGridServiceGetDagStructure =
(queryClient: QueryClient,
* @param data.runAfterGt
* @param data.runAfterLte
* @param data.runAfterLt
+* @param data.runType
+* @param data.triggeringUser SQL LIKE expression — use `%` / `_` wildcards
(e.g. `%customer_%`). Regular expressions are **not** supported.
* @returns GridRunsResponse Successful Response
* @throws ApiError
*/
-export const prefetchUseGridServiceGetGridRuns = (queryClient: QueryClient, {
dagId, limit, offset, orderBy, runAfterGt, runAfterGte, runAfterLt, runAfterLte
}: {
+export const prefetchUseGridServiceGetGridRuns = (queryClient: QueryClient, {
dagId, limit, offset, orderBy, runAfterGt, runAfterGte, runAfterLt,
runAfterLte, runType, triggeringUser }: {
dagId: string;
limit?: number;
offset?: number;
@@ -1547,7 +1553,9 @@ export const prefetchUseGridServiceGetGridRuns =
(queryClient: QueryClient, { da
runAfterGte?: string;
runAfterLt?: string;
runAfterLte?: string;
-}) => queryClient.prefetchQuery({ queryKey:
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }), queryFn: () =>
GridService.getGridRuns({ dagId, limit, offset, orderBy, runAfterGt,
runAfterGte, runAfterLt, runAfterLte }) });
+ runType?: string[];
+ triggeringUser?: string;
+}) => queryClient.prefetchQuery({ queryKey:
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }),
queryFn: () => GridService.getGridRuns({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }) });
/**
* Get Grid Ti Summaries
* Get states for TIs / "groups" of TIs.
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 f727dcdbd4f..1b133994fec 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -1510,10 +1510,12 @@ export const useStructureServiceStructureData = <TData
= Common.StructureService
* @param data.runAfterGt
* @param data.runAfterLte
* @param data.runAfterLt
+* @param data.runType
+* @param data.triggeringUser SQL LIKE expression — use `%` / `_` wildcards
(e.g. `%customer_%`). Regular expressions are **not** supported.
* @returns GridNodeResponse Successful Response
* @throws ApiError
*/
-export const useGridServiceGetDagStructure = <TData =
Common.GridServiceGetDagStructureDefaultResponse, TError = unknown, TQueryKey
extends Array<unknown> = unknown[]>({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }: {
+export const useGridServiceGetDagStructure = <TData =
Common.GridServiceGetDagStructureDefaultResponse, TError = unknown, TQueryKey
extends Array<unknown> = unknown[]>({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }: {
dagId: string;
limit?: number;
offset?: number;
@@ -1522,7 +1524,9 @@ export const useGridServiceGetDagStructure = <TData =
Common.GridServiceGetDagSt
runAfterGte?: string;
runAfterLt?: string;
runAfterLte?: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey:
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }, queryKey), queryFn: () =>
GridService.getDagStructure({ dagId, limit, offset, orderBy, runAfterGt,
runAfterGte, runAfterLt, runAfterLte }) as TData, ...options });
+ runType?: string[];
+ triggeringUser?: string;
+}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey:
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser },
queryKey), queryFn: () => GridService.getDagStructure({ dagId, limit, offset,
orderBy, runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType,
triggeringUser }) as TData, ...options });
/**
* Get Grid Runs
* Get info about a run for the grid.
@@ -1535,10 +1539,12 @@ export const useGridServiceGetDagStructure = <TData =
Common.GridServiceGetDagSt
* @param data.runAfterGt
* @param data.runAfterLte
* @param data.runAfterLt
+* @param data.runType
+* @param data.triggeringUser SQL LIKE expression — use `%` / `_` wildcards
(e.g. `%customer_%`). Regular expressions are **not** supported.
* @returns GridRunsResponse Successful Response
* @throws ApiError
*/
-export const useGridServiceGetGridRuns = <TData =
Common.GridServiceGetGridRunsDefaultResponse, TError = unknown, TQueryKey
extends Array<unknown> = unknown[]>({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }: {
+export const useGridServiceGetGridRuns = <TData =
Common.GridServiceGetGridRunsDefaultResponse, TError = unknown, TQueryKey
extends Array<unknown> = unknown[]>({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }: {
dagId: string;
limit?: number;
offset?: number;
@@ -1547,7 +1553,9 @@ export const useGridServiceGetGridRuns = <TData =
Common.GridServiceGetGridRunsD
runAfterGte?: string;
runAfterLt?: string;
runAfterLte?: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey:
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }, queryKey), queryFn: () =>
GridService.getGridRuns({ dagId, limit, offset, orderBy, runAfterGt,
runAfterGte, runAfterLt, runAfterLte }) as TData, ...options });
+ runType?: string[];
+ triggeringUser?: string;
+}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey:
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser },
queryKey), queryFn: () => GridService.getGridRuns({ dagId, limit, offset,
orderBy, runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType,
triggeringUser }) as TData, ...options });
/**
* Get Grid Ti Summaries
* Get states for TIs / "groups" of TIs.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
index 35d89616172..45fa755cb8d 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -1510,10 +1510,12 @@ export const useStructureServiceStructureDataSuspense =
<TData = Common.Structur
* @param data.runAfterGt
* @param data.runAfterLte
* @param data.runAfterLt
+* @param data.runType
+* @param data.triggeringUser SQL LIKE expression — use `%` / `_` wildcards
(e.g. `%customer_%`). Regular expressions are **not** supported.
* @returns GridNodeResponse Successful Response
* @throws ApiError
*/
-export const useGridServiceGetDagStructureSuspense = <TData =
Common.GridServiceGetDagStructureDefaultResponse, TError = unknown, TQueryKey
extends Array<unknown> = unknown[]>({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }: {
+export const useGridServiceGetDagStructureSuspense = <TData =
Common.GridServiceGetDagStructureDefaultResponse, TError = unknown, TQueryKey
extends Array<unknown> = unknown[]>({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }: {
dagId: string;
limit?: number;
offset?: number;
@@ -1522,7 +1524,9 @@ export const useGridServiceGetDagStructureSuspense =
<TData = Common.GridService
runAfterGte?: string;
runAfterLt?: string;
runAfterLte?: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey:
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }, queryKey), queryFn: () =>
GridService.getDagStructure({ dagId, limit, offset, orderBy, runAfterGt,
runAfterGte, runAfterLt, runAfterLte }) as TData, ...options });
+ runType?: string[];
+ triggeringUser?: string;
+}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey:
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser },
queryKey), queryFn: () => GridService.getDagStructure({ dagId, limit, offset,
orderBy, runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType,
triggeringUser }) as TData, ...options });
/**
* Get Grid Runs
* Get info about a run for the grid.
@@ -1535,10 +1539,12 @@ export const useGridServiceGetDagStructureSuspense =
<TData = Common.GridService
* @param data.runAfterGt
* @param data.runAfterLte
* @param data.runAfterLt
+* @param data.runType
+* @param data.triggeringUser SQL LIKE expression — use `%` / `_` wildcards
(e.g. `%customer_%`). Regular expressions are **not** supported.
* @returns GridRunsResponse Successful Response
* @throws ApiError
*/
-export const useGridServiceGetGridRunsSuspense = <TData =
Common.GridServiceGetGridRunsDefaultResponse, TError = unknown, TQueryKey
extends Array<unknown> = unknown[]>({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }: {
+export const useGridServiceGetGridRunsSuspense = <TData =
Common.GridServiceGetGridRunsDefaultResponse, TError = unknown, TQueryKey
extends Array<unknown> = unknown[]>({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser }: {
dagId: string;
limit?: number;
offset?: number;
@@ -1547,7 +1553,9 @@ export const useGridServiceGetGridRunsSuspense = <TData =
Common.GridServiceGetG
runAfterGte?: string;
runAfterLt?: string;
runAfterLte?: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey:
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte }, queryKey), queryFn: () =>
GridService.getGridRuns({ dagId, limit, offset, orderBy, runAfterGt,
runAfterGte, runAfterLt, runAfterLte }) as TData, ...options });
+ runType?: string[];
+ triggeringUser?: string;
+}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey:
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy,
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType, triggeringUser },
queryKey), queryFn: () => GridService.getGridRuns({ dagId, limit, offset,
orderBy, runAfterGt, runAfterGte, runAfterLt, runAfterLte, runType,
triggeringUser }) as TData, ...options });
/**
* Get Grid Ti Summaries
* Get states for TIs / "groups" of TIs.
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 99d6bbb7591..1f074a4d5cc 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
@@ -3888,6 +3888,8 @@ export class GridService {
* @param data.runAfterGt
* @param data.runAfterLte
* @param data.runAfterLt
+ * @param data.runType
+ * @param data.triggeringUser SQL LIKE expression — use `%` / `_`
wildcards (e.g. `%customer_%`). Regular expressions are **not** supported.
* @returns GridNodeResponse Successful Response
* @throws ApiError
*/
@@ -3905,7 +3907,9 @@ export class GridService {
run_after_gte: data.runAfterGte,
run_after_gt: data.runAfterGt,
run_after_lte: data.runAfterLte,
- run_after_lt: data.runAfterLt
+ run_after_lt: data.runAfterLt,
+ run_type: data.runType,
+ triggering_user: data.triggeringUser
},
errors: {
400: 'Bad Request',
@@ -3927,6 +3931,8 @@ export class GridService {
* @param data.runAfterGt
* @param data.runAfterLte
* @param data.runAfterLt
+ * @param data.runType
+ * @param data.triggeringUser SQL LIKE expression — use `%` / `_`
wildcards (e.g. `%customer_%`). Regular expressions are **not** supported.
* @returns GridRunsResponse Successful Response
* @throws ApiError
*/
@@ -3944,7 +3950,9 @@ export class GridService {
run_after_gte: data.runAfterGte,
run_after_gt: data.runAfterGt,
run_after_lte: data.runAfterLte,
- run_after_lt: data.runAfterLt
+ run_after_lt: data.runAfterLt,
+ run_type: data.runType,
+ triggering_user: data.triggeringUser
},
errors: {
400: 'Bad Request',
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index b2f31c4dce5..c1320c1c635 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -3176,6 +3176,11 @@ export type GetDagStructureData = {
runAfterGte?: string | null;
runAfterLt?: string | null;
runAfterLte?: string | null;
+ runType?: Array<(string)>;
+ /**
+ * SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`).
Regular expressions are **not** supported.
+ */
+ triggeringUser?: string | null;
};
export type GetDagStructureResponse = Array<GridNodeResponse>;
@@ -3189,6 +3194,11 @@ export type GetGridRunsData = {
runAfterGte?: string | null;
runAfterLt?: string | null;
runAfterLte?: string | null;
+ runType?: Array<(string)>;
+ /**
+ * SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`).
Regular expressions are **not** supported.
+ */
+ triggeringUser?: string | null;
};
export type GetGridRunsResponse = Array<GridRunsResponse>;
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
index ae58ee278e9..defe64cce63 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
@@ -108,7 +108,8 @@
"runAfterFromPlaceholder": "Run After From",
"runAfterToPlaceholder": "Run After To",
"runIdPlaceholder": "Filter by Run ID",
- "taskIdPlaceholder": "Filter by Task ID"
+ "taskIdPlaceholder": "Filter by Task ID",
+ "triggeringUserPlaceholder": "Filter by triggering user"
},
"logicalDate": "Logical Date",
"logout": "Logout",
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
index d79001fa71f..ac20004efe8 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -28,6 +28,7 @@ import { Outlet, useParams } from "react-router-dom";
import { useLocalStorage } from "usehooks-ts";
import { useDagServiceGetDag, useDagWarningServiceListDagWarnings } from
"openapi/queries";
+import type { DagRunType } from "openapi/requests/types.gen";
import BackfillBanner from "src/components/Banner/BackfillBanner";
import { SearchDagsButton } from "src/components/SearchDags";
import TriggerDAGButton from "src/components/TriggerDag/TriggerDAGButton";
@@ -59,6 +60,14 @@ export const DetailsLayout = ({ children, error, isLoading,
tabs }: Props) => {
const panelGroupRef = useRef(null);
const [dagView, setDagView] = useLocalStorage<"graph" |
"grid">(`dag_view-${dagId}`, defaultDagView);
const [limit, setLimit] = useLocalStorage<number>(`dag_runs_limit-${dagId}`,
10);
+ const [runTypeFilter, setRunTypeFilter] = useLocalStorage<DagRunType |
undefined>(
+ `run_type_filter-${dagId}`,
+ undefined,
+ );
+ const [triggeringUserFilter, setTriggeringUserFilter] =
useLocalStorage<string | undefined>(
+ `triggering_user_filter-${dagId}`,
+ undefined,
+ );
const [showGantt, setShowGantt] =
useLocalStorage<boolean>(`show_gantt-${dagId}`, true);
const { fitView, getZoom } = useReactFlow();
@@ -123,16 +132,25 @@ export const DetailsLayout = ({ children, error,
isLoading, tabs }: Props) => {
dagView={dagView}
limit={limit}
panelGroupRef={panelGroupRef}
+ runTypeFilter={runTypeFilter}
setDagView={setDagView}
setLimit={setLimit}
+ setRunTypeFilter={setRunTypeFilter}
setShowGantt={setShowGantt}
+ setTriggeringUserFilter={setTriggeringUserFilter}
showGantt={showGantt}
+ triggeringUserFilter={triggeringUserFilter}
/>
{dagView === "graph" ? (
<Graph />
) : (
<HStack gap={0}>
- <Grid limit={limit} showGantt={Boolean(runId) && showGantt}
/>
+ <Grid
+ limit={limit}
+ runType={runTypeFilter}
+ showGantt={Boolean(runId) && showGantt}
+ triggeringUser={triggeringUserFilter}
+ />
{showGantt ? <Gantt limit={limit} /> : undefined}
</HStack>
)}
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
index 8a56fc47441..dbf48e0d0df 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
@@ -24,7 +24,7 @@ import { useTranslation } from "react-i18next";
import { FiChevronsRight } from "react-icons/fi";
import { Link, useParams } from "react-router-dom";
-import type { GridRunsResponse } from "openapi/requests";
+import type { DagRunType, GridRunsResponse } from "openapi/requests";
import { useOpenGroups } from "src/context/openGroups";
import { useNavigation } from "src/hooks/navigation";
import { useGridRuns } from "src/queries/useGridRuns.ts";
@@ -41,10 +41,12 @@ dayjs.extend(dayjsDuration);
type Props = {
readonly limit: number;
+ readonly runType?: DagRunType | undefined;
readonly showGantt?: boolean;
+ readonly triggeringUser?: string | undefined;
};
-export const Grid = ({ limit, showGantt }: Props) => {
+export const Grid = ({ limit, runType, showGantt, triggeringUser }: Props) => {
const { t: translate } = useTranslation("dag");
const gridRef = useRef<HTMLDivElement>(null);
@@ -53,7 +55,7 @@ export const Grid = ({ limit, showGantt }: Props) => {
const { openGroupIds, toggleGroupId } = useOpenGroups();
const { dagId = "", runId = "" } = useParams();
- const { data: gridRuns, isLoading } = useGridRuns({ limit });
+ const { data: gridRuns, isLoading } = useGridRuns({ limit, runType,
triggeringUser });
// Check if the selected dag run is inside of the grid response, if not,
we'll update the grid filters
// Eventually we should redo the api endpoint to make this work better
@@ -77,7 +79,7 @@ export const Grid = ({ limit, showGantt }: Props) => {
}
}, [gridRuns, setHasActiveRun]);
- const { data: dagStructure } = useGridStructure({ hasActiveRun, limit });
+ const { data: dagStructure } = useGridStructure({ hasActiveRun, limit,
runType, triggeringUser });
// calculate dag run bar heights relative to max
const max = Math.max.apply(
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
b/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
index 34c06f6ab4f..68eff209fe0 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
@@ -40,10 +40,14 @@ import { MdOutlineAccountTree } from "react-icons/md";
import { useParams } from "react-router-dom";
import { useLocalStorage } from "usehooks-ts";
+import type { DagRunType } from "openapi/requests/types.gen";
import { DagVersionSelect } from "src/components/DagVersionSelect";
import { directionOptions, type Direction } from
"src/components/Graph/useGraphLayout";
+import { RunTypeIcon } from "src/components/RunTypeIcon";
+import { SearchBar } from "src/components/SearchBar";
import { Button, Tooltip } from "src/components/ui";
import { Checkbox } from "src/components/ui/Checkbox";
+import { dagRunTypeOptions } from "src/constants/stateOptions";
import { DagRunSelect } from "./DagRunSelect";
import { ToggleGroups } from "./ToggleGroups";
@@ -52,10 +56,14 @@ type Props = {
readonly dagView: string;
readonly limit: number;
readonly panelGroupRef: React.RefObject<{ setLayout?: (layout:
Array<number>) => void } & HTMLDivElement>;
+ readonly runTypeFilter: DagRunType | undefined;
readonly setDagView: (x: "graph" | "grid") => void;
readonly setLimit: React.Dispatch<React.SetStateAction<number>>;
+ readonly setRunTypeFilter: React.Dispatch<React.SetStateAction<DagRunType |
undefined>>;
readonly setShowGantt: React.Dispatch<React.SetStateAction<boolean>>;
+ readonly setTriggeringUserFilter: React.Dispatch<React.SetStateAction<string
| undefined>>;
readonly showGantt: boolean;
+ readonly triggeringUserFilter: string | undefined;
};
const getOptions = (translate: (key: string) => string) =>
@@ -86,10 +94,14 @@ export const PanelButtons = ({
dagView,
limit,
panelGroupRef,
+ runTypeFilter,
setDagView,
setLimit,
+ setRunTypeFilter,
setShowGantt,
+ setTriggeringUserFilter,
showGantt,
+ triggeringUserFilter,
}: Props) => {
const { t: translate } = useTranslation(["components", "dag"]);
const { dagId = "", runId } = useParams();
@@ -122,6 +134,22 @@ export const PanelButtons = ({
}
};
+ const handleRunTypeChange = (event: SelectValueChangeDetails<string>) => {
+ const [val] = event.value;
+
+ if (val === undefined || val === "all") {
+ setRunTypeFilter(undefined);
+ } else {
+ setRunTypeFilter(val as DagRunType);
+ }
+ };
+
+ const handleTriggeringUserChange = (value: string) => {
+ const trimmedValue = value.trim();
+
+ setTriggeringUserFilter(trimmedValue === "" ? undefined : trimmedValue);
+ };
+
const handleFocus = (view: string) => {
if (panelGroupRef.current) {
const panelGroup = panelGroupRef.current;
@@ -292,6 +320,64 @@ export const PanelButtons = ({
</Select.Content>
</Select.Positioner>
</Select.Root>
+ <Select.Root
+ // @ts-expect-error The expected option type is
incorrect
+ collection={dagRunTypeOptions}
+ data-testid="run-type-filter"
+ onValueChange={handleRunTypeChange}
+ size="sm"
+ value={[runTypeFilter ?? "all"]}
+ >
+
<Select.Label>{translate("common:dagRun.runType")}</Select.Label>
+ <Select.Control>
+ <Select.Trigger>
+ <Select.ValueText>
+ {(runTypeFilter ?? "all") === "all" ? (
+ translate("dags:filters.allRunTypes")
+ ) : (
+ <Flex gap={1}>
+ <RunTypeIcon runType={runTypeFilter!} />
+ {translate(
+ dagRunTypeOptions.items.find((item) =>
item.value === runTypeFilter)
+ ?.label ?? "",
+ )}
+ </Flex>
+ )}
+ </Select.ValueText>
+ </Select.Trigger>
+ <Select.IndicatorGroup>
+ <Select.Indicator />
+ </Select.IndicatorGroup>
+ </Select.Control>
+ <Select.Positioner>
+ <Select.Content>
+ {dagRunTypeOptions.items.map((option) => (
+ <Select.Item item={option} key={option.value}>
+ {option.value === "all" ? (
+ translate(option.label)
+ ) : (
+ <Flex gap={1}>
+ <RunTypeIcon runType={option.value as
DagRunType} />
+ {translate(option.label)}
+ </Flex>
+ )}
+ </Select.Item>
+ ))}
+ </Select.Content>
+ </Select.Positioner>
+ </Select.Root>
+ <VStack alignItems="flex-start">
+ <Text fontSize="xs" mb={1}>
+ {translate("common:dagRun.triggeringUser")}
+ </Text>
+ <SearchBar
+ defaultValue={triggeringUserFilter ?? ""}
+ hideAdvanced
+ hotkeyDisabled
+ onChange={handleTriggeringUserChange}
+
placeHolder={translate("common:filters.triggeringUserPlaceholder")}
+ />
+ </VStack>
{shouldShowToggleButtons ? (
<VStack alignItems="flex-start" px={1}>
<Checkbox checked={showGantt} onChange={() =>
setShowGantt(!showGantt)} size="sm">
diff --git a/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
b/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
index 42807a9a7bc..af077b7b1af 100644
--- a/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
@@ -19,9 +19,18 @@
import { useParams } from "react-router-dom";
import { useGridServiceGetGridRuns } from "openapi/queries";
+import type { DagRunType } from "openapi/requests/types.gen";
import { isStatePending, useAutoRefresh } from "src/utils";
-export const useGridRuns = ({ limit }: { limit: number }) => {
+export const useGridRuns = ({
+ limit,
+ runType,
+ triggeringUser,
+}: {
+ limit: number;
+ runType?: DagRunType | undefined;
+ triggeringUser?: string | undefined;
+}) => {
const { dagId = "" } = useParams();
const defaultRefetchInterval = useAutoRefresh({ dagId });
@@ -31,6 +40,8 @@ export const useGridRuns = ({ limit }: { limit: number }) => {
dagId,
limit,
orderBy: ["-run_after"],
+ runType: runType ? [runType] : undefined,
+ triggeringUser: triggeringUser ?? undefined,
},
undefined,
{
diff --git a/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts
b/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts
index 17db10fffd5..f312b028e67 100644
--- a/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts
@@ -19,14 +19,19 @@
import { useParams } from "react-router-dom";
import { useGridServiceGetDagStructure } from "openapi/queries";
+import type { DagRunType } from "openapi/requests/types.gen";
import { useAutoRefresh } from "src/utils";
export const useGridStructure = ({
hasActiveRun = undefined,
limit,
+ runType,
+ triggeringUser,
}: {
hasActiveRun?: boolean;
limit?: number;
+ runType?: DagRunType | undefined;
+ triggeringUser?: string | undefined;
}) => {
const { dagId = "" } = useParams();
const refetchInterval = useAutoRefresh({ dagId });
@@ -37,6 +42,8 @@ export const useGridStructure = ({
dagId,
limit,
orderBy: ["-run_after"],
+ runType: runType ? [runType] : undefined,
+ triggeringUser: triggeringUser ?? undefined,
},
undefined,
{
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
index 271d90c154b..32f8f2683f0 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
@@ -156,6 +156,8 @@ def setup(dag_maker, session=None):
data_interval=data_interval,
**triggered_by_kwargs,
)
+ # Set specific triggering users for testing filtering (only for manual
runs)
+ run_2.triggering_user_name = "user2"
for ti in run_1.task_instances:
ti.state = TaskInstanceState.SUCCESS
for ti in sorted(run_2.task_instances, key=lambda ti: (ti.task_id,
ti.map_index)):
@@ -494,6 +496,41 @@ class TestGetGridDataEndpoint:
},
]
+ @pytest.mark.parametrize(
+ "endpoint,run_type,expected",
+ [
+ ("runs", "scheduled", [GRID_RUN_1]),
+ ("runs", "manual", [GRID_RUN_2]),
+ ("structure", "scheduled", GRID_NODES),
+ ("structure", "manual", GRID_NODES),
+ ],
+ )
+ def test_filter_by_run_type(self, session, test_client, endpoint,
run_type, expected):
+ session.commit()
+ response =
test_client.get(f"/grid/{endpoint}/{DAG_ID}?run_type={run_type}")
+ assert response.status_code == 200
+ assert response.json() == expected
+
+ @pytest.mark.parametrize(
+ "endpoint,triggering_user,expected",
+ [
+ ("runs", "user2", [GRID_RUN_2]),
+ ("runs", "nonexistent", []),
+ ("structure", "user2", GRID_NODES),
+ ],
+ )
+ def test_filter_by_triggering_user(self, session, test_client, endpoint,
triggering_user, expected):
+ session.commit()
+ response =
test_client.get(f"/grid/{endpoint}/{DAG_ID}?triggering_user={triggering_user}")
+ assert response.status_code == 200
+ assert response.json() == expected
+
+ def test_get_grid_runs_filter_by_run_type_and_triggering_user(self,
session, test_client):
+ session.commit()
+ response =
test_client.get(f"/grid/runs/{DAG_ID}?run_type=manual&triggering_user=user2")
+ assert response.status_code == 200
+ assert response.json() == [GRID_RUN_2]
+
def test_grid_ti_summaries_group(self, session, test_client):
run_id = "run_4-1"
session.commit()