From af0837cffabe4e517550c40c18d99e07dedbb6b1 Mon Sep 17 00:00:00 2001 From: Brent Bovenzi Date: Thu, 7 Nov 2024 14:19:48 -0500 Subject: [PATCH] Add clear button to search bar (#43795) --- airflow/ui/src/components/SearchBar.test.tsx | 51 ++++++++++++++ airflow/ui/src/components/SearchBar.tsx | 73 ++++++++++++-------- airflow/ui/src/pages/DagsList/DagsList.tsx | 12 ++-- 3 files changed, 101 insertions(+), 35 deletions(-) create mode 100644 airflow/ui/src/components/SearchBar.test.tsx diff --git a/airflow/ui/src/components/SearchBar.test.tsx b/airflow/ui/src/components/SearchBar.test.tsx new file mode 100644 index 0000000000000..06d4a90d4f362 --- /dev/null +++ b/airflow/ui/src/components/SearchBar.test.tsx @@ -0,0 +1,51 @@ +/*! + * 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 { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; + +import { Wrapper } from "src/utils/Wrapper"; + +import { SearchBar } from "./SearchBar"; + +describe("Test SearchBar", () => { + it("Renders and clear button works", async () => { + render(, { + wrapper: Wrapper, + }); + + const input = screen.getByTestId("search-dags"); + + expect(screen.getByText("Advanced Search")).toBeDefined(); + expect(screen.queryByTestId("clear-search")).toBeNull(); + + fireEvent.change(input, { target: { value: "search" } }); + + await waitFor(() => + expect((input as HTMLInputElement).value).toBe("search"), + ); + + const clearButton = screen.getByTestId("clear-search"); + + expect(clearButton).toBeDefined(); + + fireEvent.click(clearButton); + + expect((input as HTMLInputElement).value).toBe(""); + }); +}); diff --git a/airflow/ui/src/components/SearchBar.tsx b/airflow/ui/src/components/SearchBar.tsx index 9c72b63443ba6..de9059b78deed 100644 --- a/airflow/ui/src/components/SearchBar.tsx +++ b/airflow/ui/src/components/SearchBar.tsx @@ -16,57 +16,76 @@ * specific language governing permissions and limitations * under the License. */ -import { - Button, - Input, - type ButtonProps, - type InputProps, -} from "@chakra-ui/react"; -import type { ChangeEvent } from "react"; +import { Button, Input, type ButtonProps } from "@chakra-ui/react"; +import { useState, type ChangeEvent } from "react"; import { FiSearch } from "react-icons/fi"; import { useDebouncedCallback } from "use-debounce"; -import { InputGroup, type InputGroupProps } from "./ui"; +import { CloseButton, InputGroup, type InputGroupProps } from "./ui"; const debounceDelay = 200; +type Props = { + readonly buttonProps?: ButtonProps; + readonly defaultValue: string; + readonly groupProps?: InputGroupProps; + readonly onChange: (value: string) => void; +}; + export const SearchBar = ({ buttonProps, + defaultValue, groupProps, - inputProps, -}: { - readonly buttonProps?: ButtonProps; - readonly groupProps?: InputGroupProps; - readonly inputProps?: InputProps; -}) => { + onChange, +}: Props) => { const handleSearchChange = useDebouncedCallback( - (event: ChangeEvent) => inputProps?.onChange?.(event), + (val: string) => onChange(val), debounceDelay, ); + const [value, setValue] = useState(defaultValue); + + const onSearchChange = (event: ChangeEvent) => { + setValue(event.target.value); + handleSearchChange(event.target.value); + }; + return ( - Advanced Search - + <> + {Boolean(value) ? ( + { + setValue(""); + onChange(""); + }} + size="xs" + /> + ) : undefined} + + } startElement={} > ); diff --git a/airflow/ui/src/pages/DagsList/DagsList.tsx b/airflow/ui/src/pages/DagsList/DagsList.tsx index c787b6874dd61..be212c66a87dc 100644 --- a/airflow/ui/src/pages/DagsList/DagsList.tsx +++ b/airflow/ui/src/pages/DagsList/DagsList.tsx @@ -26,7 +26,7 @@ import { type SelectValueChangeDetails, } from "@chakra-ui/react"; import type { ColumnDef } from "@tanstack/react-table"; -import { type ChangeEvent, useCallback, useState } from "react"; +import { useCallback, useState } from "react"; import { Link as RouterLink, useSearchParams } from "react-router-dom"; import { useLocalStorage } from "usehooks-ts"; @@ -170,9 +170,7 @@ export const DagsList = () => { const [sort] = sorting; const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : undefined; - const handleSearchChange = ({ - target: { value }, - }: ChangeEvent) => { + const handleSearchChange = (value: string) => { if (value) { searchParams.set(NAME_PATTERN_PARAM, value); } else { @@ -217,10 +215,8 @@ export const DagsList = () => {