This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new a81066e6af Render errors when getting a list of dags (#42897)
a81066e6af is described below
commit a81066e6af29406aacc81b0a371daf639affd88b
Author: Brent Bovenzi <[email protected]>
AuthorDate: Fri Oct 11 13:53:26 2024 +0200
Render errors when getting a list of dags (#42897)
* Render errors when getting a list of dags
* Restore axios, prettierignore pnpm-store
* Add pnpm-store to prettier ignore
---
airflow/ui/.prettierignore | 1 +
airflow/ui/pnpm-lock.yaml | 8 +--
airflow/ui/src/components/DataTable/DataTable.tsx | 7 ++-
airflow/ui/src/components/ErrorAlert.tsx | 67 +++++++++++++++++++++++
airflow/ui/src/main.tsx | 6 +-
airflow/ui/src/pages/DagsList/DagsList.tsx | 4 +-
6 files changed, 83 insertions(+), 10 deletions(-)
diff --git a/airflow/ui/.prettierignore b/airflow/ui/.prettierignore
index 7e860ea047..49a8631b87 100644
--- a/airflow/ui/.prettierignore
+++ b/airflow/ui/.prettierignore
@@ -4,3 +4,4 @@ dist/
*.md
*.yaml
coverage/*
+.pnpm-store
diff --git a/airflow/ui/pnpm-lock.yaml b/airflow/ui/pnpm-lock.yaml
index 6298d2ba32..c9f94d35f7 100644
--- a/airflow/ui/pnpm-lock.yaml
+++ b/airflow/ui/pnpm-lock.yaml
@@ -2049,8 +2049,8 @@ packages:
resolution: {integrity:
sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'}
- [email protected]:
- resolution: {integrity:
sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+ [email protected]:
+ resolution: {integrity:
sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
engines: {node: '>= 6'}
[email protected]:
@@ -5012,7 +5012,7 @@ snapshots:
[email protected]:
dependencies:
follow-redirects: 1.15.9
- form-data: 4.0.0
+ form-data: 4.0.1
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
@@ -5697,7 +5697,7 @@ snapshots:
cross-spawn: 7.0.3
signal-exit: 4.1.0
- [email protected]:
+ [email protected]:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
diff --git a/airflow/ui/src/components/DataTable/DataTable.tsx
b/airflow/ui/src/components/DataTable/DataTable.tsx
index 7f4c3cf083..2ed1a4f16e 100644
--- a/airflow/ui/src/components/DataTable/DataTable.tsx
+++ b/airflow/ui/src/components/DataTable/DataTable.tsx
@@ -41,6 +41,7 @@ type DataTableProps<TData> = {
readonly columns: Array<MetaColumn<TData>>;
readonly data: Array<TData>;
readonly displayMode?: "card" | "table";
+ readonly errorMessage?: ReactNode | string;
readonly getRowCanExpand?: (row: Row<TData>) => boolean;
readonly initialState?: TableState;
readonly isFetching?: boolean;
@@ -62,6 +63,7 @@ export const DataTable = <TData,>({
columns,
data,
displayMode = "table",
+ errorMessage,
getRowCanExpand = defaultGetRowCanExpand,
initialState,
isFetching,
@@ -127,10 +129,9 @@ export const DataTable = <TData,>({
Boolean(isFetching) && !Boolean(isLoading) ? "visible" : "hidden"
}
/>
+ {errorMessage}
{!Boolean(isLoading) && !rows.length && (
- <Text fontSize="small">
- {noRowsMessage ?? `No ${modelName}s found.`}
- </Text>
+ <Text pt={1}>{noRowsMessage ?? `No ${modelName}s found.`}</Text>
)}
{display === "table" && <TableList table={table} />}
{display === "card" && cardDef !== undefined && (
diff --git a/airflow/ui/src/components/ErrorAlert.tsx
b/airflow/ui/src/components/ErrorAlert.tsx
new file mode 100644
index 0000000000..3128a2cdec
--- /dev/null
+++ b/airflow/ui/src/components/ErrorAlert.tsx
@@ -0,0 +1,67 @@
+/*!
+ * 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 { Alert, AlertIcon } from "@chakra-ui/react";
+import type { ApiError } from "openapi-gen/requests/core/ApiError";
+import type {
+ HTTPExceptionResponse,
+ HTTPValidationError,
+} from "openapi-gen/requests/types.gen";
+
+type ExpandedApiError = {
+ body: HTTPExceptionResponse | HTTPValidationError;
+} & ApiError;
+
+type Props = {
+ readonly error?: unknown;
+};
+
+export const ErrorAlert = ({ error: err }: Props) => {
+ const error = err as ExpandedApiError;
+
+ if (!Boolean(error)) {
+ return undefined;
+ }
+
+ const details = error.body.detail;
+ let detailMessage;
+
+ if (details !== undefined) {
+ if (typeof details === "string") {
+ detailMessage = details;
+ } else if (Array.isArray(details)) {
+ detailMessage = details.map(
+ (detail) => `
+ ${detail.loc.join(".")} ${detail.msg}`,
+ );
+ } else {
+ detailMessage = Object.keys(details).map(
+ (key) => `${key}: ${details[key] as string}`,
+ );
+ }
+ }
+
+ return (
+ <Alert status="error">
+ <AlertIcon />
+ {error.message}
+ <br />
+ {detailMessage}
+ </Alert>
+ );
+};
diff --git a/airflow/ui/src/main.tsx b/airflow/ui/src/main.tsx
index daf4bcd024..12434ca7ba 100644
--- a/airflow/ui/src/main.tsx
+++ b/airflow/ui/src/main.tsx
@@ -18,7 +18,7 @@
*/
import { ChakraProvider } from "@chakra-ui/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import axios, { type AxiosError, type AxiosResponse } from "axios";
+import axios, { type AxiosError } from "axios";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
@@ -45,7 +45,7 @@ const queryClient = new QueryClient({
// redirect to login page if the API responds with unauthorized or forbidden
errors
axios.interceptors.response.use(
- (response: AxiosResponse) => response,
+ (response) => response,
(error: AxiosError) => {
if (error.response?.status === 403 || error.response?.status === 401) {
const params = new URLSearchParams();
@@ -53,6 +53,8 @@ axios.interceptors.response.use(
params.set("next", globalThis.location.href);
globalThis.location.replace(`/login?${params.toString()}`);
}
+
+ return Promise.reject(error);
},
);
diff --git a/airflow/ui/src/pages/DagsList/DagsList.tsx
b/airflow/ui/src/pages/DagsList/DagsList.tsx
index 178663baf7..60d6ef9c4f 100644
--- a/airflow/ui/src/pages/DagsList/DagsList.tsx
+++ b/airflow/ui/src/pages/DagsList/DagsList.tsx
@@ -34,6 +34,7 @@ import { DataTable } from "src/components/DataTable";
import { ToggleTableDisplay } from
"src/components/DataTable/ToggleTableDisplay";
import type { CardDef } from "src/components/DataTable/types";
import { useTableURLState } from "src/components/DataTable/useTableUrlState";
+import { ErrorAlert } from "src/components/ErrorAlert";
import { SearchBar } from "src/components/SearchBar";
import { TogglePause } from "src/components/TogglePause";
import { pluralize } from "src/utils/pluralize";
@@ -113,7 +114,7 @@ export const DagsList = () => {
const [sort] = sorting;
const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : undefined;
- const { data, isFetching, isLoading } = useDagServiceGetDags({
+ const { data, error, isFetching, isLoading } = useDagServiceGetDags({
lastDagRunState,
limit: pagination.pageSize,
offset: pagination.pageIndex * pagination.pageSize,
@@ -169,6 +170,7 @@ export const DagsList = () => {
columns={columns}
data={data?.dags ?? []}
displayMode={display}
+ errorMessage={<ErrorAlert error={error} />}
initialState={tableURLState}
isFetching={isFetching}
isLoading={isLoading}