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 <[email protected]>
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 <[email protected]>
* 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 <[email protected]>
---
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([email protected]([email protected]))([email protected])
+ use-debounce:
+ specifier: ^10.0.3
+ version: 10.0.3([email protected])
devDependencies:
'@7nohe/openapi-react-query-codegen':
specifier: ^1.6.0
@@ -3221,6 +3224,12 @@ packages:
'@types/react':
optional: true
+ [email protected]:
+ resolution: {integrity:
sha512-DxQSI9ZKso689WM1mjgGU3ozcxU1TJElBJ3X6S4SMzMNcm2lVH0AHmyXB+K7ewjz2BSUKJTDqTcwtSMRfB89dg==}
+ engines: {node: '>= 16.0.0'}
+ peerDependencies:
+ react: '*'
+
[email protected]:
resolution: {integrity:
sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
peerDependencies:
@@ -6892,6 +6901,10 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.5
+ [email protected]([email protected]):
+ dependencies:
+ react: 18.3.1
+
[email protected](@types/[email protected])([email protected]):
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">