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 dde38cf3e48 Fix dag import error modal pagination (#55719)
dde38cf3e48 is described below

commit dde38cf3e485da9e0abf8ed00eac6e400b28f3dc
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Tue Sep 16 19:44:50 2025 +0200

    Fix dag import error modal pagination (#55719)
    
    (cherry picked from commit 0a02ded3e67ee5ef3f8f3ccc7554d08e4150aa2c)
---
 .../src/airflow/api_fastapi/common/parameters.py   |  6 ++++
 .../core_api/openapi/v2-rest-api-generated.yaml    | 12 ++++++++
 .../core_api/routes/public/import_error.py         |  4 +++
 .../src/airflow/ui/openapi-gen/queries/common.ts   |  5 +--
 .../ui/openapi-gen/queries/ensureQueryData.ts      |  6 ++--
 .../src/airflow/ui/openapi-gen/queries/prefetch.ts |  6 ++--
 .../src/airflow/ui/openapi-gen/queries/queries.ts  |  6 ++--
 .../src/airflow/ui/openapi-gen/queries/suspense.ts |  6 ++--
 .../ui/openapi-gen/requests/services.gen.ts        |  4 ++-
 .../airflow/ui/openapi-gen/requests/types.gen.ts   |  4 +++
 .../src/pages/Dashboard/Stats/DAGImportErrors.tsx  |  3 +-
 .../pages/Dashboard/Stats/DAGImportErrorsModal.tsx | 36 ++++++++++------------
 12 files changed, 66 insertions(+), 32 deletions(-)

diff --git a/airflow-core/src/airflow/api_fastapi/common/parameters.py 
b/airflow-core/src/airflow/api_fastapi/common/parameters.py
index 896c00d487d..bef1659c12d 100644
--- a/airflow-core/src/airflow/api_fastapi/common/parameters.py
+++ b/airflow-core/src/airflow/api_fastapi/common/parameters.py
@@ -53,6 +53,7 @@ from airflow.models.dag import DagModel, DagTag
 from airflow.models.dag_favorite import DagFavorite
 from airflow.models.dag_version import DagVersion
 from airflow.models.dagrun import DagRun
+from airflow.models.errors import ParseImportError
 from airflow.models.hitl import HITLDetail
 from airflow.models.pool import Pool
 from airflow.models.taskinstance import TaskInstance
@@ -1049,3 +1050,8 @@ QueryHITLDetailRespondedUserNameFilter = Annotated[
         )
     ),
 ]
+
+# Parse Import Errors
+QueryParseImportErrorFilenamePatternSearch = Annotated[
+    _SearchParam, Depends(search_param_factory(ParseImportError.filename, 
"filename_pattern"))
+]
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
 
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
index a54062a172a..7b28e615e10 100644
--- 
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
+++ 
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
@@ -4040,6 +4040,18 @@ paths:
           default:
           - id
           title: Order By
+      - name: filename_pattern
+        in: query
+        required: false
+        schema:
+          anyOf:
+          - type: string
+          - type: 'null'
+          description: "SQL LIKE expression \u2014 use `%` / `_` wildcards 
(e.g. `%customer_%`).\
+            \ Regular expressions are **not** supported."
+          title: Filename Pattern
+        description: "SQL LIKE expression \u2014 use `%` / `_` wildcards (e.g. 
`%customer_%`).\
+          \ Regular expressions are **not** supported."
       responses:
         '200':
           description: Successful Response
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/import_error.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/import_error.py
index a989a7eef16..bb1e03a33ae 100644
--- 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/import_error.py
+++ 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/import_error.py
@@ -36,6 +36,7 @@ from airflow.api_fastapi.common.db.common import (
 from airflow.api_fastapi.common.parameters import (
     QueryLimit,
     QueryOffset,
+    QueryParseImportErrorFilenamePatternSearch,
     SortParam,
 )
 from airflow.api_fastapi.common.router import AirflowRouter
@@ -126,12 +127,14 @@ def get_import_errors(
             ).dynamic_depends()
         ),
     ],
