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/DagsFilters.tsx b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx index d36e2b2f8ef46..195774a876822 100644 --- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx +++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx @@ -18,10 +18,10 @@ */ import { Box, HStack } from "@chakra-ui/react"; import type { MultiValue } from "chakra-react-select"; -import { useCallback } from "react"; +import { useCallback, useState } from "react"; 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 { SearchParamsKeys, type SearchParamsKeysType } from "src/constants/searchParams"; import { useConfig } from "src/queries/useConfig"; @@ -66,8 +66,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")); @@ -136,6 +140,7 @@ export const DagsFilters = () => { searchParams.delete(TAGS_MATCH_MODE_PARAM); setSearchParams(searchParams); + setPattern(""); }; const handleTagModeChange = useCallback( @@ -166,11 +171,18 @@ export const DagsFilters = () => { showPaused={showPaused} /> { + void fetchNextPage(); + }} + onMenuScrollToTop={() => { + void fetchPreviousPage(); + }} onSelectTagsChange={handleSelectTagsChange} onTagModeChange={handleTagModeChange} + onUpdate={setPattern} selectedTags={selectedTags} tagFilterMode={tagFilterMode} - tags={data?.tags ?? []} + tags={data?.pages.flatMap((dagResponse) => dagResponse.tags) ?? []} /> diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/TagFilter.tsx b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/TagFilter.tsx index 57fa2b1089de1..c53429452f7cf 100644 --- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/TagFilter.tsx +++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/TagFilter.tsx @@ -23,16 +23,22 @@ import { useTranslation } from "react-i18next"; import { Switch } from "src/components/ui"; type Props = { + readonly onMenuScrollToBottom: () => void; + readonly onMenuScrollToTop: () => void; readonly onSelectTagsChange: (tags: MultiValue<{ label: string; value: string }>) => void; readonly onTagModeChange: ({ checked }: { checked: boolean }) => void; + readonly onUpdate: (newValue: string) => void; readonly selectedTags: Array; readonly tagFilterMode: string; readonly tags: Array; }; export const TagFilter = ({ + onMenuScrollToBottom, + onMenuScrollToTop, onSelectTagsChange, onTagModeChange, + onUpdate, selectedTags, tagFilterMode, tags, @@ -66,6 +72,9 @@ export const TagFilter = ({ isMulti noOptionsMessage={() => translate("table.noTagsFound")} onChange={onSelectTagsChange} + onInputChange={(newValue) => onUpdate(newValue)} + onMenuScrollToBottom={onMenuScrollToBottom} + onMenuScrollToTop={onMenuScrollToTop} options={tags.map((tag) => ({ label: tag, value: tag,