This is an automated email from the ASF dual-hosted git repository.

kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-1-test by this push:
     new ad9d78372fb feat: Implemented Filters UI for Asset View (#54640)
ad9d78372fb is described below

commit ad9d78372fbb209ed836f8264fe4c59a3efd8d79
Author: Chang-Yen (Brian) Li <[email protected]>
AuthorDate: Thu Sep 11 11:23:07 2025 -0500

    feat: Implemented Filters UI for Asset View (#54640)
    
    * feat: implemented filter timestamp
    
    * refactor: minor
    
    * feat: remove key for the date input
    
    * feat: add run and dag filter
    
    * feat: add selector for run type
    
    * fix: fix translation for selector label
    
    * fix: add empty option in the selector
    
    * refactor: move filter variables to utils
    
    * fix: bug of total_entries
    
    * feat: add label
    
    * feat: beautify UI
    
    * feat: add label for searchbar
    
    * feat: use backend filter
    
    * feat: move filter components to AssetEventsFilter
    
    * feat: add reset button
    
    * fix: remove unused translation package
    
    * fix: remove unused translation package
    
    * feat: update reset ui
    
    * refactor: adjust the order of variables
    
    * refactor: move paramkeys to global
    
    * feat: conditionally render filter
    
    * refactor: rename showFilters
    
    ---------
    
    Co-authored-by: Brent Bovenzi <[email protected]>
    (cherry picked from commit 9385762777ca1149ed3d1a28b039a684c65dabd9)
---
 .../ui/src/components/Assets/AssetEvents.tsx       |   4 +
 .../ui/src/components/Assets/AssetEventsFilter.tsx | 111 +++++++++++++++++++++
 .../src/airflow/ui/src/pages/Asset/AssetLayout.tsx |   9 ++
 3 files changed, 124 insertions(+)

diff --git a/airflow-core/src/airflow/ui/src/components/Assets/AssetEvents.tsx 
b/airflow-core/src/airflow/ui/src/components/Assets/AssetEvents.tsx
index ee1dfb25701..8f6b818f2d2 100644
--- a/airflow-core/src/airflow/ui/src/components/Assets/AssetEvents.tsx
+++ b/airflow-core/src/airflow/ui/src/components/Assets/AssetEvents.tsx
@@ -29,6 +29,7 @@ import { Select } from "src/components/ui";
 import { DataTable } from "../DataTable";
 import type { CardDef, TableState } from "../DataTable/types";
 import { AssetEvent } from "./AssetEvent";
+import { AssetEventsFilter } from "./AssetEventsFilter";
 
 const cardDef = (assetId?: number): CardDef<AssetEventResponse> => ({
   card: ({ row }) => <AssetEvent assetId={assetId} event={row} />,
@@ -43,6 +44,7 @@ type AssetEventProps = {
   readonly isLoading?: boolean;
   readonly setOrderBy?: (order: string) => void;
   readonly setTableUrlState?: (state: TableState) => void;
+  readonly showFilters?: boolean;
   readonly tableUrlState?: TableState;
   readonly titleKey?: string;
 };
@@ -53,6 +55,7 @@ export const AssetEvents = ({
   isLoading,
   setOrderBy,
   setTableUrlState,
+  showFilters = false,
   tableUrlState,
   titleKey,
   ...rest
@@ -101,6 +104,7 @@ export const AssetEvents = ({
           </Select.Root>
         )}
       </Flex>
+      {showFilters ? <AssetEventsFilter /> : null}
       <Separator mt={2.5} />
       <DataTable
         cardDef={cardDef(assetId)}
diff --git 
a/airflow-core/src/airflow/ui/src/components/Assets/AssetEventsFilter.tsx 
b/airflow-core/src/airflow/ui/src/components/Assets/AssetEventsFilter.tsx
new file mode 100644
index 00000000000..bfa46c9b7df
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/Assets/AssetEventsFilter.tsx
@@ -0,0 +1,111 @@
+/*!
+ * 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 { VStack, HStack, Box, Text, Button } from "@chakra-ui/react";
+import { useCallback, useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { LuX } from "react-icons/lu";
+import { useSearchParams } from "react-router-dom";
+
+import { DateTimeInput } from "src/components/DateTimeInput";
+import { SearchBar } from "src/components/SearchBar";
+import { SearchParamsKeys } from "src/constants/searchParams";
+
+const { DAG_ID, END_DATE, START_DATE, TASK_ID } = SearchParamsKeys;
+const filterKeys = [START_DATE, END_DATE, DAG_ID, TASK_ID];
+
+export const AssetEventsFilter = () => {
+  const { t: translate } = useTranslation("common");
+  const [searchParams, setSearchParams] = useSearchParams();
+  const startDate = searchParams.get(START_DATE) ?? "";
+  const endDate = searchParams.get(END_DATE) ?? "";
+  const dagId = searchParams.get(DAG_ID) ?? "";
+  const taskId = searchParams.get(TASK_ID) ?? "";
+  const [resetKey, setResetKey] = useState(0);
+  const handleFilterChange = useCallback(
+    (paramKey: string) => (value: string) => {
+      if (value === "") {
+        searchParams.delete(paramKey);
+      } else {
+        searchParams.set(paramKey, value);
+      }
+      setSearchParams(searchParams);
+    },
+    [searchParams, setSearchParams],
+  );
+  const filterCount = useMemo(
+    () => filterKeys.reduce((acc, key) => (searchParams.get(key) === null ? 
acc : acc + 1), 0),
+    [searchParams],
+  );
+  const handleResetFilters = useCallback(() => {
+    filterKeys.forEach((key) => searchParams.delete(key));
+    setSearchParams(searchParams);
+    setResetKey((prev) => prev + 1);
+  }, [searchParams, setSearchParams]);
+
+  return (
+    <VStack align="start" gap={4} paddingY="4px">
+      <HStack flexWrap="wrap" gap={4}>
+        <Box w="200px">
+          <Text fontSize="xs">{translate("common:table.from")}</Text>
+          <DateTimeInput
+            onChange={(event) => 
handleFilterChange(START_DATE)(event.target.value)}
+            value={startDate}
+          />
+        </Box>
+        <Box w="200px">
+          <Text fontSize="xs">{translate("common:table.to")}</Text>
+          <DateTimeInput
+            onChange={(event) => 
handleFilterChange(END_DATE)(event.target.value)}
+            value={endDate}
+          />
+        </Box>
+        <Box w="200px">
+          <Text 
fontSize="xs">{translate("common:filters.dagDisplayNamePlaceholder")}</Text>
+          <SearchBar
+            defaultValue={dagId}
+            hideAdvanced
+            hotkeyDisabled={true}
+            key={`dag-id-${resetKey}`}
+            onChange={handleFilterChange(DAG_ID)}
+            placeHolder={translate("common:filters.dagDisplayNamePlaceholder")}
+          />
+        </Box>
+        <Box w="200px">
+          <Text 
fontSize="xs">{translate("common:filters.taskIdPlaceholder")}</Text>
+          <SearchBar
+            defaultValue={taskId}
+            hideAdvanced
+            hotkeyDisabled={true}
+            key={`task-id-${resetKey}`}
+            onChange={handleFilterChange(TASK_ID)}
+            placeHolder={translate("common:filters.taskIdPlaceholder")}
+          />
+        </Box>
+        <Box alignSelf="end">
+          {filterCount > 0 && (
+            <Button onClick={handleResetFilters} size="md" variant="outline">
+              <LuX />
+              {translate("common:table.filterReset", { count: filterCount })}
+            </Button>
+          )}
+        </Box>
+      </HStack>
+    </VStack>
+  );
+};
diff --git a/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx 
b/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx
index 51a1442fcc9..e499afda799 100644
--- a/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx
@@ -22,12 +22,14 @@ import { useCallback } from "react";
 import { useTranslation } from "react-i18next";
 import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels";
 import { useParams } from "react-router-dom";
+import { useSearchParams } from "react-router-dom";
 
 import { useAssetServiceGetAsset, useAssetServiceGetAssetEvents } from 
"openapi/queries";
 import { AssetEvents } from "src/components/Assets/AssetEvents";
 import { BreadcrumbStats } from "src/components/BreadcrumbStats";
 import { useTableURLState } from "src/components/DataTable/useTableUrlState";
 import { ProgressBar } from "src/components/ui";
+import { SearchParamsKeys } from "src/constants/searchParams";
 
 import { AssetGraph } from "./AssetGraph";
 import { CreateAssetEvent } from "./CreateAssetEvent";
@@ -59,12 +61,18 @@ export const AssetLayout = () => {
     },
   ];
 
+  const { DAG_ID, END_DATE, START_DATE, TASK_ID } = SearchParamsKeys;
+  const [searchParams] = useSearchParams();
   const { data, isLoading: isLoadingEvents } = useAssetServiceGetAssetEvents(
     {
       assetId: asset?.id,
       limit: pagination.pageSize,
       offset: pagination.pageIndex * pagination.pageSize,
       orderBy,
+      sourceDagId: searchParams.get(DAG_ID) ?? undefined,
+      sourceTaskId: searchParams.get(TASK_ID) ?? undefined,
+      timestampGte: searchParams.get(START_DATE) ?? undefined,
+      timestampLte: searchParams.get(END_DATE) ?? undefined,
     },
     undefined,
     { enabled: Boolean(asset?.id) },
@@ -127,6 +135,7 @@ export const AssetLayout = () => {
                 isLoading={isLoadingEvents}
                 setOrderBy={setOrderBy}
                 setTableUrlState={setTableURLState}
+                showFilters={true}
                 tableUrlState={tableURLState}
               />
             </Box>

Reply via email to