+    filename_pattern: QueryParseImportErrorFilenamePatternSearch,
     session: SessionDep,
     user: GetUserDep,
 ) -> ImportErrorCollectionResponse:
     """Get all import errors."""
     import_errors_select, total_entries = paginated_select(
         statement=select(ParseImportError),
+        filters=[filename_pattern],
         order_by=order_by,
         offset=offset,
         limit=limit,
@@ -174,6 +177,7 @@ def get_import_errors(
     # Paginate the import errors query
     import_errors_select, total_entries = paginated_select(
         statement=import_errors_stmt,
+        filters=[filename_pattern],
         order_by=order_by,
         offset=offset,
         limit=limit,
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
index 4cc84c1ad3a..5e04cd63524 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -593,11 +593,12 @@ export const UseImportErrorServiceGetImportErrorKeyFn = 
({ importErrorId }: {
 export type ImportErrorServiceGetImportErrorsDefaultResponse = 
Awaited<ReturnType<typeof ImportErrorService.getImportErrors>>;
 export type ImportErrorServiceGetImportErrorsQueryResult<TData = 
ImportErrorServiceGetImportErrorsDefaultResponse, TError = unknown> = 
UseQueryResult<TData, TError>;
 export const useImportErrorServiceGetImportErrorsKey = 
"ImportErrorServiceGetImportErrors";
-export const UseImportErrorServiceGetImportErrorsKeyFn = ({ limit, offset, 
orderBy }: {
+export const UseImportErrorServiceGetImportErrorsKeyFn = ({ filenamePattern, 
limit, offset, orderBy }: {
+  filenamePattern?: string;
   limit?: number;
   offset?: number;
   orderBy?: string[];
-} = {}, queryKey?: Array<unknown>) => 
[useImportErrorServiceGetImportErrorsKey, ...(queryKey ?? [{ limit, offset, 
orderBy }])];
+} = {}, queryKey?: Array<unknown>) => 
[useImportErrorServiceGetImportErrorsKey, ...(queryKey ?? [{ filenamePattern, 
limit, offset, orderBy }])];
 export type JobServiceGetJobsDefaultResponse = Awaited<ReturnType<typeof 
JobService.getJobs>>;
 export type JobServiceGetJobsQueryResult<TData = 
JobServiceGetJobsDefaultResponse, TError = unknown> = UseQueryResult<TData, 
TError>;
 export const useJobServiceGetJobsKey = "JobServiceGetJobs";
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
index 39129114dc5..beb84ec2793 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -1128,14 +1128,16 @@ export const 
ensureUseImportErrorServiceGetImportErrorData = (queryClient: Query
 * @param data.limit
 * @param data.offset
 * @param data.orderBy
+* @param data.filenamePattern SQL LIKE expression — use `%` / `_` wildcards 
(e.g. `%customer_%`). Regular expressions are **not** supported.
 * @returns ImportErrorCollectionResponse Successful Response
 * @throws ApiError
 */
-export const ensureUseImportErrorServiceGetImportErrorsData = (queryClient: 
QueryClient, { limit, offset, orderBy }: {
+export const ensureUseImportErrorServiceGetImportErrorsData = (queryClient: 
QueryClient, { filenamePattern, limit, offset, orderBy }: {
+  filenamePattern?: string;
   limit?: number;
   offset?: number;
   orderBy?: string[];
-} = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ limit, offset, orderBy }), 
queryFn: () => ImportErrorService.getImportErrors({ limit, offset, orderBy }) 
});
+} = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ filenamePattern, limit, 
offset, orderBy }), queryFn: () => ImportErrorService.getImportErrors({ 
filenamePattern, limit, offset, orderBy }) });
 /**
 * Get Jobs
 * Get all jobs.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
index c9d8961be12..7eba9bf7b73 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -1128,14 +1128,16 @@ export const 
prefetchUseImportErrorServiceGetImportError = (queryClient: QueryCl
 * @param data.limit
 * @param data.offset
 * @param data.orderBy
+* @param data.filenamePattern SQL LIKE expression — use `%` / `_` wildcards 
(e.g. `%customer_%`). Regular expressions are **not** supported.
 * @returns ImportErrorCollectionResponse Successful Response
 * @throws ApiError
 */
-export const prefetchUseImportErrorServiceGetImportErrors = (queryClient: 
QueryClient, { limit, offset, orderBy }: {
+export const prefetchUseImportErrorServiceGetImportErrors = (queryClient: 
QueryClient, { filenamePattern, limit, offset, orderBy }: {
+  filenamePattern?: string;
   limit?: number;
   offset?: number;
   orderBy?: string[];
-} = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ limit, offset, orderBy }), 
queryFn: () => ImportErrorService.getImportErrors({ limit, offset, orderBy }) 
});
+} = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ filenamePattern, limit, 
offset, orderBy }), queryFn: () => ImportErrorService.getImportErrors({ 
filenamePattern, limit, offset, orderBy }) });
 /**
 * Get Jobs
 * Get all jobs.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
index 3594ae23797..31a2a9eb6b5 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -1128,14 +1128,16 @@ export const useImportErrorServiceGetImportError = 
<TData = Common.ImportErrorSe
 * @param data.limit
 * @param data.offset
 * @param data.orderBy
+* @param data.filenamePattern SQL LIKE expression — use `%` / `_` wildcards 
(e.g. `%customer_%`). Regular expressions are **not** supported.
 * @returns ImportErrorCollectionResponse Successful Response
 * @throws ApiError
 */
