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}

Reply via email to