From 47393a74cbd29e9bb3f3c2dd5f666943e15d4a09 Mon Sep 17 00:00:00 2001 From: Abhishek Pal Date: Fri, 12 Sep 2025 14:14:29 +0530 Subject: [PATCH 1/7] HDDS-13677. Update Axios to 1.9.0 and improve error handling for requests --- .../recon/ozone-recon-web/package.json | 2 +- .../recon/ozone-recon-web/pnpm-lock.yaml | 15 +++--- .../src/components/navBar/navBar.tsx | 2 +- .../ozone-recon-web/src/utils/common.tsx | 48 ++++++++++++++----- .../decommissioningSummary.tsx | 2 +- .../src/v2/components/navBar/navBar.tsx | 4 +- .../v2/components/nuMetadata/nuMetadata.tsx | 6 +-- .../v2/components/tables/containersTable.tsx | 2 +- .../insights/containerMismatchTable.tsx | 3 +- .../insights/deletePendingDirsTable.tsx | 3 +- .../insights/deletePendingKeysTable.tsx | 3 +- .../insights/deletedContainerKeysTable.tsx | 3 +- .../tables/insights/openKeysTable.tsx | 3 +- .../src/v2/pages/buckets/buckets.tsx | 3 +- .../src/v2/pages/containers/containers.tsx | 3 +- .../src/v2/pages/datanodes/datanodes.tsx | 7 ++- .../src/v2/pages/heatmap/heatmap.tsx | 2 +- .../src/v2/pages/insights/insights.tsx | 7 +-- .../src/v2/pages/insights/omInsights.tsx | 3 +- .../pages/namespaceUsage/namespaceUsage.tsx | 3 +- .../src/v2/pages/overview/overview.tsx | 6 +-- .../src/v2/pages/pipelines/pipelines.tsx | 3 +- .../src/v2/pages/volumes/volumes.tsx | 2 +- .../src/views/buckets/buckets.tsx | 2 +- .../src/views/datanodes/datanodes.tsx | 6 +-- .../views/datanodes/decommissionSummary.tsx | 2 +- .../src/views/diskUsage/diskUsage.tsx | 8 ++-- .../src/views/insights/insights.tsx | 6 +-- .../src/views/insights/om/om.tsx | 18 +++---- .../missingContainers/missingContainers.tsx | 4 +- .../src/views/overview/overview.tsx | 4 +- .../src/views/pipelines/pipelines.tsx | 2 +- .../src/views/volumes/volumes.tsx | 2 +- 33 files changed, 100 insertions(+), 89 deletions(-) diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/package.json b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/package.json index ba365c285089..bdd0da326cf1 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/package.json +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/package.json @@ -16,7 +16,7 @@ "ag-charts-community": "^7.3.0", "ag-charts-react": "^7.3.0", "antd": "~4.10.3", - "axios": "^0.30.0", + "axios": "~1.9.0", "classnames": "^2.3.2", "echarts": "^5.5.0", "filesize": "^6.4.0", diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/pnpm-lock.yaml b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/pnpm-lock.yaml index 1922365bd9e0..d2e6c7e510f3 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/pnpm-lock.yaml +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/pnpm-lock.yaml @@ -21,8 +21,8 @@ dependencies: specifier: ~4.10.3 version: 4.10.3(react-dom@16.14.0)(react@16.14.0) axios: - specifier: ^0.30.0 - version: 0.30.0 + specifier: ~1.9.0 + version: 1.9.0 classnames: specifier: ^2.3.2 version: 2.5.1 @@ -1900,11 +1900,11 @@ packages: resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} dev: true - /axios@0.30.0: - resolution: {integrity: sha512-Z4F3LjCgfjZz8BMYalWdMgAQUnEtKDmpwNHjh/C8pQZWde32TF64cqnSeyL3xD/aTIASRU30RHTNzRiV/NpGMg==} + /axios@1.9.0: + resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==} dependencies: follow-redirects: 1.15.9 - form-data: 4.0.2 + form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -3343,13 +3343,14 @@ packages: mime-types: 2.1.35 dev: true - /form-data@4.0.2: - resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + /form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 + hasown: 2.0.2 mime-types: 2.1.35 dev: false diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/navBar/navBar.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/navBar/navBar.tsx index b0bdf187cb3a..18d8fa70480c 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/navBar/navBar.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/components/navBar/navBar.tsx @@ -82,7 +82,7 @@ class NavBar extends React.Component { this.setState({ isLoading: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/common.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/common.tsx index 59e4d180f44f..e5509538328e 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/common.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/common.tsx @@ -18,7 +18,7 @@ import moment from 'moment'; import { notification } from 'antd'; -import { CanceledError } from 'axios'; +import axios, { CanceledError, AxiosError } from 'axios'; export const getCapacityPercent = (used: number, total: number) => Math.round((used / total) * 100); @@ -43,16 +43,42 @@ export const showInfoNotification = (title: string, description: string) => { notification.warn(args); }; -export const showDataFetchError = (error: string) => { +export const showDataFetchError = (error: string | AxiosError | unknown) => { let title = 'Error while fetching data'; + let errorMessage = ''; - if (error.includes('CanceledError')) return; - if (error.includes('metadata')) { - title = 'Metadata Initialization:'; - showInfoNotification(title, error); - return; + // Handle AxiosError instances + if (axios.isAxiosError(error)) { + // Don't show notifications for canceled requests + if (error.code === 'ERR_CANCELED' || error.name === 'CanceledError') { + return; + } + + if (error.response) { + // Server responded with error status + errorMessage = `Server Error (${error.response.status}): ${error.response.statusText}`; + if (error.response.data && typeof error.response.data === 'string') { + errorMessage += ` - ${error.response.data}`; + } + } else if (error.request) { + // Request was made but no response received + errorMessage = 'Network Error: No response received from server'; + } else { + // Something else happened + errorMessage = error.message || 'Unknown error occurred'; + } + } else { + errorMessage = error as string; + + if (errorMessage.includes('CanceledError')) return; + if (errorMessage.includes('metadata')) { + title = 'Metadata Initialization:'; + showInfoNotification(title, errorMessage); + return; + } } - showErrorNotification(title, error); + + showErrorNotification(title, errorMessage); }; export const byteToSize = (bytes: number, decimals: number) => { @@ -106,12 +132,12 @@ export const checkResponseError = (responses: Awaited>[]) => { if (responseError.length !== 0) { responseError.forEach((err) => { - if (err.reason.toString().includes("CanceledError")) { + if (err.reason instanceof CanceledError || err.reason.code === 'ERR_CANCELED') { throw new CanceledError('canceled', "ERR_CANCELED"); } else { - const reqMethod = err.reason.config.method; - const reqURL = err.reason.config.url + const reqMethod = err.reason.config?.method || 'unknown'; + const reqURL = err.reason.config?.url || 'unknown URL'; showDataFetchError( `Failed to ${reqMethod} URL ${reqURL}\n${err.reason.toString()}` ); diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/decommissioningSummary/decommissioningSummary.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/decommissioningSummary/decommissioningSummary.tsx index 34e72b0889aa..71cb941cd9c7 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/decommissioningSummary/decommissioningSummary.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/decommissioningSummary/decommissioningSummary.tsx @@ -101,7 +101,7 @@ const DecommissionSummary: React.FC = ({ loading: false, summaryData: {} }); - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); content = ( = ({ const response: AxiosResponse = await request; const heatmapDisabled = response?.data?.includes('HEATMAP') setIsHeatmapEnabled(!heatmapDisabled); - } catch (error: unknown) { - showDataFetchError((error as Error).toString()) + } catch (error) { + showDataFetchError(error) } } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx index eeb7475e52a2..613612d5d55a 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx @@ -18,7 +18,7 @@ import React, {useRef, useState} from 'react'; import moment from 'moment'; -import axios, {AxiosError} from 'axios'; +import axios from 'axios'; import {Table} from 'antd'; import {AxiosGetHelper, cancelRequests, PromiseAllSettledGetHelper} from '@/utils/axiosRequestHelper'; @@ -290,7 +290,7 @@ const NUMetadata: React.FC = ({ }]) setState(data); }).catch(error => { - showDataFetchError(error.toString()); + showDataFetchError(error); }); return; } @@ -347,7 +347,7 @@ const NUMetadata: React.FC = ({ } setState(data); })).catch(error => { - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); }); } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/containersTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/containersTable.tsx index 424d58cf245e..0444f0f5d81e 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/containersTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/containersTable.tsx @@ -217,7 +217,7 @@ const ContainerTable: React.FC = ({ loading: false } }); - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); }); } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx index 818eca37f8ef..6f260a6f645e 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx @@ -17,7 +17,6 @@ */ import React from 'react'; -import { AxiosError } from 'axios'; import { Dropdown, Menu, @@ -164,7 +163,7 @@ const ContainerMismatchTable: React.FC = ({ setLoading(false); }).catch(error => { setLoading(false); - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); }) } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingDirsTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingDirsTable.tsx index f0c6fc8161e4..2efa6e7c9d87 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingDirsTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingDirsTable.tsx @@ -17,7 +17,6 @@ */ import React from 'react'; -import { AxiosError } from 'axios'; import Table, { ColumnsType, TablePaginationConfig @@ -101,7 +100,7 @@ const DeletePendingDirTable: React.FC = ({ setLoading(false); }).catch(error => { setLoading(false); - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); }); } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingKeysTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingKeysTable.tsx index 65ada4956411..37c0b0d913cb 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingKeysTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingKeysTable.tsx @@ -17,7 +17,6 @@ */ import React from 'react'; -import { AxiosError } from 'axios'; import Table, { ColumnsType, TablePaginationConfig @@ -144,7 +143,7 @@ const DeletePendingKeysTable: React.FC = ({ setLoading(false); }).catch(error => { setLoading(false); - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); }) } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletedContainerKeysTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletedContainerKeysTable.tsx index 9aaf62a63d6f..efd83899594e 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletedContainerKeysTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletedContainerKeysTable.tsx @@ -17,7 +17,6 @@ */ import React from 'react'; -import { AxiosError } from 'axios'; import Table, { ColumnsType, TablePaginationConfig @@ -112,7 +111,7 @@ const DeletedContainerKeysTable: React.FC = ({ setLoading(false); }).catch(error => { setLoading(false); - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); }); } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/openKeysTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/openKeysTable.tsx index 02c73c77528d..44ef59617d63 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/openKeysTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/openKeysTable.tsx @@ -17,7 +17,6 @@ */ import React from 'react'; -import { AxiosError } from 'axios'; import { Dropdown, Menu, @@ -96,7 +95,7 @@ const OpenKeysTable: React.FC = ({ setLoading(false); }).catch(error => { setLoading(false); - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); }); } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx index 1c039f42709b..e6dc282b6b5a 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx @@ -20,6 +20,7 @@ import React, { useEffect, useRef, useState } from 'react'; import moment from 'moment'; import { ValueType } from 'react-select'; import { useLocation } from 'react-router-dom'; +import { AxiosError } from 'axios'; import AutoReloadPanel from '@/components/autoReloadPanel/autoReloadPanel'; import AclPanel from '@/v2/components/aclDrawer/aclDrawer'; @@ -207,7 +208,7 @@ const Buckets: React.FC<{}> = () => { }); }).catch(error => { setLoading(false); - showDataFetchError(error.toString()); + showDataFetchError(error); }); } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/containers/containers.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/containers/containers.tsx index 5cdbdd52625a..90562f1ab603 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/containers/containers.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/containers/containers.tsx @@ -18,7 +18,6 @@ import React, { useRef, useState } from "react"; import moment from "moment"; -import { AxiosError } from "axios"; import { Card, Row, Tabs } from "antd"; import { ValueType } from "react-select/src/types"; @@ -117,7 +116,7 @@ const Containers: React.FC<{}> = () => { setLoading(false) }).catch(error => { setLoading(false); - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); }); } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/datanodes/datanodes.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/datanodes/datanodes.tsx index 33dd661d97ba..5280ae6bc944 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/datanodes/datanodes.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/datanodes/datanodes.tsx @@ -22,7 +22,6 @@ import React, { useState } from 'react'; import moment from 'moment'; -import { AxiosError } from 'axios'; import { Button, Modal @@ -131,7 +130,7 @@ const Datanodes: React.FC<{}> = () => { request.then(() => { loadData(); }).catch((error) => { - showDataFetchError(error.toString()); + showDataFetchError(error); }).finally(() => { setLoading(false); setSelectedRows([]); @@ -150,7 +149,7 @@ const Datanodes: React.FC<{}> = () => { ); } catch (error) { decommissionUuids = []; - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); } try { @@ -189,7 +188,7 @@ const Datanodes: React.FC<{}> = () => { }); } catch (error) { setLoading(false); - showDataFetchError((error as AxiosError).toString()) + showDataFetchError(error) } } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/heatmap/heatmap.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/heatmap/heatmap.tsx index c243cee918df..eab6b63cac83 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/heatmap/heatmap.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/heatmap/heatmap.tsx @@ -204,7 +204,7 @@ const Heatmap: React.FC<{}> = () => { request.then(response => { setIsHeatmapEnabled(!response?.data?.includes('HEATMAP')); }).catch(error => { - showDataFetchError((error as Error).toString()); + showDataFetchError(error); }); } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx index f2a2c3e3f7d1..834d4d59e6da 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx @@ -17,10 +17,7 @@ */ import React, { useState } from 'react'; -import axios, { - CanceledError, - AxiosError -} from 'axios'; +import axios, { CanceledError } from 'axios'; import { Row, Col, Card, Result } from 'antd'; import { showDataFetchError } from '@/utils/common'; @@ -136,7 +133,7 @@ const Insights: React.FC<{}> = () => { setLoading(false); })).catch(error => { setLoading(false); - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); }) } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/omInsights.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/omInsights.tsx index 732af0aa00e7..7c300ff9dd68 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/omInsights.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/omInsights.tsx @@ -17,7 +17,6 @@ */ import React from 'react'; -import { AxiosError } from 'axios'; import { ValueType } from 'react-select'; import { Tabs, Tooltip } from 'antd'; import { TablePaginationConfig } from 'antd/es/table'; @@ -87,7 +86,7 @@ const OMDBInsights: React.FC<{}> = () => { )); setLoading(false); }).catch(error => { - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); }); } } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/namespaceUsage/namespaceUsage.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/namespaceUsage/namespaceUsage.tsx index f7fa6c13bbad..5f9a4859a4fc 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/namespaceUsage/namespaceUsage.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/namespaceUsage/namespaceUsage.tsx @@ -17,7 +17,6 @@ */ import React, { useRef, useState } from 'react'; -import { AxiosError } from 'axios'; import { Alert, Button, Tooltip } from 'antd'; import { InfoCircleFilled, ReloadOutlined, } from '@ant-design/icons'; import { ValueType } from 'react-select'; @@ -84,7 +83,7 @@ const NamespaceUsage: React.FC<{}> = () => { setLoading(false); }).catch(error => { setLoading(false); - showDataFetchError((error as AxiosError).toString()); + showDataFetchError(error); }); } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.tsx index 6014577f90a2..e1cc3ac86053 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.tsx @@ -229,7 +229,7 @@ const Overview: React.FC<{}> = () => { ...state, loading: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); } @@ -255,12 +255,12 @@ const Overview: React.FC<{}> = () => { loading: false, omStatus: omStatus }); - }).catch((error: Error) => { + }).catch((error) => { setState({ ...state, loading: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/pipelines/pipelines.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/pipelines/pipelines.tsx index f6ff87c7e132..cac562626652 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/pipelines/pipelines.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/pipelines/pipelines.tsx @@ -24,6 +24,7 @@ import React, { import moment from 'moment'; import { ValueType } from 'react-select'; + import AutoReloadPanel from '@/components/autoReloadPanel/autoReloadPanel'; import Search from '@/v2/components/search/search'; import MultiSelect, { Option } from '@/v2/components/select/multiSelect'; @@ -85,7 +86,7 @@ const Pipelines: React.FC<{}> = () => { setLoading(false); }).catch(error => { setLoading(false); - showDataFetchError(error.toString()); + showDataFetchError(error); }) } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx index b4614d387f3a..41e539634cef 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx @@ -118,7 +118,7 @@ const Volumes: React.FC<{}> = () => { setLoading(false); }).catch(error => { setLoading(false); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/buckets/buckets.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/buckets/buckets.tsx index 64b215b82452..da49975e0e9b 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/buckets/buckets.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/buckets/buckets.tsx @@ -496,7 +496,7 @@ export class Buckets extends React.Component, IBucketsSta loading: false, showPanel: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx index 4a3c11c11b06..7b0e3418a0af 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx @@ -408,7 +408,7 @@ export class Datanodes extends React.Component, IDatanode loading: false }); decommissionUuids = []; - showDataFetchError(error.toString()); + showDataFetchError(error); } try { // Call Datanode API Synchronously after completing Decommission API to render Operation Status Column @@ -450,7 +450,7 @@ export class Datanodes extends React.Component, IDatanode loading: false }); decommissionUuids = []; - showDataFetchError(error.toString()); + showDataFetchError(error); } }; @@ -480,7 +480,7 @@ export class Datanodes extends React.Component, IDatanode request.then(() => { this._loadData(); }).catch(error => { - showDataFetchError(error.toString()); + showDataFetchError(error); }).finally(() => { this.setState({ loading: false, diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/decommissionSummary.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/decommissionSummary.tsx index 28527c0816e0..c4457f507235 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/decommissionSummary.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/decommissionSummary.tsx @@ -67,7 +67,7 @@ class DecommissionSummary extends React.Component { loading: false, summaryData: [] }); - showDataFetchError(error.toString()); + showDataFetchError(error); } }; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/diskUsage/diskUsage.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/diskUsage/diskUsage.tsx index d154359c94f2..1f7bba9aa75b 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/diskUsage/diskUsage.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/diskUsage/diskUsage.tsx @@ -241,7 +241,7 @@ export class DiskUsage extends React.Component, IDUState> this.setState({ isLoading: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; @@ -328,7 +328,7 @@ export class DiskUsage extends React.Component, IDUState> isLoading: false, showPanel: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); return; } @@ -480,7 +480,7 @@ export class DiskUsage extends React.Component, IDUState> isLoading: false, showPanel: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); const quotaEndpoint = `/api/v1/namespace/quota?path=${path}`; @@ -525,7 +525,7 @@ export class DiskUsage extends React.Component, IDUState> isLoading: false, showPanel: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx index 63f095ff7cae..59a5b48c9924 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/insights.tsx @@ -17,7 +17,7 @@ */ import React from 'react'; -import axios, { CanceledError, AxiosError } from 'axios'; +import axios, { CanceledError } from 'axios'; import filesize from 'filesize'; import { Row, Col, Tabs, message } from 'antd'; import { LoadingOutlined } from '@ant-design/icons'; @@ -27,7 +27,7 @@ import { graphic, type EChartsOption } from 'echarts'; import { EChart } from '@/components/eChart/eChart'; import { MultiSelect, IOption } from '@/components/multiSelect/multiSelect'; import { showDataFetchError } from '@/utils/common'; -import { PromiseAllSettledGetHelper, PromiseAllSettledError } from '@/utils/axiosRequestHelper'; +import { PromiseAllSettledGetHelper } from '@/utils/axiosRequestHelper'; import './insights.less'; @@ -420,7 +420,7 @@ export class Insights extends React.Component, IInsightsS this.setState({ isLoading: false, }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); } diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/om/om.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/om/om.tsx index fd8d7e2da976..58b460be4c21 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/om/om.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/insights/om/om.tsx @@ -548,7 +548,7 @@ export class Om extends React.Component, IOmdbInsightsSta this.setState({ loading: false, }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; @@ -594,7 +594,7 @@ export class Om extends React.Component, IOmdbInsightsSta this.setState({ loading: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; @@ -653,7 +653,7 @@ export class Om extends React.Component, IOmdbInsightsSta this.setState({ loading: false, }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; @@ -730,7 +730,7 @@ export class Om extends React.Component, IOmdbInsightsSta this.setState({ loading: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; @@ -764,7 +764,7 @@ export class Om extends React.Component, IOmdbInsightsSta this.setState({ loading: false, }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; @@ -835,13 +835,7 @@ export class Om extends React.Component, IOmdbInsightsSta expandedRowData: Object.assign({}, expandedRowData, { [record.containerId]: expandedRowState }) }; }); - if (error.name === "CanceledError") { - showDataFetchError(cancelRowExpandSignal.signal.reason) - } - else { - console.log(error); - showDataFetchError(error.toString()); - } + showDataFetchError(error); }); } else { diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx index 9156d68326ec..1b0c784fbe6d 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx @@ -285,7 +285,7 @@ export class MissingContainers extends React.Component, I this.setState({ loading: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); } @@ -342,7 +342,7 @@ export class MissingContainers extends React.Component, I expandedRowData: { ...expandedRowData, [record.containerID]: expandedRowState } }; }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); } else { diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/overview/overview.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/overview/overview.tsx index 0e8940a928ee..817edacdd140 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/overview/overview.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/overview/overview.tsx @@ -225,7 +225,7 @@ export class Overview extends React.Component, IOverviewS this.setState({ loading: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; @@ -252,7 +252,7 @@ export class Overview extends React.Component, IOverviewS this.setState({ loading: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx index c53be4f7a205..a5001ee0697e 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx @@ -205,7 +205,7 @@ export class Pipelines extends React.Component, IPipeline this.setState({ activeLoading: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/volumes/volumes.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/volumes/volumes.tsx index ad1e94c4d6c1..2316a0d85792 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/volumes/volumes.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/volumes/volumes.tsx @@ -316,7 +316,7 @@ export class Volumes extends React.Component, IVolumesSta loading: false, showPanel: false }); - showDataFetchError(error.toString()); + showDataFetchError(error); }); }; From aace1ba1fe36a06c52416667e6c9c61788fdaa17 Mon Sep 17 00:00:00 2001 From: Abhishek Pal Date: Thu, 18 Sep 2025 13:35:19 +0530 Subject: [PATCH 2/7] Refactor API calls to use the useAPIData hook --- .../decommissioningSummary.tsx | 78 +++------ .../src/v2/components/navBar/navBar.tsx | 38 ++--- .../v2/components/nuMetadata/nuMetadata.tsx | 124 ++++++++------- .../v2/components/tables/containersTable.tsx | 28 ++-- .../tables/insights/openKeysTable.tsx | 87 +++++----- .../src/v2/hooks/useAPIData.hook.ts | 147 +++++++++++++---- .../src/v2/pages/datanodes/datanodes.tsx | 132 ++++++++-------- .../src/v2/pages/insights/insights.tsx | 149 +++++++++--------- 8 files changed, 413 insertions(+), 370 deletions(-) diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/decommissioningSummary/decommissioningSummary.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/decommissioningSummary/decommissioningSummary.tsx index 71cb941cd9c7..ebf3ed67eba9 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/decommissioningSummary/decommissioningSummary.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/decommissioningSummary/decommissioningSummary.tsx @@ -16,22 +16,17 @@ * limitations under the License. */ -import React, { useEffect } from 'react'; -import { AxiosError } from 'axios'; +import React from 'react'; import { Descriptions, Popover, Result } from 'antd'; import { SummaryData } from '@/v2/types/datanode.types'; -import { AxiosGetHelper, cancelRequests } from '@/utils/axiosRequestHelper'; import { showDataFetchError } from '@/utils/common'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; import Spin from 'antd/es/spin'; type DecommisioningSummaryProps = { uuid: string; } -type DecommisioningSummaryState = { - loading: boolean; - summaryData: SummaryData | Record; -}; function getDescriptions(summaryData: SummaryData): React.ReactElement { const { @@ -67,59 +62,34 @@ function getDescriptions(summaryData: SummaryData): React.ReactElement { const DecommissionSummary: React.FC = ({ uuid = '' }) => { - const [state, setState] = React.useState({ - summaryData: {}, - loading: false - }); - const cancelSignal = React.useRef(); + const { + data: decommissionResponse, + loading, + error + } = useApiData<{DatanodesDecommissionInfo: SummaryData[]}>( + `/api/v1/datanodes/decommission/info/datanode?uuid=${uuid}`, + { DatanodesDecommissionInfo: [] }, + { + onError: (error) => showDataFetchError(error) + } + ); + + const summaryData = decommissionResponse.DatanodesDecommissionInfo[0] || {}; + let content = ( ); - async function fetchDecommissionSummary(selectedUuid: string) { - setState({ - ...state, - loading: true - }); - try { - const { request, controller } = AxiosGetHelper( - `/api/v1/datanodes/decommission/info/datanode?uuid=${selectedUuid}`, - cancelSignal.current - ); - cancelSignal.current = controller; - const datanodesInfoResponse = await request; - setState({ - ...state, - loading: false, - summaryData: datanodesInfoResponse?.data?.DatanodesDecommissionInfo[0] ?? {} - }); - } catch (error) { - setState({ - ...state, - loading: false, - summaryData: {} - }); - showDataFetchError(error); - content = ( - - ) - } - } - - useEffect(() => { - fetchDecommissionSummary(uuid); - return (() => { - cancelRequests([cancelSignal.current!]); - }) - }, []); - - const { summaryData } = state; - if (summaryData?.datanodeDetails + if (error) { + content = ( + + ); + } else if (summaryData?.datanodeDetails && summaryData?.metrics && summaryData?.containers ) { diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/navBar/navBar.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/navBar/navBar.tsx index 63ea068e999b..3cc6b2aca91c 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/navBar/navBar.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/navBar/navBar.tsx @@ -16,8 +16,7 @@ * limitations under the License. */ -import React, {useEffect, useRef, useState} from 'react'; -import {AxiosResponse} from 'axios'; +import React, {useEffect} from 'react'; import {Layout, Menu} from 'antd'; import { BarChartOutlined, @@ -36,7 +35,7 @@ import {Link, useLocation} from 'react-router-dom'; import logo from '@/logo.png'; import {showDataFetchError} from '@/utils/common'; -import {AxiosGetHelper, cancelRequests} from '@/utils/axiosRequestHelper'; +import {useApiData} from '@/v2/hooks/useAPIData.hook'; import './navBar.less'; @@ -51,34 +50,17 @@ const NavBar: React.FC = ({ collapsed = false, onCollapse = () => { } }) => { - const [isHeatmapEnabled, setIsHeatmapEnabled] = useState(false); - const cancelDisabledFeatureSignal = useRef(); const location = useLocation(); - - const fetchDisabledFeatures = async () => { - const disabledfeaturesEndpoint = `/api/v1/features/disabledFeatures`; - const { request, controller } = AxiosGetHelper( - disabledfeaturesEndpoint, - cancelDisabledFeatureSignal.current - ) - cancelDisabledFeatureSignal.current = controller; - try { - const response: AxiosResponse = await request; - const heatmapDisabled = response?.data?.includes('HEATMAP') - setIsHeatmapEnabled(!heatmapDisabled); - } catch (error) { - showDataFetchError(error) + + const { data: disabledFeatures, error } = useApiData( + '/api/v1/features/disabledFeatures', + [], + { + onError: (error) => showDataFetchError(error) } - } - + ); - useEffect(() => { - fetchDisabledFeatures(); - // Component will unmount - return (() => { - cancelRequests([cancelDisabledFeatureSignal.current!]) - }) - }, []) + const isHeatmapEnabled = !disabledFeatures.includes('HEATMAP'); const menuItems = [( = ({ path = '/' }) => { - const [loading, setLoading] = useState(false); const [state, setState] = useState([]); - const keyMetadataSummarySignal = useRef(); - const cancelMetadataSignal = useRef(); + const [isProcessingData, setIsProcessingData] = useState(false); + + // Individual API calls that resolve together + const summaryAPI = useApiData( + `/api/v1/namespace/summary?path=${path}`, + {} as SummaryResponse, + { + initialFetch: false, + onError: (error) => showDataFetchError(error) + } + ); + + const quotaAPI = useApiData( + `/api/v1/namespace/quota?path=${path}`, + {}, + { + initialFetch: false, + onError: (error) => showDataFetchError(error) + } + ); + + const loading = summaryAPI.loading || quotaAPI.loading || isProcessingData; - const getObjectInfoMapping = React.useCallback((summaryResponse) => { + const getObjectInfoMapping = useCallback((summaryResponse) => { const data: MetadataState = []; /** * We are creating a specific set of keys under Object Info response @@ -230,25 +248,15 @@ const NUMetadata: React.FC = ({ return data; }, [path]); - function loadData(path: string) { - const { requests, controller } = PromiseAllSettledGetHelper([ - `/api/v1/namespace/summary?path=${path}`, - `/api/v1/namespace/quota?path=${path}` - ], cancelMetadataSignal.current); - cancelMetadataSignal.current = controller; - - requests.then(axios.spread(( - nsSummaryResponse: Awaited>, - quotaApiResponse: Awaited>, - ) => { - checkResponseError([nsSummaryResponse, quotaApiResponse]); - const summaryResponse: SummaryResponse = nsSummaryResponse.value?.data ?? {}; - const quotaResponse = quotaApiResponse.value?.data ?? {}; + // Process data when both APIs complete + const processMetadata = useCallback(async (summaryResponse: SummaryResponse, quotaResponse: any) => { + setIsProcessingData(true); + try { let data: MetadataState = []; let summaryResponsePresent = true; let quotaResponsePresent = true; - // Error checks + // Error checks for summary response if (summaryResponse.status === 'INITIALIZING') { summaryResponsePresent = false; showDataFetchError(`The metadata is currently initializing. Please wait a moment and try again later`); @@ -269,30 +277,27 @@ const NUMetadata: React.FC = ({ // If the entity is a Key then fetch the Key metadata only if (summaryResponse.type === 'KEY') { - const { request: metadataRequest, controller: metadataNewController } = AxiosGetHelper( - `/api/v1/namespace/usage?path=${path}&replica=true`, - keyMetadataSummarySignal.current - ); - keyMetadataSummarySignal.current = metadataNewController; - metadataRequest.then(response => { + try { + const usageResponse: any = await fetchData(`/api/v1/namespace/usage?path=${path}&replica=true`); data.push(...[{ key: 'File Size', - value: byteToSize(response.data.size, 3) + value: byteToSize(usageResponse.size, 3) }, { key: 'File Size With Replication', - value: byteToSize(response.data.sizeWithReplica, 3) + value: byteToSize(usageResponse.sizeWithReplica, 3) }, { key: 'Creation Time', value: moment(summaryResponse.objectInfo.creationTime).format('ll LTS') }, { key: 'Modification Time', value: moment(summaryResponse.objectInfo.modificationTime).format('ll LTS') - }]) + }]); setState(data); - }).catch(error => { + return; + } catch (error) { showDataFetchError(error); - }); - return; + return; + } } data = removeDuplicatesAndMerge(data, getObjectInfoMapping(summaryResponse), 'key'); @@ -307,7 +312,7 @@ const NUMetadata: React.FC = ({ numBucket: 'Buckets', numDir: 'Total Directories', numKey: 'Total Keys' - } + }; Object.keys(countStats).forEach((key: string) => { if (countStats[key as keyof CountStats] !== undefined && countStats[key as keyof CountStats] !== -1) { @@ -316,9 +321,10 @@ const NUMetadata: React.FC = ({ value: countStats[key as keyof CountStats] }); } - }) + }); } + // Error checks for quota response if (quotaResponse.state === 'INITIALIZING') { quotaResponsePresent = false; showDataFetchError(`The quota is currently initializing. Please wait a moment and try again later`); @@ -342,26 +348,38 @@ const NUMetadata: React.FC = ({ data.push({ key: 'Quota Used', value: byteToSize(quotaResponse.used, 3) - }) + }); } } + setState(data); - })).catch(error => { + } catch (error) { showDataFetchError(error); - }); - } - - React.useEffect(() => { - setLoading(true); - loadData(path); - setLoading(false); - - return (() => { - cancelRequests([ - cancelMetadataSignal.current!, - ]); - }) - }, [path]); + } finally { + setIsProcessingData(false); + } + }, [path, getObjectInfoMapping]); + + // Coordinate API calls - process data when both calls complete + useEffect(() => { + if (!summaryAPI.loading && !quotaAPI.loading && + summaryAPI.data && quotaAPI.data && + summaryAPI.lastUpdated && quotaAPI.lastUpdated) { + processMetadata(summaryAPI.data, quotaAPI.data); + } + }, [summaryAPI.loading, quotaAPI.loading, summaryAPI.data, quotaAPI.data, + summaryAPI.lastUpdated, quotaAPI.lastUpdated, processMetadata]); + + const loadData = useCallback(() => { + // Trigger both API calls together + summaryAPI.refetch(); + quotaAPI.refetch(); + }, [summaryAPI, quotaAPI]); + + // Load data when path changes + useEffect(() => { + loadData(); + }, [path, loadData]); return ( = ({ searchTerm = '' }) => { - const cancelSignal = useRef(); function filterSelectedColumns() { const columnKeys = selectedColumns.map((column) => column.value); @@ -191,15 +189,12 @@ const ContainerTable: React.FC = ({ ); } - function loadRowData(containerID: number) { - const { request, controller } = AxiosGetHelper( - `/api/v1/containers/${containerID}/keys`, - cancelSignal.current - ); - cancelSignal.current = controller; - - request.then(response => { - const containerKeysResponse: ContainerKeysResponse = response.data; + async function loadRowData(containerID: number) { + try { + const containerKeysResponse = await fetchData( + `/api/v1/containers/${containerID}/keys` + ); + expandedRowSetter({ ...expandedRow, [containerID]: { @@ -209,7 +204,7 @@ const ContainerTable: React.FC = ({ totalCount: containerKeysResponse.totalCount } }); - }).catch(error => { + } catch (error) { expandedRowSetter({ ...expandedRow, [containerID]: { @@ -218,7 +213,7 @@ const ContainerTable: React.FC = ({ } }); showDataFetchError(error); - }); + } } function getFilteredData(data: Container[]) { @@ -236,9 +231,6 @@ const ContainerTable: React.FC = ({ if (expanded) { loadRowData(record.containerID); } - else { - cancelSignal.current && cancelSignal.current.abort(); - } } function expandedRowRender(record: Container) { diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/openKeysTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/openKeysTable.tsx index c2a775a91003..9ee92cd5e4ef 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/openKeysTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/openKeysTable.tsx @@ -32,10 +32,10 @@ import { ValueType } from 'react-select'; import Search from '@/v2/components/search/search'; import SingleSelect, { Option } from '@/v2/components/select/singleSelect'; -import { AxiosGetHelper } from '@/utils/axiosRequestHelper'; import { byteToSize, showDataFetchError } from '@/utils/common'; import { getFormattedTime } from '@/v2/utils/momentUtils'; import { useDebounce } from '@/v2/hooks/useDebounce'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants'; import { OpenKeys, OpenKeysResponse } from '@/v2/types/insights.types'; @@ -54,57 +54,53 @@ const OpenKeysTable: React.FC = ({ paginationConfig, handleLimitChange }) => { - const [loading, setLoading] = React.useState(false); - const [data, setData] = React.useState(); + const [isFso, setIsFso] = React.useState(true); const [searchTerm, setSearchTerm] = React.useState(''); - - const cancelSignal = React.useRef(); const debouncedSearch = useDebounce(searchTerm, 300); + const { + data: openKeysResponse, + loading, + } = useApiData( + `/api/v1/keys/open?includeFso=${isFso}&includeNonFso=${!isFso}&limit=${limit.value}`, + { + lastKey: '', + replicatedDataSize: 0, + unreplicatedDataSize: 0, + fso: [], + nonFSO: [] + }, + { + onError: (error) => showDataFetchError(error) + } + ); + + // Transform the data based on FSO selection + const data = React.useMemo(() => { + let allOpenKeys: OpenKeys[]; + if (isFso) { + allOpenKeys = openKeysResponse['fso']?.map((key: OpenKeys) => ({ + ...key, + type: 'FSO' + })) ?? []; + } else { + allOpenKeys = openKeysResponse['nonFSO']?.map((key: OpenKeys) => ({ + ...key, + type: 'Non FSO' + })) ?? []; + } + return allOpenKeys; + }, [openKeysResponse, isFso]); + function filterData(data: OpenKeys[] | undefined) { return data?.filter( (data: OpenKeys) => data.path.includes(debouncedSearch) ); } - function fetchOpenKeys(isFso: boolean) { - setLoading(true); - - const { request, controller } = AxiosGetHelper( - `/api/v1/keys/open?includeFso=${isFso}&includeNonFso=${!isFso}&limit=${limit.value}`, - cancelSignal.current - ); - cancelSignal.current = controller; - - request.then(response => { - const openKeys: OpenKeysResponse = response?.data ?? { 'fso': [] }; - let allOpenKeys: OpenKeys[]; - if (isFso) { - allOpenKeys = openKeys['fso']?.map((key: OpenKeys) => ({ - ...key, - type: 'FSO' - })) ?? []; - } else { - allOpenKeys = openKeys['nonFSO']?.map((key: OpenKeys) => ({ - ...key, - type: 'Non FSO' - })) ?? []; - } - - setData(allOpenKeys); - setLoading(false); - }).catch(error => { - setLoading(false); - showDataFetchError(error); - }); - } - const handleKeyTypeChange: MenuProps['onClick'] = (e) => { - if (e.key === 'fso') { - fetchOpenKeys(true); - } else { - fetchOpenKeys(false); - } + // The hook will automatically refetch when the URL changes due to isFso change + setIsFso(e.key === 'fso'); } const COLUMNS: ColumnsType = [{ @@ -172,13 +168,6 @@ const OpenKeysTable: React.FC = ({ render: (type: string) =>
{type}
}]; - React.useEffect(() => { - // Fetch FSO open keys by default - fetchOpenKeys(true); - - return (() => cancelSignal.current && cancelSignal.current.abort()); - }, [limit.value]); - return ( <>
diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/hooks/useAPIData.hook.ts b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/hooks/useAPIData.hook.ts index dfcdec0cefae..53f19521ea0d 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/hooks/useAPIData.hook.ts +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/hooks/useAPIData.hook.ts @@ -17,42 +17,52 @@ */ import { useState, useEffect, useRef } from 'react'; -import { AxiosGetHelper } from '@/utils/axiosRequestHelper'; +import axios, { AxiosError, AxiosRequestConfig } from 'axios'; + +export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; export interface ApiState { data: T; loading: boolean; error: string | null; lastUpdated: number | null; + success: boolean; // For non-GET requests to indicate successful completion } export interface UseApiDataOptions { + method?: HttpMethod; retryAttempts?: number; retryDelay?: number; initialFetch?: boolean; - onError?: (error: string) => void; -}; + onError?: (error: AxiosError | string | unknown) => void; + onSuccess?: (data: any) => void; +} export function useApiData( url: string, defaultValue: T, options: UseApiDataOptions = {} ): ApiState & { - refetch: () => void; + execute: (data?: any) => Promise; + refetch: () => Promise; clearError: () => void; + reset: () => void; } { const { + method = 'GET', retryAttempts = 3, retryDelay = 1000, - initialFetch = true, - onError + initialFetch = method === 'GET', // Only auto-fetch for GET requests + onError, + onSuccess } = options; const [state, setState] = useState>({ data: defaultValue, loading: initialFetch, error: null, - lastUpdated: null + lastUpdated: null, + success: false }); const controllerRef = useRef(); @@ -61,15 +71,21 @@ export function useApiData( // Store stable references const urlRef = useRef(url); + const methodRef = useRef(method); const retryAttemptsRef = useRef(retryAttempts); const retryDelayRef = useRef(retryDelay); const onErrorRef = useRef(onError); + const onSuccessRef = useRef(onSuccess); // Update refs when props change useEffect(() => { urlRef.current = url; }, [url]); + useEffect(() => { + methodRef.current = method; + }, [method]); + useEffect(() => { retryAttemptsRef.current = retryAttempts; }, [retryAttempts]); @@ -82,40 +98,66 @@ export function useApiData( onErrorRef.current = onError; }, [onError]); + useEffect(() => { + onSuccessRef.current = onSuccess; + }, [onSuccess]); - const fetchData = async (isRetry = false) => { + const executeRequest = async (requestData?: any, isRetry = false) => { if (!isRetry) { - setState(prev => ({ ...prev, loading: true, error: null })); + setState(prev => ({ ...prev, loading: true, error: null, success: false })); retryCountRef.current = 0; } + // Cancel previous request + if (controllerRef.current) { + controllerRef.current.abort('New request initiated'); + } + + // Create new AbortController + controllerRef.current = new AbortController(); + try { - const { request, controller } = AxiosGetHelper( - urlRef.current, - controllerRef.current, - 'Request cancelled due to component unmount or new request' - ); - controllerRef.current = controller; - - const response = await request; + const config: AxiosRequestConfig = { + url: urlRef.current, + method: methodRef.current, + signal: controllerRef.current.signal, + }; + + // Add data for non-GET requests + if (methodRef.current !== 'GET' && requestData !== undefined) { + config.data = requestData; + } + + // Add query parameters for GET requests if data is provided as params + if (methodRef.current === 'GET' && requestData !== undefined) { + config.params = requestData; + } + + const response = await axios(config); setState({ data: response.data, loading: false, error: null, - lastUpdated: Date.now() + lastUpdated: Date.now(), + success: true }); + if (onSuccessRef.current) { + onSuccessRef.current(response.data); + } + retryCountRef.current = 0; + return response; } catch (error: any) { - if (error.name === 'CanceledError') { - return; + if (error.name === 'CanceledError' || error.name === 'AbortError') { + return Promise.reject(error); } const errorMessage = error.response?.data?.message || error.response?.statusText || error.message || - `Request failed with status: ${error.response?.status || 'unknown'}`; + `${methodRef.current} request failed with status: ${error.response?.status || 'unknown'}`; // Clear any existing retry timeout if (retryTimeoutRef.current) { @@ -127,36 +169,53 @@ export function useApiData( (!error.response?.status || error.response?.status >= 500)) { retryCountRef.current++; retryTimeoutRef.current = setTimeout(() => { - fetchData(true); + executeRequest(requestData, true); }, retryDelayRef.current * retryCountRef.current); - return; + return Promise.reject(error); } if (onErrorRef.current) { - onErrorRef.current(errorMessage); + onErrorRef.current(error); } setState({ data: defaultValue, loading: false, error: errorMessage, - lastUpdated: Date.now() + lastUpdated: Date.now(), + success: false }); + + return Promise.reject(error); } }; + const execute = (data?: any) => { + return executeRequest(data); + }; + const refetch = () => { - fetchData(); + return executeRequest(); }; const clearError = () => { setState(prev => ({ ...prev, error: null })); }; - // Initial fetch only + const reset = () => { + setState({ + data: defaultValue, + loading: false, + error: null, + lastUpdated: null, + success: false + }); + }; + + // Initial fetch for GET requests only useEffect(() => { - if (initialFetch) { - fetchData(); + if (initialFetch && methodRef.current === 'GET') { + executeRequest(); } // Cleanup retry timeout on unmount @@ -181,7 +240,35 @@ export function useApiData( return { ...state, + execute, refetch, - clearError + clearError, + reset }; } + +// Utility function for manual single requests (for dynamic/on-demand usage) +export async function fetchData( + url: string, + method: HttpMethod = 'GET', + data?: any +): Promise { + const controller = new AbortController(); + + const config: AxiosRequestConfig = { + url, + method, + signal: controller.signal, + }; + + if (method !== 'GET' && data !== undefined) { + config.data = data; + } + + if (method === 'GET' && data !== undefined) { + config.params = data; + } + + const response = await axios(config); + return response.data; +} diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/datanodes/datanodes.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/datanodes/datanodes.tsx index e9399d0da100..43da08fb9aec 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/datanodes/datanodes.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/datanodes/datanodes.tsx @@ -38,11 +38,7 @@ import DatanodesTable, { COLUMNS } from '@/v2/components/tables/datanodesTable'; import AutoReloadPanel from '@/components/autoReloadPanel/autoReloadPanel'; import { showDataFetchError } from '@/utils/common'; import { AutoReloadHelper } from '@/utils/autoReloadHelper'; -import { - AxiosGetHelper, - AxiosPutHelper, - cancelRequests -} from '@/utils/axiosRequestHelper'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; import { useDebounce } from '@/v2/hooks/useDebounce'; import { @@ -55,6 +51,10 @@ import { import './datanodes.less' +// Type for decommission API response +type DecommissionAPIResponse = { + DatanodesDecommissionInfo: DatanodeDecomissionInfo[]; +}; const defaultColumns = COLUMNS.map(column => ({ label: (typeof column.title === 'string') @@ -79,15 +79,46 @@ const COLUMN_UPDATE_DECOMMISSIONING = 'DECOMMISSIONING'; const Datanodes: React.FC<{}> = () => { - const cancelSignal = useRef(); - const cancelDecommissionSignal = useRef(); - const [state, setState] = useState({ lastUpdated: 0, columnOptions: defaultColumns, dataSource: [] }); - const [loading, setLoading] = useState(false); + + // API hooks for data fetching + const decommissionAPI = useApiData( + '/api/v1/datanodes/decommission/info', + { DatanodesDecommissionInfo: [] }, + { + initialFetch: false, + onError: (error) => showDataFetchError(error) + } + ); + + const datanodesAPI = useApiData( + '/api/v1/datanodes', + { datanodes: [], totalCount: 0 }, + { + initialFetch: false, + onError: (error) => showDataFetchError(error) + } + ); + + const removeDatanodesAPI = useApiData( + '/api/v1/datanodes/remove', + null, + { + method: 'PUT', + initialFetch: false, + onError: (error) => showDataFetchError(error), + onSuccess: () => { + loadData(); + setSelectedRows([]); + } + } + ); + + const loading = decommissionAPI.loading || datanodesAPI.loading || removeDatanodesAPI.loading; const [selectedColumns, setSelectedColumns] = useState(defaultColumns); const [selectedRows, setSelectedRows] = useState([]); const [searchTerm, setSearchTerm] = useState(''); @@ -100,62 +131,42 @@ const Datanodes: React.FC<{}> = () => { setSelectedColumns(selected as Option[]); } - async function loadDecommisionAPI() { - decommissionUuids = []; - const { request, controller } = await AxiosGetHelper( - '/api/v1/datanodes/decommission/info', - cancelDecommissionSignal.current - ); - cancelDecommissionSignal.current = controller; - return request + // These functions now just trigger the hooks to refetch + const loadDecommisionAPI = () => { + decommissionAPI.refetch(); + return Promise.resolve(decommissionAPI); }; - async function loadDataNodeAPI() { - const { request, controller } = await AxiosGetHelper( - '/api/v1/datanodes', - cancelSignal.current - ); - cancelSignal.current = controller; - return request; + const loadDataNodeAPI = () => { + datanodesAPI.refetch(); + return Promise.resolve(datanodesAPI); }; async function removeDatanode(selectedRowKeys: string[]) { - setLoading(true); - const { request, controller } = await AxiosPutHelper( - '/api/v1/datanodes/remove', - selectedRowKeys, - cancelSignal.current - ); - cancelSignal.current = controller; - request.then(() => { - loadData(); - }).catch((error) => { - showDataFetchError(error); - }).finally(() => { - setLoading(false); - setSelectedRows([]); - }); - } - - const loadData = async () => { - setLoading(true); - // Need to call decommission API on each interval to get updated status - // before datanode API call to compare UUID's - // update 'Operation State' column in table manually before rendering try { - let decomissionResponse = await loadDecommisionAPI(); - decommissionUuids = decomissionResponse.data?.DatanodesDecommissionInfo?.map( - (item: DatanodeDecomissionInfo) => item.datanodeDetails.uuid - ); + await removeDatanodesAPI.execute(selectedRowKeys); } catch (error) { - decommissionUuids = []; showDataFetchError(error); } + } - try { - const datanodesAPIResponse = await loadDataNodeAPI(); - const datanodesResponse: DatanodesResponse = datanodesAPIResponse.data; - const datanodes: DatanodeResponse[] = datanodesResponse.datanodes; + const loadData = () => { + // Trigger both API hooks to refetch data + decommissionAPI.refetch(); + datanodesAPI.refetch(); + }; + + // Process data when both APIs have loaded + useEffect(() => { + if (!decommissionAPI.loading && !datanodesAPI.loading && + decommissionAPI.data && datanodesAPI.data) { + + // Update decommission UUIDs + decommissionUuids = decommissionAPI.data?.DatanodesDecommissionInfo?.map( + (item: DatanodeDecomissionInfo) => item.datanodeDetails.uuid + ) || []; + + const datanodes: DatanodeResponse[] = datanodesAPI.data.datanodes; const dataSource: Datanode[] = datanodes?.map( (datanode) => ({ hostname: datanode.hostname, @@ -180,17 +191,14 @@ const Datanodes: React.FC<{}> = () => { networkLocation: datanode.networkLocation }) ); - setLoading(false); + setState({ ...state, dataSource: dataSource, lastUpdated: Number(moment()) }); - } catch (error) { - setLoading(false); - showDataFetchError(error) } - } + }, [decommissionAPI.loading, datanodesAPI.loading, decommissionAPI.data, datanodesAPI.data]); const autoReloadHelper: AutoReloadHelper = new AutoReloadHelper(loadData); @@ -200,10 +208,6 @@ const Datanodes: React.FC<{}> = () => { return (() => { autoReloadHelper.stopPolling(); - cancelRequests([ - cancelSignal.current!, - cancelDecommissionSignal.current! - ]); }); }, []); diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx index 834d4d59e6da..15f0a8cb8c60 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx @@ -16,12 +16,11 @@ * limitations under the License. */ -import React, { useState } from 'react'; -import axios, { CanceledError } from 'axios'; +import React, { useState, useEffect } from 'react'; import { Row, Col, Card, Result } from 'antd'; import { showDataFetchError } from '@/utils/common'; -import { PromiseAllSettledGetHelper } from '@/utils/axiosRequestHelper'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; import { Option } from '@/v2/components/select/multiSelect'; import FileSizeDistribution from '@/v2/components/plots/insightsFilePlot'; @@ -35,7 +34,6 @@ import { const Insights: React.FC<{}> = () => { - const [loading, setLoading] = useState(false); const [state, setState] = useState({ volumeBucketMap: new Map>(), volumeOptions: [], @@ -55,94 +53,97 @@ const Insights: React.FC<{}> = () => { }] }); - const cancelInsightSignal = React.useRef(); - - function loadData() { - setLoading(true); - const { requests, controller } = PromiseAllSettledGetHelper([ - '/api/v1/utilization/fileCount', - '/api/v1/utilization/containerCount' - ], cancelInsightSignal.current); - - cancelInsightSignal.current = controller; - requests.then(axios.spread(( - fileCountResponse: Awaited>, - containerCountResponse: Awaited> - ) => { - let fileAPIError; - let containerAPIError; - let responseError = [ - fileCountResponse, - containerCountResponse - ].filter((resp) => resp.status === 'rejected'); - - if (responseError.length !== 0) { - responseError.forEach((err) => { - if (err.reason.toString().includes('CancelledError')) { - throw new CanceledError('canceled', 'ERR_CANCELED'); - } else { - if (err.reason.config.url.includes("fileCount")) { - fileAPIError = err.reason.toString(); + // Individual API calls + const fileCountAPI = useApiData( + '/api/v1/utilization/fileCount', + [], + { + onError: (error) => showDataFetchError(error) + } + ); + + const containerCountAPI = useApiData( + '/api/v1/utilization/containerCount', + [], + { + onError: (error) => showDataFetchError(error) + } + ); + + const loading = fileCountAPI.loading || containerCountAPI.loading; + + const loadData = () => { + fileCountAPI.refetch(); + containerCountAPI.refetch(); + }; + + // Process the API responses when they're available + useEffect(() => { + if (!fileCountAPI.loading && !containerCountAPI.loading && + fileCountAPI.data && containerCountAPI.data) { + + // Extract errors + const fileAPIError = fileCountAPI.error; + const containerAPIError = containerCountAPI.error; + + // Process fileCount response only if successful + let volumeBucketMap = new Map>(); + let volumeOptions: Option[] = []; + + if (fileCountAPI.data && fileCountAPI.data.length > 0) { + // Construct volume -> bucket[] map for populating filters + volumeBucketMap = fileCountAPI.data.reduce( + (map: Map>, current: FileCountResponse) => { + const volume = current.volume; + const bucket = current.bucket; + if (map.has(volume)) { + const buckets = Array.from(map.get(volume)!); + map.set(volume, new Set([...buckets, bucket])); } else { - containerAPIError = err.reason.toString(); + map.set(volume, new Set().add(bucket)); } - } - }); + return map; + }, + new Map>() + ); + volumeOptions = Array.from(volumeBucketMap.keys()).map(k => ({ + label: k, + value: k + })); } - // Construct volume -> bucket[] map for populating filters - // Ex: vol1 -> [bucket1, bucket2], vol2 -> [bucket1] - const volumeBucketMap: Map> = fileCountResponse.value?.data?.reduce( - (map: Map>, current: FileCountResponse) => { - const volume = current.volume; - const bucket = current.bucket; - if (map.has(volume)) { - const buckets = Array.from(map.get(volume)!); - map.set(volume, new Set([...buckets, bucket])); - } else { - map.set(volume, new Set().add(bucket)); - } - return map; - }, - new Map>() - ); - const volumeOptions: Option[] = Array.from(volumeBucketMap.keys()).map(k => ({ - label: k, - value: k - })); - setState({ ...state, - volumeBucketMap: volumeBucketMap, - volumeOptions: volumeOptions, - fileCountError: fileAPIError, - containerSizeError: containerAPIError + volumeBucketMap, + volumeOptions, + fileCountError: fileAPIError || undefined, + containerSizeError: containerAPIError || undefined }); + setPlotResponse({ - fileCountResponse: fileCountResponse.value?.data ?? [{ + fileCountResponse: fileCountAPI.data || [{ volume: '', bucket: '', fileSize: 0, count: 0 }], - containerCountResponse: containerCountResponse.value?.data ?? [{ + containerCountResponse: containerCountAPI.data || [{ containerSize: 0, count: 0 }] }); - setLoading(false); - })).catch(error => { - setLoading(false); - showDataFetchError(error); - }) - } - - React.useEffect(() => { + } + }, [ + fileCountAPI.loading, + containerCountAPI.loading, + fileCountAPI.data, + containerCountAPI.data, + fileCountAPI.error, + containerCountAPI.error + ]); + + useEffect(() => { loadData(); - - return (() => { - cancelInsightSignal.current && cancelInsightSignal.current.abort(); - }) }, []); return ( From 6c2a6992fe6754192cc79a57e33a22f52975b7e6 Mon Sep 17 00:00:00 2001 From: Abhishek Pal Date: Thu, 18 Sep 2025 15:26:12 +0530 Subject: [PATCH 3/7] Fix network call issues --- .../src/v2/components/nuMetadata/nuMetadata.tsx | 13 ------------- .../src/v2/pages/insights/insights.tsx | 11 +---------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx index 243e14767d18..f61c45baf1f7 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx @@ -131,7 +131,6 @@ const NUMetadata: React.FC = ({ `/api/v1/namespace/summary?path=${path}`, {} as SummaryResponse, { - initialFetch: false, onError: (error) => showDataFetchError(error) } ); @@ -140,7 +139,6 @@ const NUMetadata: React.FC = ({ `/api/v1/namespace/quota?path=${path}`, {}, { - initialFetch: false, onError: (error) => showDataFetchError(error) } ); @@ -370,17 +368,6 @@ const NUMetadata: React.FC = ({ }, [summaryAPI.loading, quotaAPI.loading, summaryAPI.data, quotaAPI.data, summaryAPI.lastUpdated, quotaAPI.lastUpdated, processMetadata]); - const loadData = useCallback(() => { - // Trigger both API calls together - summaryAPI.refetch(); - quotaAPI.refetch(); - }, [summaryAPI, quotaAPI]); - - // Load data when path changes - useEffect(() => { - loadData(); - }, [path, loadData]); - return (
= () => { const loading = fileCountAPI.loading || containerCountAPI.loading; - const loadData = () => { - fileCountAPI.refetch(); - containerCountAPI.refetch(); - }; - // Process the API responses when they're available useEffect(() => { if (!fileCountAPI.loading && !containerCountAPI.loading && @@ -140,11 +135,7 @@ const Insights: React.FC<{}> = () => { containerCountAPI.data, fileCountAPI.error, containerCountAPI.error - ]); - - useEffect(() => { - loadData(); - }, []); + ]); return ( <> From d634917c2c7f8836bcd455e2e11370018ca848d9 Mon Sep 17 00:00:00 2001 From: Abhishek Pal Date: Tue, 7 Oct 2025 12:22:48 +0530 Subject: [PATCH 4/7] Fix duplicate network calls on Bucket and Volume page --- .../recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx | 1 - .../recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx index 774d5426f89c..328204f7a394 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx @@ -223,7 +223,6 @@ const Buckets: React.FC<{}> = () => { value: initialVolume }]); } - loadData(); return (() => { autoReloadHelper.stopPolling(); diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx index 683a50bd3768..0313548e7065 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx @@ -125,7 +125,6 @@ const Volumes: React.FC<{}> = () => { let autoReloadHelper: AutoReloadHelper = new AutoReloadHelper(loadData); useEffect(() => { - loadData(); autoReloadHelper.startPolling(); // Component will unmount From 74330ca5415a64718715f9b6d0a12a84ec705ff2 Mon Sep 17 00:00:00 2001 From: Abhishek Pal Date: Tue, 7 Oct 2025 13:58:19 +0530 Subject: [PATCH 5/7] Migrate other components to useDataApi --- .../v2/components/nuMetadata/nuMetadata.tsx | 13 +- .../insights/containerMismatchTable.tsx | 76 +++-- .../insights/deletePendingDirsTable.tsx | 66 ++-- .../insights/deletePendingKeysTable.tsx | 108 +++---- .../insights/deletedContainerKeysTable.tsx | 69 ++-- .../src/v2/pages/buckets/buckets.tsx | 159 +++++----- .../src/v2/pages/containers/containers.tsx | 295 ++++++++---------- .../src/v2/pages/datanodes/datanodes.tsx | 10 +- .../src/v2/pages/heatmap/heatmap.tsx | 191 ++++++------ .../pages/namespaceUsage/namespaceUsage.tsx | 91 +++--- .../src/v2/pages/pipelines/pipelines.tsx | 105 +++---- .../src/v2/pages/volumes/volumes.tsx | 111 +++---- 12 files changed, 615 insertions(+), 679 deletions(-) diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx index f61c45baf1f7..778b04e95db4 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/nuMetadata/nuMetadata.tsx @@ -125,12 +125,13 @@ const NUMetadata: React.FC = ({ }) => { const [state, setState] = useState([]); const [isProcessingData, setIsProcessingData] = useState(false); - // Individual API calls that resolve together const summaryAPI = useApiData( `/api/v1/namespace/summary?path=${path}`, {} as SummaryResponse, { + retryAttempts: 2, + initialFetch: false, onError: (error) => showDataFetchError(error) } ); @@ -139,12 +140,22 @@ const NUMetadata: React.FC = ({ `/api/v1/namespace/quota?path=${path}`, {}, { + retryAttempts: 2, + initialFetch: false, onError: (error) => showDataFetchError(error) } ); const loading = summaryAPI.loading || quotaAPI.loading || isProcessingData; + // Refetch data when path changes + useEffect(() => { + if (path) { + summaryAPI.refetch(); + quotaAPI.refetch(); + } + }, [path]); + const getObjectInfoMapping = useCallback((summaryResponse) => { const data: MetadataState = []; /** diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx index bc74af18c641..1548b36fbe0c 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Dropdown, Menu, @@ -37,7 +37,7 @@ import { ValueType } from 'react-select'; import Search from '@/v2/components/search/search'; import SingleSelect, { Option } from '@/v2/components/select/singleSelect'; import { showDataFetchError } from '@/utils/common'; -import { AxiosGetHelper } from '@/utils/axiosRequestHelper'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; import { useDebounce } from '@/v2/hooks/useDebounce'; import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants'; @@ -47,7 +47,6 @@ import { Pipelines } from '@/v2/types/insights.types'; - //-----Types----- type ContainerMismatchTableProps = { paginationConfig: TablePaginationConfig; @@ -57,6 +56,10 @@ type ContainerMismatchTableProps = { onRowExpand: (arg0: boolean, arg1: any) => void; } +const DEFAULT_MISMATCH_RESPONSE: MismatchContainersResponse = { + containerDiscrepancyInfo: [] +}; + //-----Components------ const ContainerMismatchTable: React.FC = ({ paginationConfig, @@ -65,19 +68,40 @@ const ContainerMismatchTable: React.FC = ({ expandedRowRender, handleLimitChange }) => { + const [data, setData] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + const [missingIn, setMissingIn] = useState('OM'); - const [loading, setLoading] = React.useState(false); - const [data, setData] = React.useState(); - const [searchTerm, setSearchTerm] = React.useState(''); - - const cancelSignal = React.useRef(); const debouncedSearch = useDebounce(searchTerm, 300); + // Use the modern hooks pattern + const mismatchData = useApiData( + `/api/v1/containers/mismatch?limit=${limit.value}&missingIn=${missingIn}`, + DEFAULT_MISMATCH_RESPONSE, + { + retryAttempts: 2, + initialFetch: false, + onError: (error) => showDataFetchError(error) + } + ); + + // Process data when it changes + useEffect(() => { + if (mismatchData.data && mismatchData.data.containerDiscrepancyInfo) { + setData(mismatchData.data.containerDiscrepancyInfo); + } + }, [mismatchData.data]); + + // Refetch when limit or missingIn changes + useEffect(() => { + mismatchData.refetch(); + }, [limit.value, missingIn]); + const handleExistAtChange: FilterMenuProps['onClick'] = ({ key }) => { if (key === 'OM') { - fetchMismatchContainers('SCM'); + setMissingIn('SCM'); } else { - fetchMismatchContainers('OM'); + setMissingIn('OM'); } } @@ -93,7 +117,6 @@ const ContainerMismatchTable: React.FC = ({ dataIndex: 'containerId', key: 'containerId', width: '20%' - }, { title: 'Count Of Keys', @@ -149,33 +172,6 @@ const ContainerMismatchTable: React.FC = ({ } ]; - function fetchMismatchContainers(missingIn: string) { - setLoading(true); - const { request, controller } = AxiosGetHelper( - `/api/v1/containers/mismatch?limit=${limit.value}&missingIn=${missingIn}`, - cancelSignal.current - ); - - cancelSignal.current = controller; - request.then(response => { - const mismatchedContainers: MismatchContainersResponse = response?.data; - setData(mismatchedContainers?.containerDiscrepancyInfo ?? []); - setLoading(false); - }).catch(error => { - setLoading(false); - showDataFetchError(error); - }) - } - - React.useEffect(() => { - //Fetch containers missing in OM by default - fetchMismatchContainers('OM'); - - return (() => { - cancelSignal.current && cancelSignal.current.abort(); - }) - }, [limit.value]); - return ( <>
@@ -202,7 +198,7 @@ const ContainerMismatchTable: React.FC = ({ }} dataSource={filterData(data)} columns={COLUMNS} - loading={loading} + loading={mismatchData.loading} pagination={paginationConfig} rowKey='containerId' locale={{ filterTitle: '' }} @@ -211,4 +207,4 @@ const ContainerMismatchTable: React.FC = ({ ) } -export default ContainerMismatchTable; \ No newline at end of file +export default ContainerMismatchTable; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingDirsTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingDirsTable.tsx index bebdf8bb78b5..1331221b6a58 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingDirsTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingDirsTable.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import Table, { ColumnsType, TablePaginationConfig @@ -25,9 +25,9 @@ import { ValueType } from 'react-select'; import Search from '@/v2/components/search/search'; import SingleSelect, { Option } from '@/v2/components/select/singleSelect'; -import { AxiosGetHelper } from '@/utils/axiosRequestHelper'; import { byteToSize, showDataFetchError } from '@/utils/common'; import { getFormattedTime } from '@/v2/utils/momentUtils'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; import { useDebounce } from '@/v2/hooks/useDebounce'; import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants'; @@ -40,6 +40,10 @@ type DeletePendingDirTableProps = { handleLimitChange: (arg0: ValueType) => void; } +const DEFAULT_DELETE_PENDING_DIRS_RESPONSE = { + deletedDirInfo: [] +}; + //-----Constants------ const COLUMNS: ColumnsType = [{ title: 'Directory Name', @@ -72,44 +76,40 @@ const DeletePendingDirTable: React.FC = ({ paginationConfig, handleLimitChange }) => { + const [data, setData] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); - const [loading, setLoading] = React.useState(false); - const [data, setData] = React.useState(); - const [searchTerm, setSearchTerm] = React.useState(''); - - const cancelSignal = React.useRef(); const debouncedSearch = useDebounce(searchTerm, 300); + // Use the modern hooks pattern + const deletePendingDirsData = useApiData<{ deletedDirInfo: DeletedDirInfo[] }>( + `/api/v1/keys/deletePending/dirs?limit=${limit.value}`, + DEFAULT_DELETE_PENDING_DIRS_RESPONSE, + { + retryAttempts: 2, + initialFetch: false, + onError: (error) => showDataFetchError(error) + } + ); + + // Process data when it changes + useEffect(() => { + if (deletePendingDirsData.data && deletePendingDirsData.data.deletedDirInfo) { + setData(deletePendingDirsData.data.deletedDirInfo); + } + }, [deletePendingDirsData.data]); + + // Refetch when limit changes + useEffect(() => { + deletePendingDirsData.refetch(); + }, [limit.value]); + function filterData(data: DeletedDirInfo[] | undefined) { return data?.filter( (data: DeletedDirInfo) => data.key.includes(debouncedSearch) ); } - function loadData() { - setLoading(true); - - const { request, controller } = AxiosGetHelper( - `/api/v1/keys/deletePending/dirs?limit=${limit.value}`, - cancelSignal.current - ); - cancelSignal.current = controller; - - request.then(response => { - setData(response?.data?.deletedDirInfo ?? []); - setLoading(false); - }).catch(error => { - setLoading(false); - showDataFetchError(error); - }); - } - - React.useEffect(() => { - loadData(); - - return (() => cancelSignal.current && cancelSignal.current.abort()); - }, [limit.value]); - return (<>
@@ -128,7 +128,7 @@ const DeletePendingDirTable: React.FC = ({ onChange={() => { }} />
= ({ ) } -export default DeletePendingDirTable; \ No newline at end of file +export default DeletePendingDirTable; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingKeysTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingKeysTable.tsx index 57da4bcbac1b..bf32bd155dec 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingKeysTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletePendingKeysTable.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import Table, { ColumnsType, TablePaginationConfig @@ -26,8 +26,8 @@ import { ValueType } from 'react-select'; import Search from '@/v2/components/search/search'; import SingleSelect, { Option } from '@/v2/components/select/singleSelect'; import ExpandedPendingKeysTable from '@/v2/components/tables/insights/expandedPendingKeysTable'; -import { AxiosGetHelper } from '@/utils/axiosRequestHelper'; import { byteToSize, showDataFetchError } from '@/utils/common'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; import { useDebounce } from '@/v2/hooks/useDebounce'; import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants'; @@ -54,6 +54,10 @@ type ExpandedDeletePendingKeys = { omKeyInfoList: DeletePendingKey[] } +const DEFAULT_DELETE_PENDING_KEYS_RESPONSE: DeletePendingKeysResponse = { + deletedKeyInfo: [] +}; + //------Constants------ const COLUMNS: ColumnsType = [ { @@ -79,52 +83,39 @@ const COLUMNS: ColumnsType = [ } ]; -let expandedDeletePendingKeys: ExpandedDeletePendingKeys[] = []; - //-----Components------ const DeletePendingKeysTable: React.FC = ({ paginationConfig, limit, handleLimitChange }) => { - const [loading, setLoading] = React.useState(false); - const [data, setData] = React.useState(); - const [searchTerm, setSearchTerm] = React.useState(''); + const [data, setData] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + const [expandedDeletePendingKeys, setExpandedDeletePendingKeys] = useState([]); - const cancelSignal = React.useRef(); const debouncedSearch = useDebounce(searchTerm, 300); - function filterData(data: DeletePendingKeysColumns[] | undefined) { - return data?.filter( - (data: DeletePendingKeysColumns) => data.keyName.includes(debouncedSearch) - ); - } - - function expandedRowRender(record: DeletePendingKeysColumns) { - const filteredData = expandedDeletePendingKeys?.flatMap((info) => ( - info.omKeyInfoList?.filter((key) => key.keyName === record.keyName) - )); - return ( - - ) - } - - function fetchDeletePendingKeys() { - setLoading(true); - const { request, controller } = AxiosGetHelper( - `/api/v1/keys/deletePending?limit=${limit.value}`, - cancelSignal.current - ); - cancelSignal.current = controller; + // Use the modern hooks pattern + const deletePendingKeysData = useApiData( + `/api/v1/keys/deletePending?limit=${limit.value}`, + DEFAULT_DELETE_PENDING_KEYS_RESPONSE, + { + retryAttempts: 2, + initialFetch: false, + onError: (error) => showDataFetchError(error) + } + ); + + // Process data when it changes + useEffect(() => { + if (deletePendingKeysData.data && deletePendingKeysData.data.deletedKeyInfo) { + const deletePendingKeys = deletePendingKeysData.data; + let deletedKeyData: DeletePendingKeysColumns[] = []; + let expandedData: ExpandedDeletePendingKeys[] = []; - request.then(response => { - const deletePendingKeys: DeletePendingKeysResponse = response?.data; - let deletedKeyData = []; // Sum up the data size and organize related key information - deletedKeyData = deletePendingKeys?.deletedKeyInfo?.flatMap((keyInfo) => { - expandedDeletePendingKeys.push(keyInfo); + deletedKeyData = deletePendingKeys.deletedKeyInfo?.flatMap((keyInfo) => { + expandedData.push(keyInfo); let count = 0; let item: DeletePendingKey = keyInfo.omKeyInfoList?.reduce((obj, curr) => { count += 1; @@ -138,24 +129,35 @@ const DeletePendingKeysTable: React.FC = ({ path: item.path, keyCount: count } - }); - setData(deletedKeyData); - setLoading(false); - }).catch(error => { - setLoading(false); - showDataFetchError(error); - }) - } + }) || []; - React.useEffect(() => { - fetchDeletePendingKeys(); - expandedDeletePendingKeys = []; + setData(deletedKeyData); + setExpandedDeletePendingKeys(expandedData); + } + }, [deletePendingKeysData.data]); - return (() => { - cancelSignal.current && cancelSignal.current.abort(); - }) + // Refetch when limit changes + useEffect(() => { + deletePendingKeysData.refetch(); }, [limit.value]); + function filterData(data: DeletePendingKeysColumns[] | undefined) { + return data?.filter( + (data: DeletePendingKeysColumns) => data.keyName.includes(debouncedSearch) + ); + } + + function expandedRowRender(record: DeletePendingKeysColumns) { + const filteredData = expandedDeletePendingKeys?.flatMap((info) => ( + info.omKeyInfoList?.filter((key) => key.keyName === record.keyName) + )); + return ( + + ) + } + return ( <>
@@ -181,7 +183,7 @@ const DeletePendingKeysTable: React.FC = ({ }} dataSource={filterData(data)} columns={COLUMNS} - loading={loading} + loading={deletePendingKeysData.loading} pagination={paginationConfig} rowKey='keyName' locale={{ filterTitle: '' }} @@ -190,4 +192,4 @@ const DeletePendingKeysTable: React.FC = ({ ) } -export default DeletePendingKeysTable; \ No newline at end of file +export default DeletePendingKeysTable; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletedContainerKeysTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletedContainerKeysTable.tsx index e45a008a74d0..4139bf97a40f 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletedContainerKeysTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/deletedContainerKeysTable.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import Table, { ColumnsType, TablePaginationConfig @@ -25,8 +25,8 @@ import { ValueType } from 'react-select'; import Search from '@/v2/components/search/search'; import SingleSelect, { Option } from '@/v2/components/select/singleSelect'; -import { AxiosGetHelper } from '@/utils/axiosRequestHelper'; import { showDataFetchError } from '@/utils/common'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; import { useDebounce } from '@/v2/hooks/useDebounce'; import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants'; @@ -45,6 +45,10 @@ type DeletedContainerKeysTableProps = { expandedRowRender: (arg0: any) => JSX.Element; } +const DEFAULT_DELETED_CONTAINER_KEYS_RESPONSE: DeletedContainerKeysResponse = { + containers: [] +}; + //------Constants------ const COLUMNS: ColumnsType = [ { @@ -83,47 +87,40 @@ const DeletedContainerKeysTable: React.FC = ({ onRowExpand, expandedRowRender }) => { + const [data, setData] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); - const [loading, setLoading] = React.useState(false); - const [data, setData] = React.useState(); - const [searchTerm, setSearchTerm] = React.useState(''); - - const cancelSignal = React.useRef(); const debouncedSearch = useDebounce(searchTerm, 300); + // Use the modern hooks pattern + const deletedContainerKeysData = useApiData( + `/api/v1/containers/mismatch/deleted?limit=${limit.value}`, + DEFAULT_DELETED_CONTAINER_KEYS_RESPONSE, + { + retryAttempts: 2, + initialFetch: false, + onError: (error) => showDataFetchError(error) + } + ); + + // Process data when it changes + useEffect(() => { + if (deletedContainerKeysData.data && deletedContainerKeysData.data.containers) { + setData(deletedContainerKeysData.data.containers); + } + }, [deletedContainerKeysData.data]); + + // Refetch when limit changes + useEffect(() => { + deletedContainerKeysData.refetch(); + }, [limit.value]); + function filterData(data: Container[] | undefined) { return data?.filter( (data: Container) => data.containerId.toString().includes(debouncedSearch) ); } - function fetchDeletedKeys() { - const { request, controller } = AxiosGetHelper( - `/api/v1/containers/mismatch/deleted?limit=${limit.value}`, - cancelSignal.current - ) - cancelSignal.current = controller; - - request.then(response => { - setLoading(true); - const deletedContainerKeys: DeletedContainerKeysResponse = response?.data; - setData(deletedContainerKeys?.containers ?? []); - setLoading(false); - }).catch(error => { - setLoading(false); - showDataFetchError(error); - }); - } - - React.useEffect(() => { - fetchDeletedKeys(); - - return (() => { - cancelSignal.current && cancelSignal.current.abort(); - }) - }, [limit.value]); - - return ( <>
@@ -150,7 +147,7 @@ const DeletedContainerKeysTable: React.FC = ({ }} dataSource={filterData(data)} columns={COLUMNS} - loading={loading} + loading={deletedContainerKeysData.loading} pagination={paginationConfig} rowKey='containerId' locale={{ filterTitle: '' }} @@ -159,4 +156,4 @@ const DeletedContainerKeysTable: React.FC = ({ ) } -export default DeletedContainerKeysTable; \ No newline at end of file +export default DeletedContainerKeysTable; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx index 328204f7a394..3d7fda9cb3fe 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx @@ -16,11 +16,10 @@ * limitations under the License. */ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import moment from 'moment'; import { ValueType } from 'react-select'; import { useLocation } from 'react-router-dom'; -import { AxiosError } from 'axios'; import AutoReloadPanel from '@/components/autoReloadPanel/autoReloadPanel'; import AclPanel from '@/v2/components/aclDrawer/aclDrawer'; @@ -29,11 +28,11 @@ import MultiSelect from '@/v2/components/select/multiSelect'; import SingleSelect, { Option } from '@/v2/components/select/singleSelect'; import BucketsTable, { COLUMNS } from '@/v2/components/tables/bucketsTable'; -import { AutoReloadHelper } from '@/utils/autoReloadHelper'; -import { AxiosGetHelper, cancelRequests } from "@/utils/axiosRequestHelper"; import { showDataFetchError } from '@/utils/common'; import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants'; import { useDebounce } from '@/v2/hooks/useDebounce'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; +import { useAutoReload } from '@/v2/hooks/useAutoReload.hook'; import { Bucket, @@ -56,6 +55,11 @@ const defaultColumns = COLUMNS.map(column => ({ value: column.key as string })); +const DEFAULT_BUCKET_RESPONSE: BucketResponse = { + totalCount: 0, + buckets: [] +}; + function getVolumeBucketMap(data: Bucket[]) { const volumeBucketMap = data.reduce(( map: Map>, @@ -92,9 +96,6 @@ function getFilteredBuckets( } const Buckets: React.FC<{}> = () => { - - const cancelSignal = useRef(); - const [state, setState] = useState({ totalCount: 0, lastUpdated: 0, @@ -103,7 +104,6 @@ const Buckets: React.FC<{}> = () => { bucketsUnderVolume: [], volumeOptions: [], }); - const [loading, setLoading] = useState(false); const [selectedColumns, setSelectedColumns] = useState(defaultColumns); const [selectedVolumes, setSelectedVolumes] = useState([]); const [selectedLimit, setSelectedLimit] = useState
- {isLoading + {loading ? : (Object.keys(heatmapResponse).length > 0 && (heatmapResponse.label !== null || heatmapResponse.path !== null)) ?
@@ -384,4 +385,4 @@ const Heatmap: React.FC<{}> = () => { ); } -export default Heatmap; \ No newline at end of file +export default Heatmap; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/namespaceUsage/namespaceUsage.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/namespaceUsage/namespaceUsage.tsx index 5f9a4859a4fc..3c5ac08d2b58 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/namespaceUsage/namespaceUsage.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/namespaceUsage/namespaceUsage.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import React, { useRef, useState } from 'react'; +import React, { useState, useCallback } from 'react'; import { Alert, Button, Tooltip } from 'antd'; import { InfoCircleFilled, ReloadOutlined, } from '@ant-design/icons'; import { ValueType } from 'react-select'; @@ -26,7 +26,7 @@ import NUPieChart from '@/v2/components/plots/nuPieChart'; import SingleSelect, { Option } from '@/v2/components/select/singleSelect'; import DUBreadcrumbNav from '@/v2/components/duBreadcrumbNav/duBreadcrumbNav'; import { showDataFetchError, showInfoNotification } from '@/utils/common'; -import { AxiosGetHelper, cancelRequests } from '@/utils/axiosRequestHelper'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; import { NUResponse } from '@/v2/types/namespaceUsage.types'; @@ -40,37 +40,39 @@ const LIMIT_OPTIONS: Option[] = [ { label: '30', value: '30' } ] +const DEFAULT_NU_RESPONSE: NUResponse = { + status: '', + path: '/', + subPathCount: 0, + size: 0, + sizeWithReplica: 0, + subPaths: [], + sizeDirectKey: 0 +}; + const NamespaceUsage: React.FC<{}> = () => { - const [loading, setLoading] = useState(false); const [limit, setLimit] = useState
diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/pipelines/pipelines.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/pipelines/pipelines.tsx index 645c975c2327..a99d300d4d86 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/pipelines/pipelines.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/pipelines/pipelines.tsx @@ -16,23 +16,18 @@ * limitations under the License. */ -import React, { - useEffect, - useRef, - useState -} from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import moment from 'moment'; import { ValueType } from 'react-select'; - import AutoReloadPanel from '@/components/autoReloadPanel/autoReloadPanel'; import Search from '@/v2/components/search/search'; import MultiSelect, { Option } from '@/v2/components/select/multiSelect'; import PipelinesTable, { COLUMNS } from '@/v2/components/tables/pipelinesTable'; import { showDataFetchError } from '@/utils/common'; -import { AutoReloadHelper } from '@/utils/autoReloadHelper'; -import { AxiosGetHelper, cancelRequests } from '@/utils/axiosRequestHelper'; import { useDebounce } from '@/v2/hooks/useDebounce'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; +import { useAutoReload } from '@/v2/hooks/useAutoReload.hook'; import { Pipeline, @@ -42,7 +37,6 @@ import { import './pipelines.less'; - const defaultColumns = COLUMNS.map(column => ({ label: (typeof column.title === 'string') ? column.title @@ -50,76 +44,74 @@ const defaultColumns = COLUMNS.map(column => ({ value: column.key as string, })); -const Pipelines: React.FC<{}> = () => { - const cancelSignal = useRef(); +const DEFAULT_PIPELINES_RESPONSE: PipelinesResponse = { + totalCount: 0, + pipelines: [] +}; +const Pipelines: React.FC<{}> = () => { const [state, setState] = useState({ activeDataSource: [], columnOptions: defaultColumns, lastUpdated: 0, }); - const [loading, setLoading] = useState(false); const [selectedColumns, setSelectedColumns] = useState(defaultColumns); const [searchTerm, setSearchTerm] = useState(''); const debouncedSearch = useDebounce(searchTerm, 300); - const loadData = () => { - setLoading(true); - //Cancel any previous requests - cancelRequests([cancelSignal.current!]); - - const { request, controller } = AxiosGetHelper( - '/api/v1/pipelines', - cancelSignal.current - ); + // Use the modern hooks pattern + const pipelinesData = useApiData( + '/api/v1/pipelines', + DEFAULT_PIPELINES_RESPONSE, + { + retryAttempts: 2, + initialFetch: false, + onError: (error) => showDataFetchError(error) + } + ); - cancelSignal.current = controller; - request.then(response => { - const pipelinesResponse: PipelinesResponse = response.data; - const pipelines: Pipeline[] = pipelinesResponse?.pipelines ?? {}; + // Process pipelines data when it changes + useEffect(() => { + if (pipelinesData.data && pipelinesData.data.pipelines) { + const pipelines: Pipeline[] = pipelinesData.data.pipelines; setState({ ...state, activeDataSource: pipelines, lastUpdated: Number(moment()) - }) - setLoading(false); - }).catch(error => { - setLoading(false); - showDataFetchError(error); - }) - } - - const autoReloadHelper: AutoReloadHelper = new AutoReloadHelper(loadData); - - useEffect(() => { - autoReloadHelper.startPolling(); - loadData(); - return (() => { - autoReloadHelper.stopPolling(); - cancelRequests([cancelSignal.current!]); - }) - }, []); + }); + } + }, [pipelinesData.data]); function handleColumnChange(selected: ValueType) { setSelectedColumns(selected as Option[]); } - const { - activeDataSource, - columnOptions, - lastUpdated - } = state; + function handleTagClose(label: string) { + setSelectedColumns( + selectedColumns.filter((column) => column.label !== label) + ); + } + + // Create refresh function for auto-reload + const loadPipelinesData = () => { + pipelinesData.refetch(); + }; + + const autoReload = useAutoReload(loadPipelinesData); + + const { activeDataSource, lastUpdated, columnOptions } = state; return ( <>
Pipelines + togglePolling={autoReload.handleAutoReloadToggle} + onReload={loadPipelinesData} + />
@@ -131,7 +123,7 @@ const Pipelines: React.FC<{}> = () => { selected={selectedColumns} placeholder='Columns' onChange={handleColumnChange} - onTagClose={() => { }} + onTagClose={handleTagClose} fixedColumn='pipelineId' columnLength={COLUMNS.length} />
@@ -142,14 +134,14 @@ const Pipelines: React.FC<{}> = () => { value: 'pipelineId' }]} searchInput={searchTerm} - searchColumn={'pipelineId'} + searchColumn='pipelineId' onSearchChange={ (e: React.ChangeEvent) => setSearchTerm(e.target.value) } - onChange={() => { }} /> + onChange={() => setSearchTerm('')} />
@@ -158,4 +150,5 @@ const Pipelines: React.FC<{}> = () => { ); } -export default Pipelines; \ No newline at end of file + +export default Pipelines; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx index 0313548e7065..6da64cb52ad9 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/volumes/volumes.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import moment from 'moment'; import { ValueType } from 'react-select/src/types'; @@ -28,10 +28,10 @@ import VolumesTable, { COLUMNS } from '@/v2/components/tables/volumesTable'; import Search from '@/v2/components/search/search'; import { showDataFetchError } from '@/utils/common'; -import { AutoReloadHelper } from '@/utils/autoReloadHelper'; -import { AxiosGetHelper, cancelRequests } from "@/utils/axiosRequestHelper"; import { LIMIT_OPTIONS } from '@/v2/constants/limit.constants'; import { useDebounce } from '@/v2/hooks/useDebounce'; +import { useApiData } from '@/v2/hooks/useAPIData.hook'; +import { useAutoReload } from '@/v2/hooks/useAutoReload.hook'; import { Volume, @@ -56,10 +56,12 @@ const SearchableColumnOpts = [ } ] -const Volumes: React.FC<{}> = () => { - - const cancelSignal = useRef(); +const DEFAULT_VOLUMES_RESPONSE: VolumesResponse = { + totalCount: 0, + volumes: [] +}; +const Volumes: React.FC<{}> = () => { const defaultColumns = COLUMNS.map(column => ({ label: column.title as string, value: column.key as string, @@ -70,7 +72,6 @@ const Volumes: React.FC<{}> = () => { lastUpdated: 0, columnOptions: defaultColumns }); - const [loading, setLoading] = useState(false); const [currentRow, setCurrentRow] = useState>({}); const [selectedColumns, setSelectedColumns] = useState(defaultColumns); const [selectedLimit, setSelectedLimit] = useState