-export const useImportErrorServiceGetImportErrors = <TData = 
Common.ImportErrorServiceGetImportErrorsDefaultResponse, TError = unknown, 
TQueryKey extends Array<unknown> = unknown[]>({ limit, offset, orderBy }: {
+export const useImportErrorServiceGetImportErrors = <TData = 
Common.ImportErrorServiceGetImportErrorsDefaultResponse, TError = unknown, 
TQueryKey extends Array<unknown> = unknown[]>({ filenamePattern, limit, offset, 
orderBy }: {
+  filenamePattern?: string;
   limit?: number;
   offset?: number;
   orderBy?: string[];
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ limit, offset, orderBy }, 
queryKey), queryFn: () => ImportErrorService.getImportErrors({ limit, offset, 
orderBy }) as TData, ...options });
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ filenamePattern, limit, 
offset, orderBy }, queryKey), queryFn: () => 
ImportErrorService.getImportErrors({ filenamePattern, limit, offset, orderBy }) 
as TData, ...options });
 /**
 * Get Jobs
 * Get all jobs.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
index 61cb9a59f07..bb5332268da 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -1128,14 +1128,16 @@ export const 
useImportErrorServiceGetImportErrorSuspense = <TData = Common.Impor
 * @param data.limit
 * @param data.offset
 * @param data.orderBy
+* @param data.filenamePattern SQL LIKE expression — use `%` / `_` wildcards 
(e.g. `%customer_%`). Regular expressions are **not** supported.
 * @returns ImportErrorCollectionResponse Successful Response
 * @throws ApiError
 */
-export const useImportErrorServiceGetImportErrorsSuspense = <TData = 
Common.ImportErrorServiceGetImportErrorsDefaultResponse, TError = unknown, 
TQueryKey extends Array<unknown> = unknown[]>({ limit, offset, orderBy }: {
+export const useImportErrorServiceGetImportErrorsSuspense = <TData = 
Common.ImportErrorServiceGetImportErrorsDefaultResponse, TError = unknown, 
TQueryKey extends Array<unknown> = unknown[]>({ filenamePattern, limit, offset, 
orderBy }: {
+  filenamePattern?: string;
   limit?: number;
   offset?: number;
   orderBy?: string[];
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ limit, offset, orderBy }, 
queryKey), queryFn: () => ImportErrorService.getImportErrors({ limit, offset, 
orderBy }) as TData, ...options });
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ filenamePattern, limit, 
offset, orderBy }, queryKey), queryFn: () => 
ImportErrorService.getImportErrors({ filenamePattern, limit, offset, orderBy }) 
as TData, ...options });
 /**
 * Get Jobs
 * Get all jobs.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
index 9cd340c85a0..d9abbf77eb9 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -2867,6 +2867,7 @@ export class ImportErrorService {
      * @param data.limit
      * @param data.offset
      * @param data.orderBy
+     * @param data.filenamePattern SQL LIKE expression — use `%` / `_` 
wildcards (e.g. `%customer_%`). Regular expressions are **not** supported.
      * @returns ImportErrorCollectionResponse Successful Response
      * @throws ApiError
      */
