diff --git a/airflow/ui/src/pages/Variables/Variables.tsx b/airflow/ui/src/pages/Variables/Variables.tsx index 15d263c49f7fbc..048a00d712894a 100644 --- a/airflow/ui/src/pages/Variables/Variables.tsx +++ b/airflow/ui/src/pages/Variables/Variables.tsx @@ -16,14 +16,24 @@ * specific language governing permissions and limitations * under the License. */ -import { Box } from "@chakra-ui/react"; +import { + Box, + Button, + createListCollection, + HStack, + Input, +} from "@chakra-ui/react"; import type { ColumnDef } from "@tanstack/react-table"; +import { useState, useEffect } from "react"; +import { FiUpload, FiFilter } from "react-icons/fi"; +import { IoAddCircleOutline } from "react-icons/io5"; import { useVariableServiceGetVariables } from "openapi/queries"; import type { VariableResponse } from "openapi/requests/types.gen"; import { DataTable } from "src/components/DataTable"; import { useTableURLState } from "src/components/DataTable/useTableUrlState"; import { ErrorAlert } from "src/components/ErrorAlert"; +import { CloseButton, Select } from "src/components/ui"; const variablesColumn = (): Array> => [ { @@ -52,6 +62,24 @@ const variablesColumn = (): Array> => [ }, ]; +const filterOptions = createListCollection({ + items: [ + { label: "Key", value: "key" }, + { label: "Value", value: "value" }, + ], +}); + +const filterOperatorOptions = createListCollection({ + items: [ + { label: "Starts With", value: "starts_with" }, + { label: "Ends With", value: "ends_with" }, + { label: "Contains", value: "contains" }, + { label: "Equals", value: "equals" }, + { label: "Not Starts With", value: "not_starts_with" }, + { label: "Not Ends With", value: "not_ends_with" }, + ], +}); + export const Variables = () => { const { setTableURLState, tableURLState } = useTableURLState({ sorting: [{ desc: true, id: "key" }], @@ -61,6 +89,12 @@ export const Variables = () => { const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : undefined; + const [filterType, setFilterType] = useState(undefined); + const [filterOperator, setFilterOperator] = useState( + undefined, + ); + const [filterText, setFilterText] = useState(""); + const { data, error: VariableError, @@ -72,12 +106,146 @@ export const Variables = () => { orderBy, }); + const [filteredData, setFilteredData] = useState>([]); + + useEffect(() => { + if (data?.variables) { + let filtered = data.variables; + + if (filterText.trim()) { + filtered = filtered.filter((item) => { + const valueToFilter = + filterType === "key" ? item.key : (item.value ?? ""); + + switch (filterOperator) { + case "contains": + return valueToFilter.includes(filterText); + case "ends_with": + return valueToFilter.endsWith(filterText); + case "equals": + return valueToFilter === filterText; + case "not_ends_with": + return !valueToFilter.endsWith(filterText); + case "not_starts_with": + return !valueToFilter.startsWith(filterText); + case "starts_with": + return valueToFilter.startsWith(filterText); + default: + return true; + } + }); + } + + setFilteredData(filtered); + } else { + setFilteredData([]); + } + + // Reset pagination when filter changes + setTableURLState({ + pagination: { ...pagination, pageIndex: 0 }, + sorting, + }); + }, [ + filterText, + filterType, + filterOperator, + data, + pagination, + sorting, + setTableURLState, + ]); + return ( + + + + setFilterType(event.value[0])} + size="xs" + value={[filterType ?? ""]} + width="90px" + > + + + + + {filterOptions.items.map((option) => ( + + {option.label} + + ))} + + + setFilterOperator(event.value[0])} + size="xs" + value={[filterOperator ?? ""]} + width="140px" + > + + + + + {filterOperatorOptions.items.map((option) => ( + + {option.label} + + ))} + + + setFilterText(event.target.value)} + outline="none" + placeholder="Filter text" + size="xs" + value={filterText} + width="200px" + /> + {filterText === "" ? undefined : ( + { + setFilterText(""); + }} + size="xs" + /> + )} + + + + + + + + { modelName="Variable" onStateChange={setTableURLState} skeletonCount={undefined} - total={data ? data.total_entries : 0} + total={filteredData.length} /> );