Skip to content

Commit fe8d84a

Browse files
committed
Update dagdetails
1 parent 822d62a commit fe8d84a

File tree

13 files changed

+146
-94
lines changed

13 files changed

+146
-94
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ class DAGDetailsResponse(DAGResponse):
157157
last_parsed: datetime | None
158158
default_args: abc.Mapping | None
159159
owner_links: dict[str, str] | None = None
160+
is_favorite: bool = False
160161

161162
@field_validator("timezone", mode="before")
162163
@classmethod

airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10017,6 +10017,10 @@ components:
1001710017
type: object
1001810018
- type: 'null'
1001910019
title: Owner Links
10020+
is_favorite:
10021+
type: boolean
10022+
title: Is Favorite
10023+
default: false
1002010024
file_token:
1002110025
type: string
1002210026
title: File Token

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,9 @@ def get_dag(
209209
),
210210
dependencies=[Depends(requires_access_dag(method="GET"))],
211211
)
212-
def get_dag_details(dag_id: str, session: SessionDep, dag_bag: DagBagDep) -> DAGDetailsResponse:
212+
def get_dag_details(
213+
dag_id: str, session: SessionDep, dag_bag: DagBagDep, user: GetUserDep
214+
) -> DAGDetailsResponse:
213215
"""Get details of DAG."""
214216
dag = get_latest_version_of_dag(dag_bag, dag_id, session)
215217

@@ -221,7 +223,19 @@ def get_dag_details(dag_id: str, session: SessionDep, dag_bag: DagBagDep) -> DAG
221223
if not key.startswith("_") and not hasattr(dag_model, key):
222224
setattr(dag_model, key, value)
223225

224-
return dag_model
226+
# Check if this DAG is marked as favorite by the current user
227+
user_id = str(user.get_id())
228+
is_favorite = (
229+
session.scalar(
230+
select(DagFavorite.dag_id).where(DagFavorite.user_id == user_id, DagFavorite.dag_id == dag_id)
231+
)
232+
is not None
233+
)
234+
235+
# Add is_favorite field to the DAG model
236+
setattr(dag_model, "is_favorite", is_favorite)
237+
238+
return DAGDetailsResponse.model_validate(dag_model)
225239

226240

227241
@dags_router.patch(

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,10 @@ def get_dags(
158158

159159
# Fetch favorite status for each DAG for the current user
160160
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)}
161+
favorites_select = select(DagFavorite.dag_id).where(
162+
DagFavorite.user_id == user_id, DagFavorite.dag_id.in_([dag.dag_id for dag in dags])
163+
)
164+
favorite_dag_ids = set(session.scalars(favorites_select))
167165

