From ee401f0ada4265e619999f8e5272c35d084727d8 Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Tue, 24 Dec 2024 22:21:38 +0530 Subject: [PATCH 1/8] Add search dags widget to navbar. --- airflow/ui/package.json | 2 + airflow/ui/pnpm-lock.yaml | 16 +++ airflow/ui/src/components/SearchDags.tsx | 97 +++++++++++++++++++ .../SearchDagsDropdownIndicator.tsx | 31 ++++++ .../ui/src/layouts/Details/DetailsLayout.tsx | 18 ++-- 5 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 airflow/ui/src/components/SearchDags.tsx create mode 100644 airflow/ui/src/components/SearchDagsDropdownIndicator.tsx diff --git a/airflow/ui/package.json b/airflow/ui/package.json index e78bf77fd1013..4c2a71d3302b7 100644 --- a/airflow/ui/package.json +++ b/airflow/ui/package.json @@ -22,6 +22,7 @@ "@emotion/react": "^11.13.3", "@tanstack/react-query": "^5.52.1", "@tanstack/react-table": "^8.20.1", + "@types/debounce-promise": "^3.1.9", "@uiw/codemirror-themes-all": "^4.23.5", "@uiw/react-codemirror": "^4.23.5", "@visx/group": "^3.12.0", @@ -31,6 +32,7 @@ "chakra-react-select": "6.0.0-next.2", "chart.js": "^4.4.6", "dayjs": "^1.11.13", + "debounce-promise": "^3.1.2", "elkjs": "^0.9.3", "next-themes": "^0.3.0", "react": "^18.3.1", diff --git a/airflow/ui/pnpm-lock.yaml b/airflow/ui/pnpm-lock.yaml index 39b5886f0ab51..54f8983d08125 100644 --- a/airflow/ui/pnpm-lock.yaml +++ b/airflow/ui/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@tanstack/react-table': specifier: ^8.20.1 version: 8.20.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/debounce-promise': + specifier: ^3.1.9 + version: 3.1.9 '@uiw/codemirror-themes-all': specifier: ^4.23.5 version: 4.23.6(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1) @@ -53,6 +56,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 + debounce-promise: + specifier: ^3.1.2 + version: 3.1.2 elkjs: specifier: ^0.9.3 version: 0.9.3 @@ -961,6 +967,9 @@ packages: '@types/d3-zoom@3.0.8': resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + '@types/debounce-promise@3.1.9': + resolution: {integrity: sha512-awNxydYSU+E2vL7EiOAMtiSOfL5gUM5X4YSE2A92qpxDJQ/rXz6oMPYBFDcDywlUmvIDI6zsqgq17cGm5CITQw==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -1932,6 +1941,9 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + debounce-promise@3.1.2: + resolution: {integrity: sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==} + debug@4.3.6: resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} engines: {node: '>=6.0'} @@ -4693,6 +4705,8 @@ snapshots: '@types/d3-interpolate': 3.0.4 '@types/d3-selection': 3.0.11 + '@types/debounce-promise@3.1.9': {} + '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 @@ -6306,6 +6320,8 @@ snapshots: dayjs@1.11.13: {} + debounce-promise@3.1.2: {} + debug@4.3.6: dependencies: ms: 2.1.2 diff --git a/airflow/ui/src/components/SearchDags.tsx b/airflow/ui/src/components/SearchDags.tsx new file mode 100644 index 0000000000000..677011a8afb26 --- /dev/null +++ b/airflow/ui/src/components/SearchDags.tsx @@ -0,0 +1,97 @@ +/*! + * 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. + */ +import { Field } from "@chakra-ui/react"; +import { useQueryClient } from "@tanstack/react-query"; +import { AsyncSelect } from "chakra-react-select"; +import type { OptionsOrGroups, GroupBase } from "chakra-react-select"; +import type { OnChangeValue } from "chakra-react-select"; +import debounce from "debounce-promise"; +import { useNavigate } from "react-router-dom"; + +import { UseDagServiceGetDagsKeyFn } from "openapi/queries"; +import { DagService } from "openapi/requests/services.gen"; +import type { + DAGCollectionResponse, + DAGResponse, +} from "openapi/requests/types.gen"; + +import { DropdownIndicator } from "./SearchDagsDropdownIndicator"; + +export type Option = { + label: string; + value: string; +}; + +export const SearchDags = () => { + const queryClient = useQueryClient(); + const navigate = useNavigate(); + const SEARCH_LIMIT = 5; + + const onSelect = (newValue: OnChangeValue) => { + const selected = newValue as Option; + + if (newValue) { + navigate(`/dags/${selected.value}`); + } + }; + + const searchDag = async ( + inputValue: string, + callback: (options: OptionsOrGroups>) => void, + ): Promise>> => + queryClient.fetchQuery({ + queryFn: () => + DagService.getDags({ + dagDisplayNamePattern: inputValue, + limit: SEARCH_LIMIT, + }).then((data: DAGCollectionResponse) => { + const options = data.dags.map( + (dag: DAGResponse) => + ({ + label: dag.dag_display_name || dag.dag_id, + value: dag.dag_id, + }) as Option, + ); + + callback(options); + + return options; + }), + queryKey: UseDagServiceGetDagsKeyFn({ + dagDisplayNamePattern: inputValue, + }), + staleTime: 0, + }); + + const searchDagDebounced = debounce(searchDag, 300); + + return ( + + + + ); +}; diff --git a/airflow/ui/src/components/SearchDagsDropdownIndicator.tsx b/airflow/ui/src/components/SearchDagsDropdownIndicator.tsx new file mode 100644 index 0000000000000..16c78e3182abe --- /dev/null +++ b/airflow/ui/src/components/SearchDagsDropdownIndicator.tsx @@ -0,0 +1,31 @@ +/*! + * 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. + */ +import { chakraComponents } from "chakra-react-select"; +import type { DropdownIndicatorProps } from "chakra-react-select"; +import { FiSearch } from "react-icons/fi"; + +import type { Option } from "./SearchDags"; + +export const DropdownIndicator: React.FC< + DropdownIndicatorProps +> = (props) => ( + + + +); diff --git a/airflow/ui/src/layouts/Details/DetailsLayout.tsx b/airflow/ui/src/layouts/Details/DetailsLayout.tsx index d5aee2370eeea..1f31a00d4f19d 100644 --- a/airflow/ui/src/layouts/Details/DetailsLayout.tsx +++ b/airflow/ui/src/layouts/Details/DetailsLayout.tsx @@ -16,13 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { Box, Button } from "@chakra-ui/react"; +import { Box, Button, HStack } from "@chakra-ui/react"; import type { PropsWithChildren } from "react"; import { FiChevronsLeft } from "react-icons/fi"; import { Outlet, Link as RouterLink, useParams, useSearchParams } from "react-router-dom"; import type { DAGResponse } from "openapi/requests/types.gen"; import { ErrorAlert } from "src/components/ErrorAlert"; +import { SearchDags } from "src/components/SearchDags"; import { ProgressBar } from "src/components/ui"; import { Toaster } from "src/components/ui"; import { OpenGroupsProvider } from "src/context/openGroups"; @@ -53,12 +54,15 @@ export const DetailsLayout = ({ children, dag, error, isLoading, tabs }: Props) return ( - + + + + {children} From 69a6e411a8fce3c0cf35d34983ba9c6d4b788825 Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Tue, 24 Dec 2024 22:50:49 +0530 Subject: [PATCH 2/8] Use null instead of undefined to clear selected value. --- airflow/ui/src/components/SearchDags.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/airflow/ui/src/components/SearchDags.tsx b/airflow/ui/src/components/SearchDags.tsx index 677011a8afb26..93afc6f5369f4 100644 --- a/airflow/ui/src/components/SearchDags.tsx +++ b/airflow/ui/src/components/SearchDags.tsx @@ -19,8 +19,11 @@ import { Field } from "@chakra-ui/react"; import { useQueryClient } from "@tanstack/react-query"; import { AsyncSelect } from "chakra-react-select"; -import type { OptionsOrGroups, GroupBase } from "chakra-react-select"; -import type { OnChangeValue } from "chakra-react-select"; +import type { + OptionsOrGroups, + GroupBase, + OnChangeValue, +} from "chakra-react-select"; import debounce from "debounce-promise"; import { useNavigate } from "react-router-dom"; @@ -90,7 +93,7 @@ export const SearchDags = () => { loadOptions={searchDagDebounced} onChange={onSelect} placeholder="Search Dags" - value={undefined} + value={null} // null is required https://github.com/JedWatson/react-select/issues/3066 /> ); From e3a0ae74d4be46875b9a4b95d908b87a4ffafb30 Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Wed, 25 Dec 2024 22:14:36 +0530 Subject: [PATCH 3/8] Remove unused async. --- airflow/ui/src/components/SearchDags.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow/ui/src/components/SearchDags.tsx b/airflow/ui/src/components/SearchDags.tsx index 93afc6f5369f4..59b82dd171f4c 100644 --- a/airflow/ui/src/components/SearchDags.tsx +++ b/airflow/ui/src/components/SearchDags.tsx @@ -54,7 +54,7 @@ export const SearchDags = () => { } }; - const searchDag = async ( + const searchDag = ( inputValue: string, callback: (options: OptionsOrGroups>) => void, ): Promise>> => From f4e4da8ff947d3aa618a0a4a33bfe58889de0514 Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Thu, 26 Dec 2024 13:47:45 +0530 Subject: [PATCH 4/8] Increase limit to 10 and width to accommodate longer names. --- airflow/ui/src/components/SearchDags.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airflow/ui/src/components/SearchDags.tsx b/airflow/ui/src/components/SearchDags.tsx index 59b82dd171f4c..812a7173259e4 100644 --- a/airflow/ui/src/components/SearchDags.tsx +++ b/airflow/ui/src/components/SearchDags.tsx @@ -44,7 +44,7 @@ export type Option = { export const SearchDags = () => { const queryClient = useQueryClient(); const navigate = useNavigate(); - const SEARCH_LIMIT = 5; + const SEARCH_LIMIT = 10; const onSelect = (newValue: OnChangeValue) => { const selected = newValue as Option; @@ -85,7 +85,7 @@ export const SearchDags = () => { const searchDagDebounced = debounce(searchDag, 300); return ( - + Date: Sun, 5 Jan 2025 19:49:19 +0530 Subject: [PATCH 5/8] Rebase and run format for line number rule. --- airflow/ui/src/components/SearchDags.tsx | 11 ++--------- .../ui/src/components/SearchDagsDropdownIndicator.tsx | 4 +--- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/airflow/ui/src/components/SearchDags.tsx b/airflow/ui/src/components/SearchDags.tsx index 812a7173259e4..015dd84d4b3d4 100644 --- a/airflow/ui/src/components/SearchDags.tsx +++ b/airflow/ui/src/components/SearchDags.tsx @@ -19,20 +19,13 @@ import { Field } from "@chakra-ui/react"; import { useQueryClient } from "@tanstack/react-query"; import { AsyncSelect } from "chakra-react-select"; -import type { - OptionsOrGroups, - GroupBase, - OnChangeValue, -} from "chakra-react-select"; +import type { OptionsOrGroups, GroupBase, OnChangeValue } from "chakra-react-select"; import debounce from "debounce-promise"; import { useNavigate } from "react-router-dom"; import { UseDagServiceGetDagsKeyFn } from "openapi/queries"; import { DagService } from "openapi/requests/services.gen"; -import type { - DAGCollectionResponse, - DAGResponse, -} from "openapi/requests/types.gen"; +import type { DAGCollectionResponse, DAGResponse } from "openapi/requests/types.gen"; import { DropdownIndicator } from "./SearchDagsDropdownIndicator"; diff --git a/airflow/ui/src/components/SearchDagsDropdownIndicator.tsx b/airflow/ui/src/components/SearchDagsDropdownIndicator.tsx index 16c78e3182abe..3e0689cc881fa 100644 --- a/airflow/ui/src/components/SearchDagsDropdownIndicator.tsx +++ b/airflow/ui/src/components/SearchDagsDropdownIndicator.tsx @@ -22,9 +22,7 @@ import { FiSearch } from "react-icons/fi"; import type { Option } from "./SearchDags"; -export const DropdownIndicator: React.FC< - DropdownIndicatorProps -> = (props) => ( +export const DropdownIndicator: React.FC> = (props) => ( From 1105360ad5c02bfb584660808c64649a247ac8fc Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Wed, 8 Jan 2025 12:31:03 +0530 Subject: [PATCH 6/8] Move search to a modal like Chakra docs. --- airflow/ui/src/components/SearchDags.tsx | 10 +++- .../ui/src/components/SearchDagsButton.tsx | 46 +++++++++++++++++++ .../ui/src/layouts/Details/DetailsLayout.tsx | 4 +- 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 airflow/ui/src/components/SearchDagsButton.tsx diff --git a/airflow/ui/src/components/SearchDags.tsx b/airflow/ui/src/components/SearchDags.tsx index 015dd84d4b3d4..1e32002c61519 100644 --- a/airflow/ui/src/components/SearchDags.tsx +++ b/airflow/ui/src/components/SearchDags.tsx @@ -21,6 +21,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { AsyncSelect } from "chakra-react-select"; import type { OptionsOrGroups, GroupBase, OnChangeValue } from "chakra-react-select"; import debounce from "debounce-promise"; +import React from "react"; import { useNavigate } from "react-router-dom"; import { UseDagServiceGetDagsKeyFn } from "openapi/queries"; @@ -34,7 +35,11 @@ export type Option = { value: string; }; -export const SearchDags = () => { +export const SearchDags = ({ + setIsOpen, +}: { + readonly setIsOpen: React.Dispatch>; +}) => { const queryClient = useQueryClient(); const navigate = useNavigate(); const SEARCH_LIMIT = 10; @@ -43,6 +48,7 @@ export const SearchDags = () => { const selected = newValue as Option; if (newValue) { + setIsOpen(false); navigate(`/dags/${selected.value}`); } }; @@ -78,7 +84,7 @@ export const SearchDags = () => { const searchDagDebounced = debounce(searchDag, 300); return ( - + { + const [isOpen, setIsOpen] = useState(false); + + const onOpenChange = () => { + setIsOpen(false); + }; + + return ( + + + + + + + + + ); +}; diff --git a/airflow/ui/src/layouts/Details/DetailsLayout.tsx b/airflow/ui/src/layouts/Details/DetailsLayout.tsx index 1f31a00d4f19d..4187f692c9e60 100644 --- a/airflow/ui/src/layouts/Details/DetailsLayout.tsx +++ b/airflow/ui/src/layouts/Details/DetailsLayout.tsx @@ -23,7 +23,7 @@ import { Outlet, Link as RouterLink, useParams, useSearchParams } from "react-ro import type { DAGResponse } from "openapi/requests/types.gen"; import { ErrorAlert } from "src/components/ErrorAlert"; -import { SearchDags } from "src/components/SearchDags"; +import { SearchDagsButton } from "src/components/SearchDagsButton"; import { ProgressBar } from "src/components/ui"; import { Toaster } from "src/components/ui"; import { OpenGroupsProvider } from "src/context/openGroups"; @@ -61,7 +61,7 @@ export const DetailsLayout = ({ children, dag, error, isLoading, tabs }: Props) Back to all dags - + {children} From 3118a2217fdf76fb6b9e02cc98a8be71f749d55c Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Wed, 8 Jan 2025 23:04:44 +0530 Subject: [PATCH 7/8] Set default list of dags on initial render. --- airflow/ui/src/components/SearchDags.tsx | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/airflow/ui/src/components/SearchDags.tsx b/airflow/ui/src/components/SearchDags.tsx index 1e32002c61519..847347c56fc3b 100644 --- a/airflow/ui/src/components/SearchDags.tsx +++ b/airflow/ui/src/components/SearchDags.tsx @@ -19,7 +19,7 @@ import { Field } from "@chakra-ui/react"; import { useQueryClient } from "@tanstack/react-query"; import { AsyncSelect } from "chakra-react-select"; -import type { OptionsOrGroups, GroupBase, OnChangeValue } from "chakra-react-select"; +import type { OptionsOrGroups, GroupBase, SingleValue } from "chakra-react-select"; import debounce from "debounce-promise"; import React from "react"; import { useNavigate } from "react-router-dom"; @@ -44,10 +44,8 @@ export const SearchDags = ({ const navigate = useNavigate(); const SEARCH_LIMIT = 10; - const onSelect = (newValue: OnChangeValue) => { - const selected = newValue as Option; - - if (newValue) { + const onSelect = (selected: SingleValue