diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/useDagsInfinite.ts b/airflow-core/src/airflow/ui/openapi-gen/queries/useDagsInfinite.ts new file mode 100644 index 0000000000000..2776977ce46d3 --- /dev/null +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/useDagsInfinite.ts @@ -0,0 +1,58 @@ +/*! + * 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 { InfiniteData, useInfiniteQuery, UseInfiniteQueryOptions } from "@tanstack/react-query"; + +import { DagService } from "openapi/requests/services.gen"; +import { DAGTagCollectionResponse } from "openapi/requests/types.gen"; + +import * as Common from "./common"; + +export const useDagTagsInfinite = = unknown[]>( + { + limit, + orderBy, + tagNamePattern, + }: { + limit?: number; + orderBy?: string; + tagNamePattern?: string; + } = {}, + queryKey?: TQueryKey, + options?: Omit< + UseInfiniteQueryOptions< + DAGTagCollectionResponse, + TError, + InfiniteData, + DAGTagCollectionResponse, + unknown[], + number + >, + "queryKey" | "queryFn" + >, +) => + useInfiniteQuery({ + queryKey: Common.UseDagServiceGetDagTagsKeyFn({ limit, orderBy, tagNamePattern }, queryKey), + queryFn: ({ pageParam }) => DagService.getDagTags({ limit, offset: pageParam, orderBy, tagNamePattern }), + initialPageParam: 0, + getNextPageParam: (lastPage, _allPages, lastPageParam, _allPageParams) => + lastPageParam < lastPage.total_entries ? lastPage.tags.length + lastPageParam : undefined, + getPreviousPageParam: (firstPage, _allPages, firstPageParam, _allPageParams) => + firstPageParam > 0 ? -firstPage.tags.length + firstPageParam : undefined, + ...options, + }); diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters.tsx b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters.tsx index 8e3ac9c1b0d43..6f650c8c6972b 100644 --- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters.tsx +++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters.tsx @@ -25,11 +25,11 @@ import { type SelectValueChangeDetails, } from "@chakra-ui/react"; import { Select as ReactSelect, type MultiValue } from "chakra-react-select"; -import { useCallback } from "react"; +import { useCallback, useState } from "react"; import { LuX } from "react-icons/lu"; import { useSearchParams } from "react-router-dom"; -import { useDagServiceGetDagTags } from "openapi/queries"; +import { useDagTagsInfinite } from "openapi/queries/useDagsInfinite"; import { useTableURLState } from "src/components/DataTable/useTableUrlState"; import { QuickFilterButton } from "src/components/QuickFilterButton"; import { StateBadge } from "src/components/StateBadge"; @@ -64,8 +64,12 @@ export const DagsFilters = () => { const isFailed = state === "failed"; const isSuccess = state === "success"; - const { data } = useDagServiceGetDagTags({ + const [pattern, setPattern] = useState(""); + + const { data, fetchNextPage, fetchPreviousPage } = useDagTagsInfinite({ + limit: 10, orderBy: "name", + tagNamePattern: pattern, }); const hidePausedDagsByDefault = Boolean(useConfig("hide_paused_dags_by_default")); @@ -132,6 +136,7 @@ export const DagsFilters = () => { searchParams.delete(TAGS_PARAM); setSearchParams(searchParams); + setPattern(""); }; let filterCount = 0; @@ -222,11 +227,20 @@ export const DagsFilters = () => { isMulti noOptionsMessage={() => "No tags found"} onChange={handleSelectTagsChange} + onInputChange={(newValue) => setPattern(newValue)} + onMenuScrollToBottom={() => { + void fetchNextPage(); + }} + onMenuScrollToTop={() => { + void fetchPreviousPage(); + }} options={ - data?.tags.map((tag) => ({ - label: tag, - value: tag, - })) ?? [] + data?.pages.flatMap((response) => + response.tags.map((tag) => ({ + label: tag, + value: tag, + })), + ) ?? [] } placeholder="Filter by tag" value={selectedTags.map((tag) => ({