168166
# Populate the last 'dag_runs_limit' DagRuns for each DAG
169167
recent_runs_subquery = (

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2041,6 +2041,11 @@ export const $DAGDetailsResponse = {
20412041
],
20422042
title: 'Owner Links'
20432043
},
2044+
is_favorite: {
2045+
type: 'boolean',
2046+
title: 'Is Favorite',
2047+
default: false
2048+
},
20442049
file_token: {
20452050
type: 'string',
20462051
title: 'File Token',

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
@@ -562,6 +562,7 @@ export type DAGDetailsResponse = {
562562
owner_links?: {
563563
[key: string]: (string);
564564
} | null;
565+
is_favorite?: boolean;
565566
/**
566567
* Return file token.
567568
*/

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

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,25 @@
1717
* under the License.
1818
*/
1919
import { Box } from "@chakra-ui/react";
20-
import { useCallback } from "react";
2120
import { useTranslation } from "react-i18next";
2221
import { FiStar } from "react-icons/fi";
2322

24-
import { useFavoriteDag } from "src/queries/useFavoriteDag";
25-
import { useUnfavoriteDag } from "src/queries/useUnfavoriteDag";
23+
import { useToggleFavoriteDag } from "src/queries/useToggleFavoriteDag";
2624

2725
import ActionButton from "../ui/ActionButton";
2826

2927
type FavoriteDagButtonProps = {
3028
readonly dagId: string;
31-
readonly isFavorite: boolean;
29+
readonly isFavorite?: boolean;
3230
readonly withText?: boolean;
3331
};
3432

35-
export const FavoriteDagButton = ({ dagId, isFavorite, withText = true }: FavoriteDagButtonProps) => {
33+
export const FavoriteDagButton = ({ dagId, isFavorite = false, withText = true }: FavoriteDagButtonProps) => {
3634
const { t: translate } = useTranslation("dags");
3735

38-
const { mutate: favoriteDag } = useFavoriteDag();
39-
const { mutate: unfavoriteDag } = useUnfavoriteDag();
36+
const { isLoading, toggleFavorite } = useToggleFavoriteDag(dagId);
4037

41-
const onToggle = useCallback(() => {
42-
const mutationFn = isFavorite ? unfavoriteDag : favoriteDag;
43-
44-
mutationFn({ dagId });
45-
}, [dagId, isFavorite, favoriteDag, unfavoriteDag]);
38+
const onToggle = () => toggleFavorite(isFavorite);
4639

4740
return (
4841
<Box>
@@ -56,6 +49,7 @@ export const FavoriteDagButton = ({ dagId, isFavorite, withText = true }: Favori
5649
}}
5750
/>
5851
}
52+
loading={isLoading}
5953
onClick={onToggle}
6054
text={isFavorite ? translate("unfavoriteDag") : translate("favoriteDag")}
6155
withText={withText}

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ 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";
2726
import type { DAGDetailsResponse, DagRunState } from "openapi/requests/types.gen";
2827
import { DagIcon } from "src/assets/DagIcon";
2928
import DeleteDagButton from "src/components/DagActions/DeleteDagButton";
@@ -116,8 +115,6 @@ export const Header = ({
116115
},
117116
];
118117

119-
const { data: dagData } = useDagServiceGetDagsUi({ dagIds: [dagId ?? ""] });
120-
121118
return (
122119
<HeaderCard
123120
actions={
@@ -131,11 +128,7 @@ export const Header = ({
131128
text={translate("dag:header.buttons.dagDocs")}
132129
/>
133130
)}
134-
<FavoriteDagButton
135-
dagId={dag.dag_id}
136-
isFavorite={dagData?.dags[0]?.is_favorite ?? false}
137-
withText
138-
/>
131+
<FavoriteDagButton dagId={dag.dag_id} isFavorite={dag.is_favorite} withText />
139132
<Menu.Root>
140133
<Menu.Trigger asChild>
141134
<Button aria-label={translate("dag:header.buttons.advanced")} variant="outline">

airflow-core/src/airflow/ui/src/queries/useFavoriteDag.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*!
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
import { useQueryClient } from "@tanstack/react-query";
20+
import { useCallback } from "react";
21+
22+
import {
23+
useDagServiceGetDagsUiKey,
24+
useDagServiceFavoriteDag,
25+
useDagServiceUnfavoriteDag,
26+
UseDagServiceGetDagDetailsKeyFn,
27+
} from "openapi/queries";
28+
29+
export const useToggleFavoriteDag = (dagId: string) => {
30+
const queryClient = useQueryClient();
31+
32+
const onSuccess = useCallback(async () => {
33+
// Invalidate the DAGs list query
34+
await queryClient.invalidateQueries({
35+
queryKey: [useDagServiceGetDagsUiKey, UseDagServiceGetDagDetailsKeyFn({ dagId }, [{ dagId }])],
36+
});
37+
38+
// Invalidate the specific DAG details query for this DAG
39+
await queryClient.invalidateQueries({
40+
queryKey: UseDagServiceGetDagDetailsKeyFn({ dagId }, [{ dagId }]),
41+
});
42+
}, [queryClient, dagId]);
43+
44+
const favoriteMutation = useDagServiceFavoriteDag({
45+
onSuccess,
46+
});
47+
48+
const unfavoriteMutation = useDagServiceUnfavoriteDag({
49+
onSuccess,
50+
});
51+
52+
const toggleFavorite = useCallback(
53+
(isFavorite: boolean) => {
54+
const mutation = isFavorite ? unfavoriteMutation : favoriteMutation;
55+
56+
mutation.mutate({ dagId });
57+
},
58+
[dagId, favoriteMutation, unfavoriteMutation],
59+
);
60+
61+
return {
62+
error: favoriteMutation.error ?? unfavoriteMutation.error,
63+
isLoading: favoriteMutation.isPending || unfavoriteMutation.isPending,
64+
toggleFavorite,
65+
};
66+
};

0 commit comments

Comments
 (0)