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}