Skip to content

Commit 822d62a

Browse files
committed
Add is_favorite to ui dags list
1 parent cc16b1a commit 822d62a

File tree

12 files changed

+88
-15
lines changed

12 files changed

+88
-15
lines changed

airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dags.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class DAGWithLatestDagRunsResponse(DAGResponse):
2929
asset_expression: dict | None
3030
latest_dag_runs: list[DAGRunResponse]
3131
pending_actions: list[HITLDetail]
32+
is_favorite: bool
3233

3334

3435
class DAGWithLatestDagRunsCollectionResponse(BaseModel):

airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,6 +1686,9 @@ components:
16861686
$ref: '#/components/schemas/HITLDetail'
16871687
type: array
16881688
title: Pending Actions
1689+
is_favorite:
1690+
type: boolean
1691+
title: Is Favorite
16891692
file_token:
16901693
type: string
16911694
title: File Token
@@ -1721,6 +1724,7 @@ components:
17211724
- asset_expression
17221725
- latest_dag_runs
17231726
- pending_actions
1727+
- is_favorite
17241728
- file_token
17251729
title: DAGWithLatestDagRunsResponse
17261730
description: DAG with latest dag runs response serializer.

airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,12 @@
6161
)
6262
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
6363
from airflow.api_fastapi.core_api.security import (
64+
GetUserDep,
6465
ReadableDagsFilterDep,
6566
requires_access_dag,
6667
)
6768
from airflow.models import DagModel, DagRun
69+
from airflow.models.dag_favorite import DagFavorite
6870
from airflow.models.hitl import HITLDetail
6971
from airflow.models.taskinstance import TaskInstance
7072
from airflow.utils.state import TaskInstanceState
@@ -114,6 +116,7 @@ def get_dags(
114116
has_pending_actions: QueryPendingActionsFilter,
115117
readable_dags_filter: ReadableDagsFilterDep,
116118
session: SessionDep,
119+
user: GetUserDep,
117120
dag_runs_limit: int = 10,
118121
) -> DAGWithLatestDagRunsCollectionResponse:
119122
"""Get DAGs with recent DagRun."""
@@ -153,6 +156,15 @@ def get_dags(
153156

154157
dags = [dag for dag in session.scalars(dags_select)]
155158

159+
# Fetch favorite status for each DAG for the current user
160+
user_id = str(user.get_id())
161+
favorite_dag_ids = set()
162+
if dags:
163+
favorites_select = select(DagFavorite.dag_id).where(
164+
DagFavorite.user_id == user_id, DagFavorite.dag_id.in_([dag.dag_id for dag in dags])
165+
)
166+
favorite_dag_ids = {dag_id for dag_id in session.scalars(favorites_select)}
167+
156168
# Populate the last 'dag_runs_limit' DagRuns for each DAG
157169
recent_runs_subquery = (
158170
select(
@@ -224,6 +236,7 @@ def get_dags(
224236
"asset_expression": dag.asset_expression,
225237
"latest_dag_runs": [],
226238
"pending_actions": pending_actions_by_dag_id[dag.dag_id],
239+
"is_favorite": dag.dag_id in favorite_dag_ids,
227240
}
228241
)
229242
for dag in dags

airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7343,6 +7343,10 @@ export const $DAGWithLatestDagRunsResponse = {
73437343
type: 'array',
73447344
title: 'Pending Actions'
73457345
},
7346+
is_favorite: {
7347+
type: 'boolean',
7348+
title: 'Is Favorite'
7349+
},
73467350
file_token: {
73477351
type: 'string',
73487352
title: 'File Token',
@@ -7351,7 +7355,7 @@ export const $DAGWithLatestDagRunsResponse = {
73517355
}
73527356
},
73537357
type: 'object',
7354-
required: ['dag_id', 'dag_display_name', 'is_paused', 'is_stale', 'last_parsed_time', 'last_parse_duration', 'last_expired', 'bundle_name', 'bundle_version', 'relative_fileloc', 'fileloc', 'description', 'timetable_summary', 'timetable_description', 'tags', 'max_active_tasks', 'max_active_runs', 'max_consecutive_failed_dag_runs', 'has_task_concurrency_limits', 'has_import_errors', 'next_dagrun_logical_date', 'next_dagrun_data_interval_start', 'next_dagrun_data_interval_end', 'next_dagrun_run_after', 'owners', 'asset_expression', 'latest_dag_runs', 'pending_actions', 'file_token'],
7358+
required: ['dag_id', 'dag_display_name', 'is_paused', 'is_stale', 'last_parsed_time', 'last_parse_duration', 'last_expired', 'bundle_name', 'bundle_version', 'relative_fileloc', 'fileloc', 'description', 'timetable_summary', 'timetable_description', 'tags', 'max_active_tasks', 'max_active_runs', 'max_consecutive_failed_dag_runs', 'has_task_concurrency_limits', 'has_import_errors', 'next_dagrun_logical_date', 'next_dagrun_data_interval_start', 'next_dagrun_data_interval_end', 'next_dagrun_run_after', 'owners', 'asset_expression', 'latest_dag_runs', 'pending_actions', 'is_favorite', 'file_token'],
73557359
title: 'DAGWithLatestDagRunsResponse',
73567360
description: 'DAG with latest dag runs response serializer.'
73577361
} as const;

airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,7 @@ export type DAGWithLatestDagRunsResponse = {
18341834
} | null;
18351835
latest_dag_runs: Array<DAGRunResponse>;
18361836
pending_actions: Array<HITLDetail>;
1837+
is_favorite: boolean;
18371838
/**
18381839
* Return file token.
18391840
*/

airflow-core/src/airflow/ui/src/components/DagActions/FavoriteDagButton.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,23 @@
1717
* under the License.
1818
*/
1919
import { Box } from "@chakra-ui/react";
20-
import { useCallback, useMemo } from "react";
20+
import { useCallback } from "react";
2121
import { useTranslation } from "react-i18next";
2222
import { FiStar } from "react-icons/fi";
2323

24-
import { useDagServiceGetDagsUi } from "openapi/queries";
2524
import { useFavoriteDag } from "src/queries/useFavoriteDag";
2625
import { useUnfavoriteDag } from "src/queries/useUnfavoriteDag";
2726

2827
import ActionButton from "../ui/ActionButton";
2928

3029
type FavoriteDagButtonProps = {
3130
readonly dagId: string;
31+
readonly isFavorite: boolean;
3232
readonly withText?: boolean;
3333
};
3434

35-
export const FavoriteDagButton = ({ dagId, withText = true }: FavoriteDagButtonProps) => {
35+
export const FavoriteDagButton = ({ dagId, isFavorite, withText = true }: FavoriteDagButtonProps) => {
3636
const { t: translate } = useTranslation("dags");
37-
const { data: favorites } = useDagServiceGetDagsUi({ isFavorite: true });
38-
39-
const isFavorite = useMemo(
40-
() => favorites?.dags.some((fav) => fav.dag_id === dagId) ?? false,
41-
[favorites, dagId],
42-
);
4337

4438
const { mutate: favoriteDag } = useFavoriteDag();
4539
const { mutate: unfavoriteDag } = useUnfavoriteDag();
@@ -54,7 +48,14 @@ export const FavoriteDagButton = ({ dagId, withText = true }: FavoriteDagButtonP
5448
<Box>
5549
<ActionButton
5650
actionName={isFavorite ? translate("unfavoriteDag") : translate("favoriteDag")}
57-
icon={<FiStar style={{ fill: isFavorite ? "var(--chakra-colors-brand-solid)" : "none" }} />}
51+
icon={
52+
<FiStar
53+
style={{
54+
fill: isFavorite ? "var(--chakra-colors-brand-solid)" : "none",
55+
stroke: "var(--chakra-colors-brand-solid)",
56+
}}
57+
/>
58+
}
5859
onClick={onToggle}
5960
text={isFavorite ? translate("unfavoriteDag") : translate("favoriteDag")}
6061
withText={withText}

airflow-core/src/airflow/ui/src/mocks/handlers/dags.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const handlers: Array<HttpHandler> = [
3232
fileloc: "/airflow/dags/tutorial_taskflow_api.py",
3333
has_import_errors: false,
3434
has_task_concurrency_limits: false,
35+
is_favorite: true,
3536
is_paused: false,
3637
is_stale: false,
3738
last_parsed_time: "2025-01-13T07:34:01.593459Z",
@@ -69,6 +70,7 @@ export const handlers: Array<HttpHandler> = [
6970
fileloc: "/airflow/dags/tutorial_taskflow_api_failed.py",
7071
has_import_errors: false,
7172
has_task_concurrency_limits: false,
73+
is_favorite: false,
7274
is_paused: false,
7375
is_stale: false,
7476
last_parsed_time: "2025-01-13T07:34:01.593459Z",
@@ -128,6 +130,7 @@ export const handlers: Array<HttpHandler> = [
128130
fileloc: "/airflow/dags/tutorial_taskflow_api_failed.py",
129131
has_import_errors: false,
130132
has_task_concurrency_limits: false,
133+
is_favorite: false,
131134
is_paused: false,
132135
is_stale: false,
133136
last_expired: null,
@@ -155,6 +158,7 @@ export const handlers: Array<HttpHandler> = [
155158
fileloc: "/airflow/dags/tutorial_taskflow_api_success.py",
156159
has_import_errors: false,
157160
has_task_concurrency_limits: false,
161+
is_favorite: true,
158162
is_paused: false,
159163
is_stale: false,
160164
last_expired: null,

airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { FiBookOpen } from "react-icons/fi";
2323
import { LuMenu } from "react-icons/lu";
2424
import { useParams, Link as RouterLink } from "react-router-dom";
2525

26+
import { useDagServiceGetDagsUi } from "openapi/queries";
2627
import type { DAGDetailsResponse, DagRunState } from "openapi/requests/types.gen";
2728
import { DagIcon } from "src/assets/DagIcon";
2829
import DeleteDagButton from "src/components/DagActions/DeleteDagButton";
@@ -115,6 +116,8 @@ export const Header = ({
115116
},
116117
];
117118

119+
const { data: dagData } = useDagServiceGetDagsUi({ dagIds: [dagId ?? ""] });
120+
118121
return (
119122
<HeaderCard
120123
actions={
@@ -128,7 +131,11 @@ export const Header = ({
128131
text={translate("dag:header.buttons.dagDocs")}
129132
/>
130133
)}
131-
<FavoriteDagButton dagId={dag.dag_id} withText={true} />
134+
<FavoriteDagButton
135+
dagId={dag.dag_id}
136+
isFavorite={dagData?.dags[0]?.is_favorite ?? false}
137+
withText
138+
/>
132139
<Menu.Root>
133140
<Menu.Trigger asChild>
134141
<Button aria-label={translate("dag:header.buttons.advanced")} variant="outline">

airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ const mockDag = {
6666
fileloc: "/files/dags/nested_task_groups.py",
6767
has_import_errors: false,
6868
has_task_concurrency_limits: false,
69+
is_favorite: false,
6970
is_paused: false,
7071
is_stale: false,
7172
last_expired: null,

airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const DagCard = ({ dag }: Props) => {
7272
isPaused={dag.is_paused}
7373
withText={false}
7474
/>
75-
<FavoriteDagButton dagId={dag.dag_id} withText={false} />
75+
<FavoriteDagButton dagId={dag.dag_id} isFavorite={dag.is_favorite} withText={false} />
7676
<DeleteDagButton dagDisplayName={dag.dag_display_name} dagId={dag.dag_id} withText={false} />
7777
</HStack>
7878
</Flex>

0 commit comments

Comments
 (0)