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 f4e6656a1b448..a218285ba4a7c 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 @@ -26,6 +26,7 @@ import { ResetButton } from "src/components/ui"; import { SearchParamsKeys, type SearchParamsKeysType } from "src/constants/searchParams"; import { useConfig } from "src/queries/useConfig"; import { useDagTagsInfinite } from "src/queries/useDagTagsInfinite"; +import { getFilterCount } from "src/utils/filterUtils"; import { FavoriteFilter } from "./FavoriteFilter"; import { PausedFilter } from "./PausedFilter"; @@ -41,32 +42,6 @@ const { TAGS_MATCH_MODE: TAGS_MATCH_MODE_PARAM, }: SearchParamsKeysType = SearchParamsKeys; -type FilterOptions = { - selectedTags: Array; - showFavorites: string | null; - showPaused: string | null; - state: string | null; -}; - -const getFilterCount = ({ selectedTags, showFavorites, showPaused, state }: FilterOptions) => { - let count = 0; - - if (state !== null) { - count += 1; - } - if (showPaused !== null) { - count += 1; - } - if (selectedTags.length > 0) { - count += 1; - } - if (showFavorites !== null) { - count += 1; - } - - return count; -}; - export const DagsFilters = () => { const [searchParams, setSearchParams] = useSearchParams(); diff --git a/airflow-core/src/airflow/ui/src/pages/Events/EventsFilters.tsx b/airflow-core/src/airflow/ui/src/pages/Events/EventsFilters.tsx index 00a4b25c63c1c..75af3d4bc3674 100644 --- a/airflow-core/src/airflow/ui/src/pages/Events/EventsFilters.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Events/EventsFilters.tsx @@ -26,8 +26,7 @@ import { DateTimeInput } from "src/components/DateTimeInput"; import { SearchBar } from "src/components/SearchBar"; import { ResetButton } from "src/components/ui"; import { SearchParamsKeys } from "src/constants/searchParams"; - -import { getFilterCount } from "./filterUtils"; +import { getFilterCount } from "src/utils/filterUtils"; const { AFTER: AFTER_PARAM, diff --git a/airflow-core/src/airflow/ui/src/pages/Events/filterUtils.ts b/airflow-core/src/airflow/ui/src/pages/Events/filterUtils.ts deleted file mode 100644 index db448155b1457..0000000000000 --- a/airflow-core/src/airflow/ui/src/pages/Events/filterUtils.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*! - * 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 type FilterOptions = { - after: string | null; - before: string | null; - dagId: string | null; - eventType: string | null; - mapIndex: string | null; - runId: string | null; - taskId: string | null; - tryNumber: string | null; - user: string | null; -}; - -export const getFilterCount = ({ - after, - before, - dagId, - eventType, - mapIndex, - runId, - taskId, - tryNumber, - user, -}: FilterOptions) => { - let count = 0; - - if (after !== null) { - count += 1; - } - if (before !== null) { - count += 1; - } - if (dagId !== null) { - count += 1; - } - if (eventType !== null) { - count += 1; - } - if (mapIndex !== null) { - count += 1; - } - if (runId !== null) { - count += 1; - } - if (taskId !== null) { - count += 1; - } - if (tryNumber !== null) { - count += 1; - } - if (user !== null) { - count += 1; - } - - return count; -}; diff --git a/airflow-core/src/airflow/ui/src/utils/filterUtils.test.ts b/airflow-core/src/airflow/ui/src/utils/filterUtils.test.ts new file mode 100644 index 0000000000000..f11b2ee6efa52 --- /dev/null +++ b/airflow-core/src/airflow/ui/src/utils/filterUtils.test.ts @@ -0,0 +1,106 @@ +/*! + * 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 { describe, it, expect } from "vitest"; + +import { getFilterCount } from "./filterUtils"; + +describe("getFilterCount", () => { + it("counts non-undefined values correctly", () => { + const filters = { + count: 5, + enabled: true, + name: "test", + nullValue: undefined, + status: "active", + undefinedValue: undefined, + }; + + expect(getFilterCount(filters)).toBe(4); + }); + + it("handles array fields correctly", () => { + const filters = { + emptyTags: [], + nullValue: undefined, + status: "active", + tags: ["tag1", "tag2"], + }; + + expect(getFilterCount(filters)).toBe(2); + }); + + it("returns 0 for empty filters", () => { + expect(getFilterCount({})).toBe(0); + }); + + it("returns 0 when all values are undefined/undefined", () => { + const filters = { + nullValue: undefined, + undefinedValue: undefined, + }; + + expect(getFilterCount(filters)).toBe(0); + }); + + it("handles boolean values correctly", () => { + const filters = { + disabled: false, + enabled: true, + nullValue: undefined, + }; + + expect(getFilterCount(filters)).toBe(2); + }); + + it("handles zero values correctly", () => { + const filters = { + amount: 0.0, + count: 0, + nullValue: undefined, + }; + + expect(getFilterCount(filters)).toBe(2); + }); + + it("handles empty strings correctly", () => { + const filters = { + emptyString: "", + nonEmptyString: "value", + nullValue: undefined, + }; + + expect(getFilterCount(filters)).toBe(2); + }); +}); + +it("handles complex filters pattern", () => { + const filters = { + after: "2024-01-01", + before: ["tag1", "tag2"], + dagId: "test-dag", + eventType: "event", + mapIndex: undefined, + runId: "test-run", + taskId: undefined, + tryNumber: undefined, + user: "admin", + }; + + expect(getFilterCount(filters)).toBe(6); +}); diff --git a/airflow-core/src/airflow/ui/src/utils/filterUtils.ts b/airflow-core/src/airflow/ui/src/utils/filterUtils.ts new file mode 100644 index 0000000000000..9df5a5a9995a8 --- /dev/null +++ b/airflow-core/src/airflow/ui/src/utils/filterUtils.ts @@ -0,0 +1,27 @@ +/*! + * 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 const getFilterCount = (filters: Record): number => + Object.values(filters).reduce((count: number, value) => { + if (Array.isArray(value)) { + return count + (value.length > 0 ? 1 : 0); + } + + return count + (value !== null && value !== undefined ? 1 : 0); + }, 0);