@@ -2877,7 +2878,8 @@ export class ImportErrorService {
             query: {
                 limit: data.limit,
                 offset: data.offset,
-                order_by: data.orderBy
+                order_by: data.orderBy,
+                filename_pattern: data.filenamePattern
             },
             errors: {
                 401: 'Unauthorized',
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index 5ee9f5edf90..2f2789b2126 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -2889,6 +2889,10 @@ export type GetImportErrorData = {
 export type GetImportErrorResponse = ImportErrorResponse;
 
 export type GetImportErrorsData = {
+    /**
+     * SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). 
Regular expressions are **not** supported.
+     */
+    filenamePattern?: string | null;
     limit?: number;
     offset?: number;
     orderBy?: Array<(string)>;
diff --git 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx
index 2966fee23cd..e6fc9d1f3e2 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx
@@ -35,7 +35,6 @@ export const DAGImportErrors = ({ iconOnly = false }: { 
readonly iconOnly?: bool
 
   const { data, error, isLoading } = useImportErrorServiceGetImportErrors();
   const importErrorsCount = data?.total_entries ?? 0;
-  const importErrors = data?.import_errors ?? [];
 
   if (isLoading) {
     return <Skeleton height="9" width="225px" />;
@@ -70,7 +69,7 @@ export const DAGImportErrors = ({ iconOnly = false }: { 
readonly iconOnly?: bool
           onClick={onOpen}
         />
       )}
-      <DAGImportErrorsModal importErrors={importErrors} onClose={onClose} 
open={open} />
+      <DAGImportErrorsModal onClose={onClose} open={open} />
     </Box>
   );
 };
diff --git 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrorsModal.tsx
 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrorsModal.tsx
index f52d51086e6..e38d06a26f4 100644
--- 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrorsModal.tsx
+++ 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrorsModal.tsx
@@ -17,34 +17,35 @@
  * under the License.
  */
 import { Heading, Text, HStack } from "@chakra-ui/react";
-import { useEffect, useState } from "react";
+import { useState } from "react";
 import { useTranslation } from "react-i18next";
 import { LuFileWarning } from "react-icons/lu";
 import { PiFilePy } from "react-icons/pi";
 
-import type { ImportErrorResponse } from "openapi/requests/types.gen";
+import { useImportErrorServiceGetImportErrors } from "openapi/queries";
 import { SearchBar } from "src/components/SearchBar";
 import Time from "src/components/Time";
 import { Accordion, Dialog } from "src/components/ui";
 import { Pagination } from "src/components/ui/Pagination";
 
 type ImportDAGErrorModalProps = {
-  importErrors: Array<ImportErrorResponse>;
   onClose: () => void;
   open: boolean;
 };
 
 const PAGE_LIMIT = 15;
 
-export const DAGImportErrorsModal: React.FC<ImportDAGErrorModalProps> = ({ 
importErrors, onClose, open }) => {
+export const DAGImportErrorsModal: React.FC<ImportDAGErrorModalProps> = ({ 
onClose, open }) => {
   const [page, setPage] = useState(1);
   const [searchQuery, setSearchQuery] = useState("");
-  const [filteredErrors, setFilteredErrors] = useState(importErrors);
-  const { t: translate } = useTranslation(["dashboard", "components"]);
 
-  const startRange = (page - 1) * PAGE_LIMIT;
-  const endRange = startRange + PAGE_LIMIT;
-  const visibleItems = filteredErrors.slice(startRange, endRange);
+  const { data } = useImportErrorServiceGetImportErrors({
+    filenamePattern: searchQuery || undefined,
+    limit: PAGE_LIMIT,
+    offset: PAGE_LIMIT * (page - 1),
+  });
+
+  const { t: translate } = useTranslation(["dashboard", "components"]);
 
   const onOpenChange = () => {
     setSearchQuery("");
@@ -52,13 +53,10 @@ export const DAGImportErrorsModal: 
React.FC<ImportDAGErrorModalProps> = ({ impor
     onClose();
   };
 
-  useEffect(() => {
-    const query = searchQuery.toLowerCase();
-    const filtered = importErrors.filter((error) => 
error.filename.toLowerCase().includes(query));
-
-    setFilteredErrors(filtered);
+  const handleSearchChange = (value: string) => {
+    setSearchQuery(value);
     setPage(1);
-  }, [searchQuery, importErrors]);
+  };
 
   return (
     <Dialog.Root onOpenChange={onOpenChange} open={open} 
scrollBehavior="inside" size="xl">
@@ -66,13 +64,13 @@ export const DAGImportErrorsModal: 
React.FC<ImportDAGErrorModalProps> = ({ impor
         <Dialog.Header display="flex" justifyContent="space-between">
           <HStack fontSize="xl">
             <LuFileWarning />
-            <Heading>{translate("importErrors.dagImportError", { count: 
importErrors.length })}</Heading>
+            <Heading>{translate("importErrors.dagImportError", { count: 
data?.total_entries ?? 0 })}</Heading>
           </HStack>
           <SearchBar
             buttonProps={{ disabled: true }}
             defaultValue={searchQuery}
             hideAdvanced
-            onChange={setSearchQuery}
+            onChange={handleSearchChange}
             placeHolder={translate("importErrors.searchByFile")}
           />
         </Dialog.Header>
@@ -81,7 +79,7 @@ export const DAGImportErrorsModal: 
React.FC<ImportDAGErrorModalProps> = ({ impor
 
         <Dialog.Body>
           <Accordion.Root collapsible multiple size="md" variant="enclosed">
-            {visibleItems.map((importError) => (
+            {data?.import_errors.map((importError) => (
               <Accordion.Item key={importError.import_error_id} 
value={importError.filename}>
                 <Accordion.ItemTrigger cursor="pointer">
                   <Text display="flex" fontWeight="bold">
@@ -108,7 +106,7 @@ export const DAGImportErrorsModal: 
React.FC<ImportDAGErrorModalProps> = ({ impor
         </Dialog.Body>
 
         <Pagination.Root
-          count={filteredErrors.length}
+          count={data?.total_entries ?? 0}
           onPageChange={(event) => setPage(event.page)}
           p={4}
           page={page}

Reply via email to