This is an automated email from the ASF dual-hosted git repository.

bbovenzi 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 f38d56dbf4d Add search by dag_display_name_pattern on dag list page 
with rebase (#42896)
f38d56dbf4d is described below

commit f38d56dbf4dc1639142fc5a494d5da24996a56cc
Author: Luyang Liu <luyan...@proton.me>
AuthorDate: Tue Oct 15 23:30:19 2024 +1100

    Add search by dag_display_name_pattern on dag list page with rebase (#42896)
    
    * Add search by `dag_display_name_pattern` on dag list page.
    
    * refactor search parameter update logic
    
    * Refactor SearchBar component and simplify debounce logic in DagsList.
    
    * Refactor search input change handling for SearchBar and DagsList 
components
    
    * Update airflow/ui/src/pages/DagsList/DagsList.tsx
    
    Co-authored-by: Brent Bovenzi <brent.bove...@gmail.com>
    
    * Refactor search parameter handling and improve type consistency
    
    * Remove typo
    
    * Add `LAST_DAG_RUN_STATE` to `SearchParamsKeys` and update filters
    
    * Add LAST_DAG_RUN_STATE to SearchParamsKeys and update filters
    
    * Fix missing change for add SearchBar component by removing forwardRef and 
adding debounced search logic.
    
    * minor change for SearchBar and DagsFilters components
    
    * Optimize imports and improve formatting across components
    
    * refactor: move query options from autogenerated useDagServiceGetDags to 
DagsList.
    
    * Fix formatting.
    
    ---------
    
    Co-authored-by: Brent Bovenzi <brent.bove...@gmail.com>
---
 airflow/ui/package.json                            |  3 +-
 airflow/ui/pnpm-lock.yaml                          | 13 ++++
 .../ui/src/components/DataTable/searchParams.ts    | 13 +++-
 airflow/ui/src/components/SearchBar.tsx            | 62 ++++++++++-------
 airflow/ui/src/constants/searchParams.ts           | 31 +++++++++
 airflow/ui/src/pages/DagsList/DagsFilters.tsx      | 16 +++--
 airflow/ui/src/pages/DagsList/DagsList.tsx         | 78 +++++++++++++++++-----
 7 files changed, 169 insertions(+), 47 deletions(-)

diff --git a/airflow/ui/package.json b/airflow/ui/package.json
index 014564c1604..a7cf90bb573 100644
--- a/airflow/ui/package.json
+++ b/airflow/ui/package.json
@@ -28,7 +28,8 @@
     "react": "^18.3.1",
     "react-dom": "^18.3.1",
     "react-icons": "^5.3.0",
-    "react-router-dom": "^6.26.2"
+    "react-router-dom": "^6.26.2",
+    "use-debounce": "^10.0.3"
   },
   "devDependencies": {
     "@7nohe/openapi-react-query-codegen": "^1.6.0",
diff --git a/airflow/ui/pnpm-lock.yaml b/airflow/ui/pnpm-lock.yaml
index c9f94d35f73..3b73df0fa80 100644
--- a/airflow/ui/pnpm-lock.yaml
+++ b/airflow/ui/pnpm-lock.yaml
@@ -47,6 +47,9 @@ importers:
       react-router-dom:
         specifier: ^6.26.2
         version: 6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      use-debounce:
+        specifier: ^10.0.3
+        version: 10.0.3(react@18.3.1)
     devDependencies:
       '@7nohe/openapi-react-query-codegen':
         specifier: ^1.6.0
@@ -3221,6 +3224,12 @@ packages:
       '@types/react':
         optional: true
 
+  use-debounce@10.0.3:
+    resolution: {integrity: 
sha512-DxQSI9ZKso689WM1mjgGU3ozcxU1TJElBJ3X6S4SMzMNcm2lVH0AHmyXB+K7ewjz2BSUKJTDqTcwtSMRfB89dg==}
+    engines: {node: '>= 16.0.0'}
+    peerDependencies:
+      react: '*'
+
   use-isomorphic-layout-effect@1.1.2:
     resolution: {integrity: 
sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
     peerDependencies:
@@ -6892,6 +6901,10 @@ snapshots:
     optionalDependencies:
       '@types/react': 18.3.5
 
+  use-debounce@10.0.3(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+
   use-isomorphic-layout-effect@1.1.2(@types/react@18.3.5)(react@18.3.1):
     dependencies:
       react: 18.3.1
diff --git a/airflow/ui/src/components/DataTable/searchParams.ts 
b/airflow/ui/src/components/DataTable/searchParams.ts
index 8cc57ad7b5b..39001b097f3 100644
--- a/airflow/ui/src/components/DataTable/searchParams.ts
+++ b/airflow/ui/src/components/DataTable/searchParams.ts
@@ -18,11 +18,18 @@
  */
 import type { SortingState } from "@tanstack/react-table";
 
+import {
+  SearchParamsKeys,
+  type SearchParamsKeysType,
+} from "src/constants/searchParams";
+
 import type { TableState } from "./types";
 
-export const LIMIT_PARAM = "limit";
-export const OFFSET_PARAM = "offset";
-export const SORT_PARAM = "sort";
+const {
+  LIMIT: LIMIT_PARAM,
+  OFFSET: OFFSET_PARAM,
+  SORT: SORT_PARAM,
+}: SearchParamsKeysType = SearchParamsKeys;
 
 export const stateToSearchParams = (
   state: TableState,
diff --git a/airflow/ui/src/components/SearchBar.tsx 
b/airflow/ui/src/components/SearchBar.tsx
index 830f9426771..ad50a65b431 100644
--- a/airflow/ui/src/components/SearchBar.tsx
+++ b/airflow/ui/src/components/SearchBar.tsx
@@ -18,15 +18,19 @@
  */
 import {
   Button,
-  type ButtonProps,
   Input,
   InputGroup,
-  type InputGroupProps,
   InputLeftElement,
-  type InputProps,
   InputRightElement,
+  type ButtonProps,
+  type InputGroupProps,
+  type InputProps,
 } from "@chakra-ui/react";
+import type { ChangeEvent } from "react";
 import { FiSearch } from "react-icons/fi";
+import { useDebouncedCallback } from "use-debounce";
+
+const debounceDelay = 200;
 
 export const SearchBar = ({
   buttonProps,
@@ -36,23 +40,35 @@ export const SearchBar = ({
   readonly buttonProps?: ButtonProps;
   readonly groupProps?: InputGroupProps;
   readonly inputProps?: InputProps;
-}) => (
-  <InputGroup {...groupProps}>
-    <InputLeftElement pointerEvents="none">
-      <FiSearch />
-    </InputLeftElement>
-    <Input placeholder="Search DAGs" pr={150} {...inputProps} />
-    <InputRightElement width={150}>
-      <Button
-        colorScheme="blue"
-        fontWeight="normal"
-        height="1.75rem"
-        variant="ghost"
-        width={140}
-        {...buttonProps}
-      >
-        Advanced Search
-      </Button>
-    </InputRightElement>
-  </InputGroup>
-);
+}) => {
+  const handleSearchChange = useDebouncedCallback(
+    (event: ChangeEvent<HTMLInputElement>) => inputProps?.onChange?.(event),
+    debounceDelay,
+  );
+
+  return (
+    <InputGroup {...groupProps}>
+      <InputLeftElement pointerEvents="none">
+        <FiSearch />
+      </InputLeftElement>
+      <Input
+        placeholder="Search DAGs"
+        pr={150}
+        {...inputProps}
+        onChange={handleSearchChange}
+      />
+      <InputRightElement width={150}>
+        <Button
+          colorScheme="blue"
+          fontWeight="normal"
+          height="1.75rem"
+          variant="ghost"
+          width={140}
+          {...buttonProps}
+        >
+          Advanced Search
+        </Button>
+      </InputRightElement>
+    </InputGroup>
+  );
+};
diff --git a/airflow/ui/src/constants/searchParams.ts 
b/airflow/ui/src/constants/searchParams.ts
new file mode 100644
index 00000000000..893a4461bff
--- /dev/null
+++ b/airflow/ui/src/constants/searchParams.ts
@@ -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.
+ */
+export enum SearchParamsKeys {
+  LAST_DAG_RUN_STATE = "last_dag_run_state",
+  LIMIT = "limit",
+  NAME_PATTERN = "name_pattern",
+  OFFSET = "offset",
+  PAUSED = "paused",
+  SORT = "sort",
+}
+
+export type SearchParamsKeysType = Record<
+  keyof typeof SearchParamsKeys,
+  string
+>;
diff --git a/airflow/ui/src/pages/DagsList/DagsFilters.tsx 
b/airflow/ui/src/pages/DagsList/DagsFilters.tsx
index 6316816f48f..3d507ace365 100644
--- a/airflow/ui/src/pages/DagsList/DagsFilters.tsx
+++ b/airflow/ui/src/pages/DagsList/DagsFilters.tsx
@@ -23,15 +23,21 @@ import { useSearchParams } from "react-router-dom";
 
 import { useTableURLState } from "src/components/DataTable/useTableUrlState";
 import { QuickFilterButton } from "src/components/QuickFilterButton";
+import {
+  SearchParamsKeys,
+  type SearchParamsKeysType,
+} from "src/constants/searchParams";
 
-const PAUSED_PARAM = "paused";
-const STATE_PARAM = "last_dag_run_state";
+const {
+  LAST_DAG_RUN_STATE: LAST_DAG_RUN_STATE_PARAM,
+  PAUSED: PAUSED_PARAM,
+}: SearchParamsKeysType = SearchParamsKeys;
 
 export const DagsFilters = () => {
   const [searchParams, setSearchParams] = useSearchParams();
 
   const showPaused = searchParams.get(PAUSED_PARAM);
-  const state = searchParams.get(STATE_PARAM);
+  const state = searchParams.get(LAST_DAG_RUN_STATE_PARAM);
   const isAll = state === null;
   const isRunning = state === "running";
   const isFailed = state === "failed";
@@ -61,9 +67,9 @@ export const DagsFilters = () => {
     useCallback(
       ({ currentTarget: { value } }) => {
         if (value === "all") {
-          searchParams.delete(STATE_PARAM);
+          searchParams.delete(LAST_DAG_RUN_STATE_PARAM);
         } else {
-          searchParams.set(STATE_PARAM, value);
+          searchParams.set(LAST_DAG_RUN_STATE_PARAM, value);
         }
         setSearchParams(searchParams);
         setTableURLState({
diff --git a/airflow/ui/src/pages/DagsList/DagsList.tsx 
b/airflow/ui/src/pages/DagsList/DagsList.tsx
index 60d6ef9c4f4..623b8a3b4ba 100644
--- a/airflow/ui/src/pages/DagsList/DagsList.tsx
+++ b/airflow/ui/src/pages/DagsList/DagsList.tsx
@@ -25,7 +25,12 @@ import {
   VStack,
 } from "@chakra-ui/react";
 import type { ColumnDef } from "@tanstack/react-table";
-import { type ChangeEventHandler, useCallback, useState } from "react";
+import {
+  type ChangeEvent,
+  type ChangeEventHandler,
+  useCallback,
+  useState,
+} from "react";
 import { useSearchParams } from "react-router-dom";
 
 import { useDagServiceGetDags } from "openapi/queries";
@@ -37,6 +42,10 @@ import { useTableURLState } from 
"src/components/DataTable/useTableUrlState";
 import { ErrorAlert } from "src/components/ErrorAlert";
 import { SearchBar } from "src/components/SearchBar";
 import { TogglePause } from "src/components/TogglePause";
+import {
+  SearchParamsKeys,
+  type SearchParamsKeysType,
+} from "src/constants/searchParams";
 import { pluralize } from "src/utils/pluralize";
 
 import { DagCard } from "./DagCard";
@@ -90,6 +99,12 @@ const columns: Array<ColumnDef<DAGResponse>> = [
   },
 ];
 
+const {
+  LAST_DAG_RUN_STATE: LAST_DAG_RUN_STATE_PARAM,
+  NAME_PATTERN: NAME_PATTERN_PARAM,
+  PAUSED: PAUSED_PARAM,
+}: SearchParamsKeysType = SearchParamsKeys;
+
 const cardDef: CardDef<DAGResponse> = {
   card: ({ row }) => <DagCard dag={row} />,
   meta: {
@@ -97,31 +112,61 @@ const cardDef: CardDef<DAGResponse> = {
   },
 };
 
-const PAUSED_PARAM = "paused";
-const STATE_PARAM = "last_dag_run_state";
-
 export const DagsList = () => {
-  const [searchParams] = useSearchParams();
+  const [searchParams, setSearchParams] = useSearchParams();
   const [display, setDisplay] = useState<"card" | "table">("card");
 
   const showPaused = searchParams.get(PAUSED_PARAM);
-  const lastDagRunState = searchParams.get(STATE_PARAM) as DagRunState;
+  const lastDagRunState = searchParams.get(
+    LAST_DAG_RUN_STATE_PARAM,
+  ) as DagRunState;
 
   const { setTableURLState, tableURLState } = useTableURLState();
   const { pagination, sorting } = tableURLState;
+  const [dagDisplayNamePattern, setDagDisplayNamePattern] = useState(
+    searchParams.get(NAME_PATTERN_PARAM) ?? undefined,
+  );
 
   // TODO: update API to accept multiple orderBy params
   const [sort] = sorting;
   const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : undefined;
 
-  const { data, error, isFetching, isLoading } = useDagServiceGetDags({
-    lastDagRunState,
-    limit: pagination.pageSize,
-    offset: pagination.pageIndex * pagination.pageSize,
-    onlyActive: true,
-    orderBy,
-    paused: showPaused === null ? undefined : showPaused === "true",
-  });
+  const handleSearchChange = ({
+    target: { value },
+  }: ChangeEvent<HTMLInputElement>) => {
+    if (value) {
+      searchParams.set(NAME_PATTERN_PARAM, value);
+    } else {
+      searchParams.delete(NAME_PATTERN_PARAM);
+    }
+    setSearchParams(searchParams);
+    setTableURLState({
+      pagination: { ...pagination, pageIndex: 0 },
+      sorting,
+    });
+    setDagDisplayNamePattern(value);
+  };
+
+  const { data, error, isFetching, isLoading } = useDagServiceGetDags(
+    {
+      dagDisplayNamePattern: Boolean(dagDisplayNamePattern)
+        ? `%${dagDisplayNamePattern}%`
+        : undefined,
+      lastDagRunState,
+      limit: pagination.pageSize,
+      offset: pagination.pageIndex * pagination.pageSize,
+      onlyActive: true,
+      orderBy,
+      paused: showPaused === null ? undefined : showPaused === "true",
+    },
+    [dagDisplayNamePattern, showPaused],
+    {
+      refetchOnMount: true,
+      refetchOnReconnect: false,
+      refetchOnWindowFocus: false,
+      staleTime: 5 * 60 * 1000,
+    },
+  );
 
   const handleSortChange = useCallback<ChangeEventHandler<HTMLSelectElement>>(
     ({ currentTarget: { value } }) => {
@@ -140,7 +185,10 @@ export const DagsList = () => {
       <VStack alignItems="none">
         <SearchBar
           buttonProps={{ isDisabled: true }}
-          inputProps={{ isDisabled: true }}
+          inputProps={{
+            defaultValue: dagDisplayNamePattern,
+            onChange: handleSearchChange,
+          }}
         />
         <DagsFilters />
         <HStack justifyContent="space-between">

Reply via email to