This is an automated email from the ASF dual-hosted git repository.
arafat2198 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new c937649d46e HDDS-13677. Update Axios to 1.9.0 and improve error
handling (#9025).
c937649d46e is described below
commit c937649d46e7df7949a43849b9942aa07ef6453b
Author: Abhishek Pal <[email protected]>
AuthorDate: Fri Oct 10 15:48:52 2025 +0530
HDDS-13677. Update Axios to 1.9.0 and improve error handling (#9025).
---
.../webapps/recon/ozone-recon-web/package.json | 2 +-
.../webapps/recon/ozone-recon-web/pnpm-lock.yaml | 8 +-
.../src/components/navBar/navBar.tsx | 2 +-
.../recon/ozone-recon-web/src/utils/common.tsx | 48 +++--
.../decommissioningSummary.tsx | 78 +++-----
.../src/v2/components/navBar/navBar.tsx | 38 +---
.../src/v2/components/nuMetadata/nuMetadata.tsx | 116 ++++++------
.../src/v2/components/tables/containersTable.tsx | 30 ++-
.../tables/insights/containerMismatchTable.tsx | 77 ++++----
.../tables/insights/deletePendingDirsTable.tsx | 67 ++++---
.../tables/insights/deletePendingKeysTable.tsx | 109 +++++------
.../tables/insights/deletedContainerKeysTable.tsx | 70 ++++---
.../components/tables/insights/openKeysTable.tsx | 88 ++++-----
.../src/v2/hooks/useAPIData.hook.ts | 204 ++++++++++++++-------
.../src/v2/pages/buckets/buckets.tsx | 159 ++++++++--------
.../src/v2/pages/containers/containers.tsx | 132 +++++++------
.../src/v2/pages/datanodes/datanodes.tsx | 143 +++++++--------
.../src/v2/pages/heatmap/heatmap.tsx | 175 ++++++++----------
.../src/v2/pages/insights/insights.tsx | 147 +++++++--------
.../src/v2/pages/insights/omInsights.tsx | 3 +-
.../src/v2/pages/namespaceUsage/namespaceUsage.tsx | 82 ++++-----
.../src/v2/pages/pipelines/pipelines.tsx | 104 +++++------
.../src/v2/pages/volumes/volumes.tsx | 113 +++++-------
.../ozone-recon-web/src/views/buckets/buckets.tsx | 2 +-
.../src/views/datanodes/datanodes.tsx | 6 +-
.../src/views/datanodes/decommissionSummary.tsx | 2 +-
.../src/views/diskUsage/diskUsage.tsx | 8 +-
.../src/views/insights/insights.tsx | 6 +-
.../ozone-recon-web/src/views/insights/om/om.tsx | 18 +-
.../views/missingContainers/missingContainers.tsx | 4 +-
.../src/views/overview/overview.tsx | 4 +-
.../src/views/pipelines/pipelines.tsx | 2 +-
.../ozone-recon-web/src/views/volumes/volumes.tsx | 2 +-
33 files changed, 1005 insertions(+), 1044 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 cb749226cab..bdd0da326cf 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.2",
+ "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 543ed58e36e..0223816ef04 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([email protected])([email protected])
axios:
- specifier: ^0.30.2
- version: 0.30.2
+ specifier: ~1.9.0
+ version: 1.9.0
classnames:
specifier: ^2.3.2
version: 2.5.1
@@ -1908,8 +1908,8 @@ packages:
resolution: {integrity:
sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==}
dev: true
- /[email protected]:
- resolution: {integrity:
sha512-0pE4RQ4UQi1jKY6p7u6i1Tkzqmu+d+/tHS7Q7rKunWLB9WyilBTpHHpXzPNMDj5hTbK0B0PTLSz07yqMBiF6xg==}
+ /[email protected]:
+ resolution: {integrity:
sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==}
dependencies:
follow-redirects: 1.15.11
form-data: 4.0.4
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 b0bdf187cb3..18d8fa70480 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<INavBarProps> {
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 59e4d180f44..e5509538328 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<Promise<any>>[]) => {
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 34e72b0889a..ebf3ed67eba 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<string, unknown>;
-};
function getDescriptions(summaryData: SummaryData): React.ReactElement {
const {
@@ -67,59 +62,34 @@ function getDescriptions(summaryData: SummaryData):
React.ReactElement {
const DecommissionSummary: React.FC<DecommisioningSummaryProps> = ({
uuid = ''
}) => {
- const [state, setState] = React.useState<DecommisioningSummaryState>({
- summaryData: {},
- loading: false
- });
- const cancelSignal = React.useRef<AbortController>();
+ 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 = (
<Spin
size='large'
style={{ margin: '15px 15px 10px 15px' }} />
);
- 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 as AxiosError).toString());
- content = (
- <Result
- status='error'
- title='Unable to fetch Decommission Summary data'
- className='decommission-summary-result' />
- )
- }
- }
-
- useEffect(() => {
- fetchDecommissionSummary(uuid);
- return (() => {
- cancelRequests([cancelSignal.current!]);
- })
- }, []);
-
- const { summaryData } = state;
- if (summaryData?.datanodeDetails
+ if (error) {
+ content = (
+ <Result
+ status='error'
+ title='Unable to fetch Decommission Summary data'
+ className='decommission-summary-result' />
+ );
+ } 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 518afef0198..3cc6b2aca91 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<NavBarProps> = ({
collapsed = false,
onCollapse = () => { }
}) => {
- const [isHeatmapEnabled, setIsHeatmapEnabled] = useState<boolean>(false);
- const cancelDisabledFeatureSignal = useRef<AbortController>();
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<string[]> = await request;
- const heatmapDisabled = response?.data?.includes('HEATMAP')
- setIsHeatmapEnabled(!heatmapDisabled);
- } catch (error: unknown) {
- showDataFetchError((error as Error).toString())
+
+ const { data: disabledFeatures, error } = useApiData<string[]>(
+ '/api/v1/features/disabledFeatures',
+ [],
+ {
+ onError: (error) => showDataFetchError(error)
}
- }
-
+ );
- useEffect(() => {
- fetchDisabledFeatures();
- // Component will unmount
- return (() => {
- cancelRequests([cancelDisabledFeatureSignal.current!])
- })
- }, [])
+ const isHeatmapEnabled = !disabledFeatures.includes('HEATMAP');
const menuItems = [(
<Menu.Item key='/Overview'
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 eeb7475e52a..bcea9ab40cf 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
@@ -16,13 +16,12 @@
* limitations under the License.
*/
-import React, {useRef, useState} from 'react';
+import React, {useState, useEffect, useCallback} from 'react';
import moment from 'moment';
-import axios, {AxiosError} from 'axios';
import {Table} from 'antd';
-import {AxiosGetHelper, cancelRequests, PromiseAllSettledGetHelper} from
'@/utils/axiosRequestHelper';
-import {byteToSize, checkResponseError, removeDuplicatesAndMerge,
showDataFetchError} from '@/utils/common';
+import {byteToSize, removeDuplicatesAndMerge, showDataFetchError} from
'@/utils/common';
+import {useApiData, fetchData} from '@/v2/hooks/useAPIData.hook';
import {Acl} from '@/v2/types/acl.types';
@@ -124,12 +123,30 @@ type MetadataState = {
const NUMetadata: React.FC<MetadataProps> = ({
path = '/'
}) => {
- const [loading, setLoading] = useState<boolean>(false);
const [state, setState] = useState<MetadataState>([]);
- const keyMetadataSummarySignal = useRef<AbortController>();
- const cancelMetadataSignal = useRef<AbortController>();
+ const [isProcessingData, setIsProcessingData] = useState<boolean>(false);
+ // Individual API calls that resolve together
+ const summaryAPI = useApiData<SummaryResponse>(
+ `/api/v1/namespace/summary?path=${path}`,
+ {} as SummaryResponse,
+ {
+ retryAttempts: 2,
+ onError: (error) => showDataFetchError(error)
+ }
+ );
+
+ const quotaAPI = useApiData<any>(
+ `/api/v1/namespace/quota?path=${path}`,
+ {},
+ {
+ retryAttempts: 2,
+ 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 +247,15 @@ const NUMetadata: React.FC<MetadataProps> = ({
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<Promise<any>>,
- quotaApiResponse: Awaited<Promise<any>>,
- ) => {
- 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 +276,27 @@ const NUMetadata: React.FC<MetadataProps> = ({
// 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 => {
- showDataFetchError(error.toString());
- });
- return;
+ return;
+ } catch (error) {
+ showDataFetchError(error);
+ return;
+ }
}
data = removeDuplicatesAndMerge(data,
getObjectInfoMapping(summaryResponse), 'key');
@@ -307,7 +311,7 @@ const NUMetadata: React.FC<MetadataProps> = ({
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 +320,10 @@ const NUMetadata: React.FC<MetadataProps> = ({
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 +347,27 @@ const NUMetadata: React.FC<MetadataProps> = ({
data.push({
key: 'Quota Used',
value: byteToSize(quotaResponse.used, 3)
- })
+ });
}
}
+
setState(data);
- })).catch(error => {
- showDataFetchError((error as AxiosError).toString());
- });
- }
-
- React.useEffect(() => {
- setLoading(true);
- loadData(path);
- setLoading(false);
-
- return (() => {
- cancelRequests([
- cancelMetadataSignal.current!,
- ]);
- })
- }, [path]);
+ } catch (error) {
+ showDataFetchError(error);
+ } 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]);
return (
<Table
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 424d58cf245..dba9a3a350f 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
@@ -16,10 +16,9 @@
* limitations under the License.
*/
-import React, {useRef} from 'react';
+import React from 'react';
import filesize from 'filesize';
-import { AxiosError } from 'axios';
import { Popover, Table } from 'antd';
import {
ColumnsType,
@@ -29,7 +28,7 @@ import { CheckCircleOutlined, NodeIndexOutlined } from
'@ant-design/icons';
import {getFormattedTime} from '@/v2/utils/momentUtils';
import {showDataFetchError} from '@/utils/common';
-import {AxiosGetHelper} from '@/utils/axiosRequestHelper';
+import {fetchData} from '@/v2/hooks/useAPIData.hook';
import {
Container,
ContainerKeysResponse,
@@ -182,7 +181,6 @@ const ContainerTable: React.FC<ContainerTableProps> = ({
searchTerm = ''
}) => {
- const cancelSignal = useRef<AbortController>();
function filterSelectedColumns() {
const columnKeys = selectedColumns.map((column) => column.value);
@@ -191,15 +189,12 @@ const ContainerTable: React.FC<ContainerTableProps> = ({
);
}
- 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<ContainerKeysResponse>(
+ `/api/v1/containers/${containerID}/keys`
+ );
+
expandedRowSetter({
...expandedRow,
[containerID]: {
@@ -209,7 +204,7 @@ const ContainerTable: React.FC<ContainerTableProps> = ({
totalCount: containerKeysResponse.totalCount
}
});
- }).catch(error => {
+ } catch (error) {
expandedRowSetter({
...expandedRow,
[containerID]: {
@@ -217,8 +212,8 @@ const ContainerTable: React.FC<ContainerTableProps> = ({
loading: false
}
});
- showDataFetchError((error as AxiosError).toString());
- });
+ showDataFetchError(error);
+ }
}
function getFilteredData(data: Container[]) {
@@ -236,9 +231,6 @@ const ContainerTable: React.FC<ContainerTableProps> = ({
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/containerMismatchTable.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/insights/containerMismatchTable.tsx
index 565acde6db7..1548b36fbe0 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,8 +16,7 @@
* limitations under the License.
*/
-import React from 'react';
-import { AxiosError } from 'axios';
+import React, { useState, useEffect } from 'react';
import {
Dropdown,
Menu,
@@ -38,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';
@@ -48,7 +47,6 @@ import {
Pipelines
} from '@/v2/types/insights.types';
-
//-----Types-----
type ContainerMismatchTableProps = {
paginationConfig: TablePaginationConfig;
@@ -58,6 +56,10 @@ type ContainerMismatchTableProps = {
onRowExpand: (arg0: boolean, arg1: any) => void;
}
+const DEFAULT_MISMATCH_RESPONSE: MismatchContainersResponse = {
+ containerDiscrepancyInfo: []
+};
+
//-----Components------
const ContainerMismatchTable: React.FC<ContainerMismatchTableProps> = ({
paginationConfig,
@@ -66,19 +68,40 @@ const ContainerMismatchTable:
React.FC<ContainerMismatchTableProps> = ({
expandedRowRender,
handleLimitChange
}) => {
+ const [data, setData] = useState<Container[]>([]);
+ const [searchTerm, setSearchTerm] = useState<string>('');
+ const [missingIn, setMissingIn] = useState<string>('OM');
- const [loading, setLoading] = React.useState<boolean>(false);
- const [data, setData] = React.useState<Container[]>();
- const [searchTerm, setSearchTerm] = React.useState<string>('');
-
- const cancelSignal = React.useRef<AbortController>();
const debouncedSearch = useDebounce(searchTerm, 300);
+ // Use the modern hooks pattern
+ const mismatchData = useApiData<MismatchContainersResponse>(
+ `/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');
}
}
@@ -94,7 +117,6 @@ const ContainerMismatchTable:
React.FC<ContainerMismatchTableProps> = ({
dataIndex: 'containerId',
key: 'containerId',
width: '20%'
-
},
{
title: 'Count Of Keys',
@@ -150,33 +172,6 @@ const ContainerMismatchTable:
React.FC<ContainerMismatchTableProps> = ({
}
];
- 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 as AxiosError).toString());
- })
- }
-
- React.useEffect(() => {
- //Fetch containers missing in OM by default
- fetchMismatchContainers('OM');
-
- return (() => {
- cancelSignal.current && cancelSignal.current.abort();
- })
- }, [limit.value]);
-
return (
<>
<div className='table-header-section'>
@@ -203,7 +198,7 @@ const ContainerMismatchTable:
React.FC<ContainerMismatchTableProps> = ({
}}
dataSource={filterData(data)}
columns={COLUMNS}
- loading={loading}
+ loading={mismatchData.loading}
pagination={paginationConfig}
rowKey='containerId'
locale={{ filterTitle: '' }}
@@ -212,4 +207,4 @@ const ContainerMismatchTable:
React.FC<ContainerMismatchTableProps> = ({
)
}
-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 190754b9388..1331221b6a5 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,8 +16,7 @@
* limitations under the License.
*/
-import React from 'react';
-import { AxiosError } from 'axios';
+import React, { useState, useEffect } from 'react';
import Table, {
ColumnsType,
TablePaginationConfig
@@ -26,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';
@@ -41,6 +40,10 @@ type DeletePendingDirTableProps = {
handleLimitChange: (arg0: ValueType<Option, false>) => void;
}
+const DEFAULT_DELETE_PENDING_DIRS_RESPONSE = {
+ deletedDirInfo: []
+};
+
//-----Constants------
const COLUMNS: ColumnsType<DeletedDirInfo> = [{
title: 'Directory Name',
@@ -73,44 +76,40 @@ const DeletePendingDirTable:
React.FC<DeletePendingDirTableProps> = ({
paginationConfig,
handleLimitChange
}) => {
+ const [data, setData] = useState<DeletedDirInfo[]>([]);
+ const [searchTerm, setSearchTerm] = useState<string>('');
- const [loading, setLoading] = React.useState<boolean>(false);
- const [data, setData] = React.useState<DeletedDirInfo[]>();
- const [searchTerm, setSearchTerm] = React.useState<string>('');
-
- const cancelSignal = React.useRef<AbortController>();
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 as AxiosError).toString());
- });
- }
-
- React.useEffect(() => {
- loadData();
-
- return (() => cancelSignal.current && cancelSignal.current.abort());
- }, [limit.value]);
-
return (<>
<div className='table-header-section'>
<div className='table-filter-section'>
@@ -129,7 +128,7 @@ const DeletePendingDirTable:
React.FC<DeletePendingDirTableProps> = ({
onChange={() => { }} />
</div>
<Table
- loading={loading}
+ loading={deletePendingDirsData.loading}
dataSource={filterData(data)}
columns={COLUMNS}
pagination={paginationConfig}
@@ -139,4 +138,4 @@ const DeletePendingDirTable:
React.FC<DeletePendingDirTableProps> = ({
</>)
}
-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 81ed9020c2b..bf32bd155de 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,8 +16,7 @@
* limitations under the License.
*/
-import React from 'react';
-import { AxiosError } from 'axios';
+import React, { useState, useEffect } from 'react';
import Table, {
ColumnsType,
TablePaginationConfig
@@ -27,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';
@@ -55,6 +54,10 @@ type ExpandedDeletePendingKeys = {
omKeyInfoList: DeletePendingKey[]
}
+const DEFAULT_DELETE_PENDING_KEYS_RESPONSE: DeletePendingKeysResponse = {
+ deletedKeyInfo: []
+};
+
//------Constants------
const COLUMNS: ColumnsType<DeletePendingKeysColumns> = [
{
@@ -80,52 +83,39 @@ const COLUMNS: ColumnsType<DeletePendingKeysColumns> = [
}
];
-let expandedDeletePendingKeys: ExpandedDeletePendingKeys[] = [];
-
//-----Components------
const DeletePendingKeysTable: React.FC<DeletePendingKeysTableProps> = ({
paginationConfig,
limit,
handleLimitChange
}) => {
- const [loading, setLoading] = React.useState<boolean>(false);
- const [data, setData] = React.useState<DeletePendingKeysColumns[]>();
- const [searchTerm, setSearchTerm] = React.useState<string>('');
+ const [data, setData] = useState<DeletePendingKeysColumns[]>([]);
+ const [searchTerm, setSearchTerm] = useState<string>('');
+ const [expandedDeletePendingKeys, setExpandedDeletePendingKeys] =
useState<ExpandedDeletePendingKeys[]>([]);
- const cancelSignal = React.useRef<AbortController>();
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 (
- <ExpandedPendingKeysTable
- data={filteredData}
- paginationConfig={paginationConfig} />
- )
- }
-
- 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<DeletePendingKeysResponse>(
+ `/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;
@@ -139,24 +129,35 @@ const DeletePendingKeysTable:
React.FC<DeletePendingKeysTableProps> = ({
path: item.path,
keyCount: count
}
- });
- setData(deletedKeyData);
- setLoading(false);
- }).catch(error => {
- setLoading(false);
- showDataFetchError((error as AxiosError).toString());
- })
- }
+ }) || [];
- 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 (
+ <ExpandedPendingKeysTable
+ data={filteredData}
+ paginationConfig={paginationConfig} />
+ )
+ }
+
return (
<>
<div className='table-header-section'>
@@ -182,7 +183,7 @@ const DeletePendingKeysTable:
React.FC<DeletePendingKeysTableProps> = ({
}}
dataSource={filterData(data)}
columns={COLUMNS}
- loading={loading}
+ loading={deletePendingKeysData.loading}
pagination={paginationConfig}
rowKey='keyName'
locale={{ filterTitle: '' }}
@@ -191,4 +192,4 @@ const DeletePendingKeysTable:
React.FC<DeletePendingKeysTableProps> = ({
)
}
-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 9f665857b88..4139bf97a40 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,8 +16,7 @@
* limitations under the License.
*/
-import React from 'react';
-import { AxiosError } from 'axios';
+import React, { useState, useEffect } from 'react';
import Table, {
ColumnsType,
TablePaginationConfig
@@ -26,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';
@@ -46,6 +45,10 @@ type DeletedContainerKeysTableProps = {
expandedRowRender: (arg0: any) => JSX.Element;
}
+const DEFAULT_DELETED_CONTAINER_KEYS_RESPONSE: DeletedContainerKeysResponse = {
+ containers: []
+};
+
//------Constants------
const COLUMNS: ColumnsType<Container> = [
{
@@ -84,47 +87,40 @@ const DeletedContainerKeysTable:
React.FC<DeletedContainerKeysTableProps> = ({
onRowExpand,
expandedRowRender
}) => {
+ const [data, setData] = useState<Container[]>([]);
+ const [searchTerm, setSearchTerm] = useState<string>('');
- const [loading, setLoading] = React.useState<boolean>(false);
- const [data, setData] = React.useState<Container[]>();
- const [searchTerm, setSearchTerm] = React.useState<string>('');
-
- const cancelSignal = React.useRef<AbortController>();
const debouncedSearch = useDebounce(searchTerm, 300);
+ // Use the modern hooks pattern
+ const deletedContainerKeysData = useApiData<DeletedContainerKeysResponse>(
+ `/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 as AxiosError).toString());
- });
- }
-
- React.useEffect(() => {
- fetchDeletedKeys();
-
- return (() => {
- cancelSignal.current && cancelSignal.current.abort();
- })
- }, [limit.value]);
-
-
return (
<>
<div className='table-header-section'>
@@ -151,7 +147,7 @@ const DeletedContainerKeysTable:
React.FC<DeletedContainerKeysTableProps> = ({
}}
dataSource={filterData(data)}
columns={COLUMNS}
- loading={loading}
+ loading={deletedContainerKeysData.loading}
pagination={paginationConfig}
rowKey='containerId'
locale={{ filterTitle: '' }}
@@ -160,4 +156,4 @@ const DeletedContainerKeysTable:
React.FC<DeletedContainerKeysTableProps> = ({
)
}
-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/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 38c57f4cef2..9ee92cd5e4e 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,
@@ -33,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';
@@ -55,57 +54,53 @@ const OpenKeysTable: React.FC<OpenKeysTableProps> = ({
paginationConfig,
handleLimitChange
}) => {
- const [loading, setLoading] = React.useState<boolean>(false);
- const [data, setData] = React.useState<OpenKeys[]>();
+ const [isFso, setIsFso] = React.useState<boolean>(true);
const [searchTerm, setSearchTerm] = React.useState<string>('');
-
- const cancelSignal = React.useRef<AbortController>();
const debouncedSearch = useDebounce(searchTerm, 300);
+ const {
+ data: openKeysResponse,
+ loading,
+ } = useApiData<OpenKeysResponse>(
+
`/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 as AxiosError).toString());
- });
- }
-
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<OpenKeys> = [{
@@ -173,13 +168,6 @@ const OpenKeysTable: React.FC<OpenKeysTableProps> = ({
render: (type: string) => <div key={type}>{type}</div>
}];
- React.useEffect(() => {
- // Fetch FSO open keys by default
- fetchOpenKeys(true);
-
- return (() => cancelSignal.current && cancelSignal.current.abort());
- }, [limit.value]);
-
return (
<>
<div className='table-header-section'>
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 dfcdec0cefa..cc97a599a0f 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,105 +17,120 @@
*/
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<T> {
data: T;
loading: boolean;
error: string | null;
lastUpdated: number | null;
+ success: boolean;
}
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<T>(
url: string,
defaultValue: T,
options: UseApiDataOptions = {}
): ApiState<T> & {
- refetch: () => void;
+ execute: (data?: any) => Promise<any>;
+ refetch: () => Promise<any>;
clearError: () => void;
+ reset: () => void;
} {
const {
+ method = 'GET',
retryAttempts = 3,
retryDelay = 1000,
- initialFetch = true,
- onError
+ initialFetch = method === 'GET',
+ onError,
+ onSuccess
} = options;
const [state, setState] = useState<ApiState<T>>({
data: defaultValue,
loading: initialFetch,
error: null,
- lastUpdated: null
+ lastUpdated: null,
+ success: false
});
const controllerRef = useRef<AbortController>();
const retryCountRef = useRef(0);
const retryTimeoutRef = useRef<NodeJS.Timeout>();
-
- // Store stable references
- const urlRef = useRef(url);
- const retryAttemptsRef = useRef(retryAttempts);
- const retryDelayRef = useRef(retryDelay);
- const onErrorRef = useRef(onError);
-
- // Update refs when props change
- useEffect(() => {
- urlRef.current = url;
- }, [url]);
-
- useEffect(() => {
- retryAttemptsRef.current = retryAttempts;
- }, [retryAttempts]);
-
- useEffect(() => {
- retryDelayRef.current = retryDelay;
- }, [retryDelay]);
-
- useEffect(() => {
- onErrorRef.current = onError;
- }, [onError]);
+ const mountedRef = useRef(false);
+ const executeRequest = async (requestData?: any, isRetry = false) => {
+ // Don't make requests if URL is empty or falsy
+ if (!url || url.trim() === '') {
+ return Promise.reject(new Error('URL is required'));
+ }
- const fetchData = async (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,
+ method,
+ signal: controllerRef.current.signal,
+ };
+
+ // Add data for non-GET requests
+ if (method !== 'GET' && requestData !== undefined) {
+ config.data = requestData;
+ }
+
+ // Add query parameters for GET requests if data is provided as params
+ if (method === '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 (onSuccess) {
+ onSuccess(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'}`;
+ `${method} request failed with status:
${error.response?.status || 'unknown'}`;
// Clear any existing retry timeout
if (retryTimeoutRef.current) {
@@ -123,65 +138,122 @@ export function useApiData<T>(
}
// Retry logic for network errors and 5xx errors
- if (retryCountRef.current < retryAttemptsRef.current &&
+ if (retryCountRef.current < retryAttempts &&
(!error.response?.status || error.response?.status >= 500)) {
retryCountRef.current++;
retryTimeoutRef.current = setTimeout(() => {
- fetchData(true);
- }, retryDelayRef.current * retryCountRef.current);
- return;
+ executeRequest(requestData, true);
+ }, retryDelay * retryCountRef.current);
+ return Promise.reject(error);
}
- if (onErrorRef.current) {
- onErrorRef.current(errorMessage);
+ if (onError) {
+ onError(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
+ });
+ };
+
+ // Handle initial fetch, URL changes, and cleanup
useEffect(() => {
- if (initialFetch) {
- fetchData();
+ // Don't make requests if URL is empty or falsy
+ if (!url || url.trim() === '') {
+ return;
}
- // Cleanup retry timeout on unmount
- return () => {
- if (retryTimeoutRef.current) {
- clearTimeout(retryTimeoutRef.current);
+ if (!mountedRef.current) {
+ // Initial mount - this is required since we might have a situation where
+ // the component is mounted but initial fetch is not enabled, hence we
need to separate out
+ // by checking if the component is mounted or just the URL has changed.
+ mountedRef.current = true;
+ if (initialFetch && method === 'GET') {
+ executeRequest();
}
- };
- }, []); // Empty dependency array
+ } else {
+ // URL changed - refetch for GET requests
+ if (method === 'GET') {
+ executeRequest();
+ }
+ }
- // Cleanup on unmount
- useEffect(() => {
+ // Cleanup on unmount
return () => {
if (controllerRef.current) {
- controllerRef.current.abort('Component unmounted');
+ controllerRef.current.abort();
}
if (retryTimeoutRef.current) {
clearTimeout(retryTimeoutRef.current);
}
};
- }, []);
+ }, [url]); // eslint-disable-line react-hooks/exhaustive-deps
return {
...state,
+ execute,
refetch,
- clearError
+ clearError,
+ reset
+ };
+}
+
+// Utility function for manual single requests (for dynamic/on-demand usage)
+export async function fetchData<T>(
+ url: string,
+ method: HttpMethod = 'GET',
+ data?: any
+): Promise<T> {
+ // Don't make requests if URL is empty or falsy
+ if (!url || url.trim() === '') {
+ return Promise.reject(new Error('URL is required'));
+ }
+
+ 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/buckets/buckets.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/buckets/buckets.tsx
index 7d2c77de3c3..3d7fda9cb3f 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,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';
import { useLocation } from 'react-router-dom';
@@ -28,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,
@@ -55,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<string, Set<Bucket>>,
@@ -91,9 +96,6 @@ function getFilteredBuckets(
}
const Buckets: React.FC<{}> = () => {
-
- const cancelSignal = useRef<AbortController>();
-
const [state, setState] = useState<BucketsState>({
totalCount: 0,
lastUpdated: 0,
@@ -102,7 +104,6 @@ const Buckets: React.FC<{}> = () => {
bucketsUnderVolume: [],
volumeOptions: [],
});
- const [loading, setLoading] = useState<boolean>(false);
const [selectedColumns, setSelectedColumns] =
useState<Option[]>(defaultColumns);
const [selectedVolumes, setSelectedVolumes] = useState<Option[]>([]);
const [selectedLimit, setSelectedLimit] = useState<Option>(LIMIT_OPTIONS[0]);
@@ -114,9 +115,20 @@ const Buckets: React.FC<{}> = () => {
const debouncedSearch = useDebounce(searchTerm, 300);
const { search } = useLocation();
+ // Use the modern hooks pattern
+ const bucketsData = useApiData<BucketResponse>(
+ `/api/v1/buckets?limit=${selectedLimit.value}`,
+ DEFAULT_BUCKET_RESPONSE,
+ {
+ retryAttempts: 2,
+ initialFetch: false,
+ onError: (error) => showDataFetchError(error)
+ }
+ );
+
function getVolumeSearchParam() {
return new URLSearchParams(search).get('volume');
- };
+ }
function handleVolumeChange(selected: ValueType<Option, true>) {
const { volumeBucketMap } = state;
@@ -132,7 +144,7 @@ const Buckets: React.FC<{}> = () => {
...state,
bucketsUnderVolume: selectedBuckets
});
- };
+ }
function handleAclLinkClick(bucket: Bucket) {
setCurrentRow(bucket);
@@ -147,41 +159,28 @@ const Buckets: React.FC<{}> = () => {
setSelectedLimit(selected as Option);
}
- const loadData = () => {
- setLoading(true);
- const { request, controller } = AxiosGetHelper(
- '/api/v1/buckets',
- cancelSignal.current,
- '',
- { limit: selectedLimit.value }
- );
- cancelSignal.current = controller;
- request.then(response => {
- const bucketsResponse: BucketResponse = response.data;
- const totalCount = bucketsResponse.totalCount;
- const buckets: Bucket[] = bucketsResponse.buckets;
-
- const dataSource: Bucket[] = buckets?.map(bucket => {
- return {
- volumeName: bucket.volumeName,
- name: bucket.name,
- versioning: bucket.versioning,
- storageType: bucket.storageType,
- bucketLayout: bucket.bucketLayout,
- creationTime: bucket.creationTime,
- modificationTime: bucket.modificationTime,
- sourceVolume: bucket.sourceVolume,
- sourceBucket: bucket.sourceBucket,
- usedBytes: bucket.usedBytes,
- usedNamespace: bucket.usedNamespace,
- quotaInBytes: bucket.quotaInBytes,
- quotaInNamespace: bucket.quotaInNamespace,
- owner: bucket.owner,
- acls: bucket.acls
- };
- }) ?? [];
-
- const volumeBucketMap: Map<string, Set<Bucket>> =
getVolumeBucketMap(dataSource);
+ // Process buckets data when it changes
+ useEffect(() => {
+ if (bucketsData.data && bucketsData.data.buckets) {
+ const buckets: Bucket[] = bucketsData.data.buckets.map(bucket => ({
+ volumeName: bucket.volumeName,
+ name: bucket.name,
+ versioning: bucket.versioning,
+ storageType: bucket.storageType,
+ bucketLayout: bucket.bucketLayout,
+ creationTime: bucket.creationTime,
+ modificationTime: bucket.modificationTime,
+ sourceVolume: bucket.sourceVolume,
+ sourceBucket: bucket.sourceBucket,
+ usedBytes: bucket.usedBytes,
+ usedNamespace: bucket.usedNamespace,
+ quotaInBytes: bucket.quotaInBytes,
+ quotaInNamespace: bucket.quotaInNamespace,
+ owner: bucket.owner,
+ acls: bucket.acls
+ }));
+
+ const volumeBucketMap: Map<string, Set<Bucket>> =
getVolumeBucketMap(buckets);
// Set options for volume selection dropdown
const volumeOptions: Option[] = Array.from(
@@ -191,30 +190,34 @@ const Buckets: React.FC<{}> = () => {
value: k
}));
- setLoading(false);
+ setState(prevState => ({
+ ...prevState,
+ totalCount: bucketsData.data.totalCount,
+ volumeBucketMap: volumeBucketMap,
+ volumeOptions: volumeOptions,
+ lastUpdated: Number(moment())
+ }));
+ // Set default volumes if none selected
setSelectedVolumes((prevState) => {
if (prevState.length === 0) return volumeOptions;
return prevState;
});
+ }
+ }, [bucketsData.data]);
- setState({
- ...state,
- totalCount: totalCount,
- volumeBucketMap: volumeBucketMap,
- volumeOptions: volumeOptions,
- lastUpdated: Number(moment())
- });
- }).catch(error => {
- setLoading(false);
- showDataFetchError(error.toString());
- });
- }
-
- const autoReloadHelper: AutoReloadHelper = new AutoReloadHelper(loadData);
+ // Update buckets under volume when volume selection or data changes
+ useEffect(() => {
+ setState(prevState => ({
+ ...prevState,
+ bucketsUnderVolume: getFilteredBuckets(
+ selectedVolumes,
+ prevState.volumeBucketMap
+ )
+ }));
+ }, [selectedVolumes, state.volumeBucketMap]);
useEffect(() => {
- autoReloadHelper.startPolling();
const initialVolume = getVolumeSearchParam();
if (initialVolume) {
setSelectedVolumes([{
@@ -222,30 +225,14 @@ const Buckets: React.FC<{}> = () => {
value: initialVolume
}]);
}
- loadData();
-
- return (() => {
- autoReloadHelper.stopPolling();
- cancelRequests([cancelSignal.current!]);
- })
}, []);
- useEffect(() => {
- // If the data is fetched, we need to regenerate the columns
- // To make sure the filters are properly applied
- setState({
- ...state,
- bucketsUnderVolume: getFilteredBuckets(
- selectedVolumes,
- state.volumeBucketMap
- )
- });
- }, [state.volumeBucketMap])
+ // Create refresh function for auto-reload
+ const loadBucketsData = () =>{
+ bucketsData.refetch();
+ };
- // If limit changes, load new data
- useEffect(() => {
- loadData();
- }, [selectedLimit.value]);
+ const autoReload = useAutoReload(loadBucketsData);
const {
lastUpdated, columnOptions,
@@ -257,10 +244,10 @@ const Buckets: React.FC<{}> = () => {
<div className='page-header-v2'>
Buckets
<AutoReloadPanel
- isLoading={loading}
+ isLoading={bucketsData.loading}
lastRefreshed={lastUpdated}
- togglePolling={autoReloadHelper.handleAutoReloadToggle}
- onReload={loadData}
+ togglePolling={autoReload.handleAutoReloadToggle}
+ onReload={loadBucketsData}
/>
</div>
<div className='data-container'>
@@ -305,7 +292,7 @@ const Buckets: React.FC<{}> = () => {
}} />
</div>
<BucketsTable
- loading={loading}
+ loading={bucketsData.loading}
data={bucketsUnderVolume}
handleAclClick={handleAclLinkClick}
selectedColumns={selectedColumns}
@@ -323,4 +310,4 @@ const Buckets: React.FC<{}> = () => {
)
}
-export default Buckets;
\ No newline at end of file
+export default Buckets;
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 3a784bb9932..6bd9f6fc725 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
@@ -16,9 +16,8 @@
* limitations under the License.
*/
-import React, { useRef, useState } from "react";
+import React, { useState, useCallback } from "react";
import moment from "moment";
-import { AxiosError } from "axios";
import { Card, Row, Tabs } from "antd";
import { ValueType } from "react-select/src/types";
@@ -27,9 +26,9 @@ import MultiSelect, { Option } from
"@/v2/components/select/multiSelect";
import ContainerTable, { COLUMNS } from
"@/v2/components/tables/containersTable";
import AutoReloadPanel from "@/components/autoReloadPanel/autoReloadPanel";
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 {
Container,
@@ -39,7 +38,6 @@ import {
import './containers.less';
-
const SearchableColumnOpts = [{
label: 'Container ID',
value: 'containerID'
@@ -53,10 +51,11 @@ const defaultColumns = COLUMNS.map(column => ({
value: column.key as string
}));
-const Containers: React.FC<{}> = () => {
-
- const cancelSignal = useRef<AbortController>();
+const DEFAULT_CONTAINERS_RESPONSE = {
+ containers: []
+};
+const Containers: React.FC<{}> = () => {
const [state, setState] = useState<ContainerState>({
lastUpdated: 0,
columnOptions: defaultColumns,
@@ -67,8 +66,6 @@ const Containers: React.FC<{}> = () => {
mismatchedReplicaContainerData: []
});
const [expandedRow, setExpandedRow] = useState<ExpandedRow>({});
-
- const [loading, setLoading] = useState<boolean>(false);
const [selectedColumns, setSelectedColumns] =
useState<Option[]>(defaultColumns);
const [searchTerm, setSearchTerm] = useState<string>('');
const [selectedTab, setSelectedTab] = useState<string>('1');
@@ -76,18 +73,21 @@ const Containers: React.FC<{}> = () => {
const debouncedSearch = useDebounce(searchTerm, 300);
- function loadData() {
- setLoading(true);
-
- const { request, controller } = AxiosGetHelper(
- '/api/v1/containers/unhealthy',
- cancelSignal.current
- );
-
- cancelSignal.current = controller;
+ // Use the modern hooks pattern
+ const containersData = useApiData<{ containers: Container[] }>(
+ '/api/v1/containers/unhealthy',
+ DEFAULT_CONTAINERS_RESPONSE,
+ {
+ retryAttempts: 2,
+ initialFetch: false,
+ onError: (error) => showDataFetchError(error)
+ }
+ );
- request.then(response => {
- const containers: Container[] = response.data.containers;
+ // Process containers data when it changes
+ React.useEffect(() => {
+ if (containersData.data && containersData.data.containers) {
+ const containers: Container[] = containersData.data.containers;
const missingContainerData: Container[] = containers?.filter(
container => container.containerState === 'MISSING'
@@ -102,45 +102,50 @@ const Containers: React.FC<{}> = () => {
container => container.containerState === 'MIS_REPLICATED'
) ?? [];
const mismatchedReplicaContainerData: Container[] = containers?.filter(
- container => container.containerState === 'REPLICA_MISMATCH'
+ container => container.containerState === 'MISMATCHED_REPLICA'
) ?? [];
setState({
...state,
- missingContainerData: missingContainerData,
- underReplicatedContainerData: underReplicatedContainerData,
- overReplicatedContainerData: overReplicatedContainerData,
- misReplicatedContainerData: misReplicatedContainerData,
- mismatchedReplicaContainerData: mismatchedReplicaContainerData,
+ missingContainerData,
+ underReplicatedContainerData,
+ overReplicatedContainerData,
+ misReplicatedContainerData,
+ mismatchedReplicaContainerData,
lastUpdated: Number(moment())
});
- setLoading(false)
- }).catch(error => {
- setLoading(false);
- showDataFetchError((error as AxiosError).toString());
- });
- }
+ }
+ }, [containersData.data]);
function handleColumnChange(selected: ValueType<Option, true>) {
setSelectedColumns(selected as Option[]);
}
- const autoReloadHelper: AutoReloadHelper = new AutoReloadHelper(loadData);
+ function handleTagClose(label: string) {
+ setSelectedColumns(
+ selectedColumns.filter((column) => column.label !== label)
+ );
+ }
+
+ function handleTabChange(key: string) {
+ setSelectedTab(key);
+ }
- React.useEffect(() => {
- autoReloadHelper.startPolling();
- loadData();
+ // Create refresh function for auto-reload
+ const loadContainersData = () => {
+ containersData.refetch();
+ };
- return (() => {
- autoReloadHelper.stopPolling();
- cancelRequests([cancelSignal.current!])
- })
- }, []);
+ const autoReload = useAutoReload(loadContainersData);
const {
- lastUpdated, columnOptions,
- missingContainerData, underReplicatedContainerData,
- overReplicatedContainerData, misReplicatedContainerData,
mismatchedReplicaContainerData
+ lastUpdated,
+ columnOptions,
+ missingContainerData,
+ underReplicatedContainerData,
+ overReplicatedContainerData,
+ misReplicatedContainerData,
+ mismatchedReplicaContainerData
} = state;
// Mapping the data to the Tab keys for enabling/disabling search
@@ -181,22 +186,39 @@ const Containers: React.FC<{}> = () => {
</div>
)
+ const getCurrentTabData = () => {
+ switch (selectedTab) {
+ case '1':
+ return missingContainerData;
+ case '2':
+ return underReplicatedContainerData;
+ case '3':
+ return overReplicatedContainerData;
+ case '4':
+ return misReplicatedContainerData;
+ case '5':
+ return mismatchedReplicaContainerData;
+ default:
+ return missingContainerData;
+ }
+ };
+
return (
<>
<div className='page-header-v2'>
Containers
<AutoReloadPanel
- isLoading={loading}
+ isLoading={containersData.loading}
lastRefreshed={lastUpdated}
- togglePolling={autoReloadHelper.handleAutoReloadToggle}
- onReload={loadData}
+ togglePolling={autoReload.handleAutoReloadToggle}
+ onReload={loadContainersData}
/>
</div>
<div style={{ padding: '24px' }}>
<div style={{ marginBottom: '12px' }}>
<Card
title='Highlights'
- loading={loading}>
+ loading={containersData.loading}>
<Row
align='middle'>
{highlightData}
@@ -236,7 +258,7 @@ const Containers: React.FC<{}> = () => {
tab='Missing'>
<ContainerTable
data={missingContainerData}
- loading={loading}
+ loading={containersData.loading}
searchColumn={searchColumn}
searchTerm={debouncedSearch}
selectedColumns={selectedColumns}
@@ -249,7 +271,7 @@ const Containers: React.FC<{}> = () => {
tab='Under-Replicated'>
<ContainerTable
data={underReplicatedContainerData}
- loading={loading}
+ loading={containersData.loading}
searchColumn={searchColumn}
searchTerm={debouncedSearch}
selectedColumns={selectedColumns}
@@ -262,7 +284,7 @@ const Containers: React.FC<{}> = () => {
tab='Over-Replicated'>
<ContainerTable
data={overReplicatedContainerData}
- loading={loading}
+ loading={containersData.loading}
searchColumn={searchColumn}
searchTerm={debouncedSearch}
selectedColumns={selectedColumns}
@@ -275,7 +297,7 @@ const Containers: React.FC<{}> = () => {
tab='Mis-Replicated'>
<ContainerTable
data={misReplicatedContainerData}
- loading={loading}
+ loading={containersData.loading}
searchColumn={searchColumn}
searchTerm={debouncedSearch}
selectedColumns={selectedColumns}
@@ -288,7 +310,7 @@ const Containers: React.FC<{}> = () => {
tab='Mismatched Replicas'>
<ContainerTable
data={mismatchedReplicaContainerData}
- loading={loading}
+ loading={containersData.loading}
searchColumn={searchColumn}
searchTerm={debouncedSearch}
selectedColumns={selectedColumns}
@@ -303,4 +325,4 @@ const Containers: React.FC<{}> = () => {
);
}
-export default Containers;
\ No newline at end of file
+export default Containers;
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 7c044b2ae1e..101db9d4b03 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
@@ -38,12 +37,7 @@ import MultiSelect, { Option } from
'@/v2/components/select/multiSelect';
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,7 +49,12 @@ import {
} from '@/v2/types/datanode.types';
import './datanodes.less'
+import { useAutoReload } from '@/v2/hooks/useAutoReload.hook';
+// Type for decommission API response
+type DecommissionAPIResponse = {
+ DatanodesDecommissionInfo: DatanodeDecomissionInfo[];
+};
const defaultColumns = COLUMNS.map(column => ({
label: (typeof column.title === 'string')
@@ -80,15 +79,46 @@ const COLUMN_UPDATE_DECOMMISSIONING = 'DECOMMISSIONING';
const Datanodes: React.FC<{}> = () => {
- const cancelSignal = useRef<AbortController>();
- const cancelDecommissionSignal = useRef<AbortController>();
-
const [state, setState] = useState<DatanodesState>({
lastUpdated: 0,
columnOptions: defaultColumns,
dataSource: []
});
- const [loading, setLoading] = useState<boolean>(false);
+
+ // API hooks for data fetching
+ const decommissionAPI = useApiData<DecommissionAPIResponse>(
+ '/api/v1/datanodes/decommission/info',
+ { DatanodesDecommissionInfo: [] },
+ {
+ initialFetch: false,
+ onError: (error) => showDataFetchError(error)
+ }
+ );
+
+ const datanodesAPI = useApiData<DatanodesResponse>(
+ '/api/v1/datanodes',
+ { datanodes: [], totalCount: 0 },
+ {
+ initialFetch: false,
+ onError: (error) => showDataFetchError(error)
+ }
+ );
+
+ const removeDatanodesAPI = useApiData<any>(
+ '/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<Option[]>(defaultColumns);
const [selectedRows, setSelectedRows] = useState<React.Key[]>([]);
const [searchTerm, setSearchTerm] = useState<string>('');
@@ -101,62 +131,31 @@ 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
- };
-
- async function loadDataNodeAPI() {
- const { request, controller } = await AxiosGetHelper(
- '/api/v1/datanodes',
- cancelSignal.current
- );
- cancelSignal.current = controller;
- return request;
- };
-
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.toString());
- }).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 as AxiosError).toString());
+ 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,
@@ -181,30 +180,22 @@ const Datanodes: React.FC<{}> = () => {
networkLocation: datanode.networkLocation
})
);
- setLoading(false);
+
setState({
...state,
dataSource: dataSource,
lastUpdated: Number(moment())
});
- } catch (error) {
- setLoading(false);
- showDataFetchError((error as AxiosError).toString())
}
- }
+ }, [decommissionAPI.loading, datanodesAPI.loading, decommissionAPI.data,
datanodesAPI.data]);
- const autoReloadHelper: AutoReloadHelper = new AutoReloadHelper(loadData);
+ const autoReload = useAutoReload(loadData);
useEffect(() => {
- autoReloadHelper.startPolling();
- loadData();
+ autoReload.startPolling();
return (() => {
- autoReloadHelper.stopPolling();
- cancelRequests([
- cancelSignal.current!,
- cancelDecommissionSignal.current!
- ]);
+ autoReload.stopPolling();
});
}, []);
@@ -231,7 +222,7 @@ const Datanodes: React.FC<{}> = () => {
<AutoReloadPanel
isLoading={loading}
lastRefreshed={lastUpdated}
- togglePolling={autoReloadHelper.handleAutoReloadToggle}
+ togglePolling={autoReload.handleAutoReloadToggle}
onReload={loadData} />
</div>
<div className='data-container'>
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 c243cee918d..8895a1dc3e2 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
@@ -15,15 +15,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React, { ChangeEvent, useRef, useState } from 'react';
+import React, { ChangeEvent, useState, useEffect, useCallback } from 'react';
import moment, { Moment } from 'moment';
import { Button, Menu, Input, Dropdown, DatePicker, Form, Result, Spin } from
'antd';
import { MenuProps } from 'antd/es/menu';
import { DownOutlined } from '@ant-design/icons';
-
import { showDataFetchError } from '@/utils/common';
-import { AxiosGetHelper } from '@/utils/axiosRequestHelper';
+import { useApiData } from '@/v2/hooks/useAPIData.hook';
import * as CONSTANTS from '@/v2/constants/heatmap.constants';
import { HeatmapChild, HeatmapResponse, HeatmapState, InputPathState,
InputPathValidTypes, IResponseError } from '@/v2/types/heatmap.types';
import HeatmapPlot from '@/v2/components/plots/heatmapPlot';
@@ -34,17 +33,22 @@ import { useLocation } from 'react-router-dom';
let minSize = Infinity;
let maxSize = 0;
-const Heatmap: React.FC<{}> = () => {
+const DEFAULT_HEATMAP_RESPONSE: HeatmapResponse = {
+ label: '',
+ path: '',
+ children: [],
+ size: 0,
+ maxAccessCount: 0,
+ minAccessCount: 0
+};
+const DEFAULT_DISABLED_FEATURES_RESPONSE = {
+ data: []
+};
+
+const Heatmap: React.FC<{}> = () => {
const [state, setState] = useState<HeatmapState>({
- heatmapResponse: {
- label: '',
- path: '',
- children: [],
- size: 0,
- maxAccessCount: 0,
- minAccessCount: 0
- },
+ heatmapResponse: DEFAULT_HEATMAP_RESPONSE,
entityType: CONSTANTS.ENTITY_TYPES[0],
date: CONSTANTS.TIME_PERIODS[0]
});
@@ -55,15 +59,64 @@ const Heatmap: React.FC<{}> = () => {
helpMessage: ''
});
- const [isLoading, setLoading] = useState<boolean>(false);
+ const [searchPath, setSearchPath] = useState<string>(CONSTANTS.ROOT_PATH);
const [treeEndpointFailed, setTreeEndpointFailed] = useState<boolean>(false);
const location = useLocation();
- const cancelSignal = useRef<AbortController>();
- const cancelDisabledFeatureSignal = useRef<AbortController>();
+ const [isHeatmapEnabled, setIsHeatmapEnabled] =
useState<boolean>((location?.state as any)?.isHeatmapEnabled);
+
+ // Use the modern hooks pattern for heatmap data - only trigger on
searchPath change
+ const heatmapData = useApiData<HeatmapResponse>(
+ isHeatmapEnabled && state.date && searchPath && state.entityType
+ ?
`/api/v1/heatmap/readaccess?startDate=${state.date}&path=${searchPath}&entityType=${state.entityType}`
+ : '',
+ DEFAULT_HEATMAP_RESPONSE,
+ {
+ retryAttempts: 2,
+ onError: (error: any) => {
+ if (error.response?.status !== 404) {
+ showDataFetchError(error.message.toString());
+ }
+ setTreeEndpointFailed(true);
+ setInputPathState(prevState => ({
+ ...prevState,
+ inputPath: CONSTANTS.ROOT_PATH
+ }));
+ setSearchPath(CONSTANTS.ROOT_PATH);
+ }
+ }
+ );
+
+ // Use the modern hooks pattern for disabled features
+ const disabledFeaturesData = useApiData<{ data: string[] }>(
+ '/api/v1/features/disabledFeatures',
+ DEFAULT_DISABLED_FEATURES_RESPONSE,
+ {
+ retryAttempts: 2,
+ onError: (error: any) => showDataFetchError(error)
+ }
+ );
- const [isHeatmapEnabled, setIsHeatmapEnabled] =
useState<boolean>(location?.state?.isHeatmapEnabled);
+ // Process heatmap data when it changes
+ useEffect(() => {
+ if (heatmapData.data && heatmapData.data.label !== '') {
+ minSize = heatmapData.data.minAccessCount;
+ maxSize = heatmapData.data.maxAccessCount;
+ const heatmapResponse: HeatmapResponse = updateSize(heatmapData.data);
+ setState(prevState => ({
+ ...prevState,
+ heatmapResponse: heatmapResponse
+ }));
+ setTreeEndpointFailed(false);
+ }
+ }, [heatmapData.data]);
+ // Process disabled features data when it changes
+ useEffect(() => {
+ if (disabledFeaturesData.data && disabledFeaturesData.data.data) {
+ setIsHeatmapEnabled(!disabledFeaturesData.data.data.includes('HEATMAP'));
+ }
+ }, [disabledFeaturesData.data]);
function handleChange(e: ChangeEvent<HTMLInputElement>) {
const value = e.target.value;
@@ -84,7 +137,9 @@ const Heatmap: React.FC<{}> = () => {
}
function handleSubmit() {
- updateHeatmap(inputPathState.inputPath, state.entityType, state.date);
+ if (isHeatmapEnabled && state.date && inputPathState.inputPath &&
state.entityType) {
+ setSearchPath(inputPathState.inputPath);
+ }
}
const normalize = (min: number, max: number, size: number) => {
@@ -117,17 +172,17 @@ const Heatmap: React.FC<{}> = () => {
// hide block at key,volume,bucket level if size accessCount and
maxAccessCount are zero apply normalized size only for leaf level
if ((obj as HeatmapChild)?.size === 0 && (obj as
HeatmapChild)?.accessCount === 0) {
- obj['normalizedSize'] = 0;
+ (obj as any)['normalizedSize'] = 0;
} else if ((obj as HeatmapResponse)?.size === 0 && (obj as
HeatmapResponse)?.maxAccessCount === 0) {
- obj['normalizedSize'] = 0;
+ (obj as any)['normalizedSize'] = 0;
}
else if (obj?.size === 0 && ((obj as HeatmapChild)?.accessCount >= 0 ||
(obj as HeatmapResponse).maxAccessCount >= 0)) {
- obj['normalizedSize'] = 1;
+ (obj as any)['normalizedSize'] = 1;
obj.size = 0;
}
else {
const newSize = normalize(minSize, maxSize, obj.size);
- obj['normalizedSize'] = newSize;
+ (obj as any)['normalizedSize'] = newSize;
}
}
@@ -137,89 +192,18 @@ const Heatmap: React.FC<{}> = () => {
return obj as HeatmapResponse;
};
- const updateHeatmap = (path: string, entityType: string, date: string |
number) => {
- // Only perform requests if the heatmap is enabled
- if (isHeatmapEnabled) {
- setLoading(true);
- // We want to ensure these are not empty as they will be passed as path
params
- if (date && path && entityType) {
- const { request, controller } = AxiosGetHelper(
-
`/api/v1/heatmap/readaccess?startDate=${date}&path=${path}&entityType=${entityType}`,
- cancelSignal.current
- );
- cancelSignal.current = controller;
-
- request.then(response => {
- if (response?.status === 200) {
- minSize = response.data.minAccessCount;
- maxSize = response.data.maxAccessCount;
- const heatmapResponse: HeatmapResponse = updateSize(response.data);
- setLoading(false);
- setState(prevState => ({
- ...prevState,
- heatmapResponse: heatmapResponse
- }));
- } else {
- const error = new Error((response.status).toString()) as
IResponseError;
- error.status = response.status;
- error.message = `Failed to fetch Heatmap Response with status
${error.status}`
- throw error;
- }
- }).catch(error => {
- setLoading(false);
- setInputPathState(prevState => ({
- ...prevState,
- inputPath: CONSTANTS.ROOT_PATH
- }));
- setTreeEndpointFailed(true);
- if (error.response.status !== 404) {
- showDataFetchError(error.message.toString());
- }
- });
- } else {
- setLoading(false);
- }
-
- }
- }
-
const updateHeatmapParent = (path: string) => {
setInputPathState(prevState => ({
...prevState,
inputPath: path
}));
+ setSearchPath(path);
}
function isDateDisabled(current: Moment) {
return current > moment() || current < moment().subtract(90, 'day');
}
- function getIsHeatmapEnabled() {
- const disabledfeaturesEndpoint = `/api/v1/features/disabledFeatures`;
- const { request, controller } = AxiosGetHelper(
- disabledfeaturesEndpoint,
- cancelDisabledFeatureSignal.current
- )
- cancelDisabledFeatureSignal.current = controller;
- request.then(response => {
- setIsHeatmapEnabled(!response?.data?.includes('HEATMAP'));
- }).catch(error => {
- showDataFetchError((error as Error).toString());
- });
- }
-
- React.useEffect(() => {
- // We do not know if heatmap is enabled or not, so set it
- if (isHeatmapEnabled === undefined) {
- getIsHeatmapEnabled();
- }
- updateHeatmap(inputPathState.inputPath, state.entityType, state.date);
-
- return (() => {
- cancelSignal.current && cancelSignal.current.abort();
- })
- }, [isHeatmapEnabled, state.entityType, state.date]);
-
const handleDatePickerChange = (date: moment.MomentInput) => {
setState(prevState => ({
...prevState,
@@ -249,6 +233,7 @@ const Heatmap: React.FC<{}> = () => {
const { date, entityType, heatmapResponse } = state;
const { inputPath, helpMessage, isInputPathValid } = inputPathState;
+ const loading = heatmapData.loading || disabledFeaturesData.loading;
const menuCalendar = (
<Menu
@@ -363,7 +348,7 @@ const Heatmap: React.FC<{}> = () => {
</div>
</div>
</div>
- {isLoading
+ {loading
? <Spin size='large' />
: (Object.keys(heatmapResponse).length > 0 &&
(heatmapResponse.label !== null || heatmapResponse.path !== null))
? <div id="heatmap-plot-container">
@@ -384,4 +369,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/insights/insights.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/insights/insights.tsx
index f2a2c3e3f7d..09bf62f288c 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,15 +16,11 @@
* limitations under the License.
*/
-import React, { useState } from 'react';
-import axios, {
- CanceledError,
- AxiosError
-} 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';
@@ -38,7 +34,6 @@ import {
const Insights: React.FC<{}> = () => {
- const [loading, setLoading] = useState<boolean>(false);
const [state, setState] = useState<InsightsState>({
volumeBucketMap: new Map<string, Set<string>>(),
volumeOptions: [],
@@ -58,95 +53,89 @@ const Insights: React.FC<{}> = () => {
}]
});
- const cancelInsightSignal = React.useRef<AbortController>();
-
- 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<Promise<any>>,
- containerCountResponse: Awaited<Promise<any>>
- ) => {
- 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<FileCountResponse[]>(
+ '/api/v1/utilization/fileCount',
+ [],
+ {
+ onError: (error) => showDataFetchError(error)
+ }
+ );
+
+ const containerCountAPI = useApiData<any[]>(
+ '/api/v1/utilization/containerCount',
+ [],
+ {
+ onError: (error) => showDataFetchError(error)
+ }
+ );
+
+ const loading = fileCountAPI.loading || containerCountAPI.loading;
+
+ // 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<string, Set<string>>();
+ let volumeOptions: Option[] = [];
+
+ if (fileCountAPI.data && fileCountAPI.data.length > 0) {
+ // Construct volume -> bucket[] map for populating filters
+ volumeBucketMap = fileCountAPI.data.reduce(
+ (map: Map<string, Set<string>>, 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<string>([...buckets, bucket]));
} else {
- containerAPIError = err.reason.toString();
+ map.set(volume, new Set<string>().add(bucket));
}
- }
- });
+ return map;
+ },
+ new Map<string, Set<string>>()
+ );
+ 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<string, Set<string>> =
fileCountResponse.value?.data?.reduce(
- (map: Map<string, Set<string>>, 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<string>([...buckets, bucket]));
- } else {
- map.set(volume, new Set<string>().add(bucket));
- }
- return map;
- },
- new Map<string, Set<string>>()
- );
- 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 as AxiosError).toString());
- })
- }
-
- React.useEffect(() => {
- loadData();
-
- return (() => {
- cancelInsightSignal.current && cancelInsightSignal.current.abort();
- })
- }, []);
+ }
+ }, [
+ fileCountAPI.loading,
+ containerCountAPI.loading,
+ fileCountAPI.data,
+ containerCountAPI.data,
+ fileCountAPI.error,
+ containerCountAPI.error
+ ]);
return (
<>
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 732af0aa00e..7c300ff9dd6 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 f7fa6c13bba..ab652225d4c 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,8 +16,7 @@
* limitations under the License.
*/
-import React, { useRef, useState } from 'react';
-import { AxiosError } from 'axios';
+import React, { useState } from 'react';
import { Alert, Button, Tooltip } from 'antd';
import { InfoCircleFilled, ReloadOutlined, } from '@ant-design/icons';
import { ValueType } from 'react-select';
@@ -27,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';
@@ -41,37 +40,38 @@ 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<boolean>(false);
const [limit, setLimit] = useState<Option>(LIMIT_OPTIONS[1]);
- const [duResponse, setDUResponse] = useState<NUResponse>({
- status: '',
- path: '/',
- subPathCount: 0,
- size: 0,
- sizeWithReplica: 0,
- subPaths: [],
- sizeDirectKey: 0
- });
-
- const cancelPieSignal = useRef<AbortController>();
-
- function loadData(path: string) {
- console.log("Loading data at: ", path);
- setLoading(true);
- const { request, controller } = AxiosGetHelper(
- `/api/v1/namespace/usage?path=${path}&files=true&sortSubPaths=true`,
- cancelPieSignal.current
- );
- cancelPieSignal.current = controller;
+ const [currentPath, setCurrentPath] = useState<string>('/');
+
+ // Use the modern hooks pattern
+ const namespaceUsageData = useApiData<NUResponse>(
+ `/api/v1/namespace/usage?path=${currentPath}&files=true&sortSubPaths=true`,
+ DEFAULT_NU_RESPONSE,
+ {
+ retryAttempts: 2,
+ onError: (error) => showDataFetchError(error)
+ }
+ );
- request.then(response => {
- const duResponse: NUResponse = response.data;
- console.log(duResponse);
+ // Process data when it changes
+ React.useEffect(() => {
+ if (namespaceUsageData.data) {
+ const duResponse = namespaceUsageData.data;
const status = duResponse.status;
+
if (status === 'PATH_NOT_FOUND') {
- setLoading(false);
- showDataFetchError(`Invalid Path: ${path}`);
+ showDataFetchError(`Invalid Path: ${currentPath}`);
return;
}
@@ -79,27 +79,19 @@ const NamespaceUsage: React.FC<{}> = () => {
showInfoNotification("Information being initialized", "Namespace
Summary is being initialized, please wait.")
return;
}
+ }
+ }, [namespaceUsageData.data, currentPath]);
- setDUResponse(duResponse);
- setLoading(false);
- }).catch(error => {
- setLoading(false);
- showDataFetchError((error as AxiosError).toString());
- });
- }
+ const loadData = (path: string) => {
+ setCurrentPath(path);
+ };
function handleLimitChange(selected: ValueType<Option, false>) {
setLimit(selected as Option);
}
- React.useEffect(() => {
- //On mount load default data
- loadData(duResponse.path)
-
- return (() => {
- cancelRequests([cancelPieSignal.current!]);
- })
- }, []);
+ const duResponse = namespaceUsageData.data || DEFAULT_NU_RESPONSE;
+ const loading = namespaceUsageData.loading;
return (
<>
@@ -149,7 +141,7 @@ const NamespaceUsage: React.FC<{}> = () => {
subPaths={duResponse.subPaths}
sizeWithReplica={duResponse.sizeWithReplica}
size={duResponse.size} />
- <NUMetadata path={duResponse.path} />
+ <NUMetadata path={currentPath} />
</div>
</div>
</div>
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 19a3c9447a6..a99d300d4d8 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,11 +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';
@@ -29,9 +25,9 @@ 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,
@@ -41,7 +37,6 @@ import {
import './pipelines.less';
-
const defaultColumns = COLUMNS.map(column => ({
label: (typeof column.title === 'string')
? column.title
@@ -49,76 +44,74 @@ const defaultColumns = COLUMNS.map(column => ({
value: column.key as string,
}));
-const Pipelines: React.FC<{}> = () => {
- const cancelSignal = useRef<AbortController>();
+const DEFAULT_PIPELINES_RESPONSE: PipelinesResponse = {
+ totalCount: 0,
+ pipelines: []
+};
+const Pipelines: React.FC<{}> = () => {
const [state, setState] = useState<PipelinesState>({
activeDataSource: [],
columnOptions: defaultColumns,
lastUpdated: 0,
});
- const [loading, setLoading] = useState<boolean>(false);
const [selectedColumns, setSelectedColumns] =
useState<Option[]>(defaultColumns);
const [searchTerm, setSearchTerm] = useState<string>('');
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<PipelinesResponse>(
+ '/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.toString());
- })
- }
-
- const autoReloadHelper: AutoReloadHelper = new AutoReloadHelper(loadData);
-
- useEffect(() => {
- autoReloadHelper.startPolling();
- loadData();
- return (() => {
- autoReloadHelper.stopPolling();
- cancelRequests([cancelSignal.current!]);
- })
- }, []);
+ });
+ }
+ }, [pipelinesData.data]);
function handleColumnChange(selected: ValueType<Option, true>) {
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 (
<>
<div className='page-header-v2'>
Pipelines
<AutoReloadPanel
- isLoading={loading}
+ isLoading={pipelinesData.loading}
lastRefreshed={lastUpdated}
- togglePolling={autoReloadHelper.handleAutoReloadToggle}
- onReload={loadData} />
+ togglePolling={autoReload.handleAutoReloadToggle}
+ onReload={loadPipelinesData}
+ />
</div>
<div className='data-container'>
<div className='content-div'>
@@ -130,7 +123,7 @@ const Pipelines: React.FC<{}> = () => {
selected={selectedColumns}
placeholder='Columns'
onChange={handleColumnChange}
- onTagClose={() => { }}
+ onTagClose={handleTagClose}
fixedColumn='pipelineId'
columnLength={COLUMNS.length} />
</div>
@@ -141,14 +134,14 @@ const Pipelines: React.FC<{}> = () => {
value: 'pipelineId'
}]}
searchInput={searchTerm}
- searchColumn={'pipelineId'}
+ searchColumn='pipelineId'
onSearchChange={
(e: React.ChangeEvent<HTMLInputElement>) =>
setSearchTerm(e.target.value)
}
- onChange={() => { }} />
+ onChange={() => setSearchTerm('')} />
</div>
<PipelinesTable
- loading={loading}
+ loading={pipelinesData.loading}
data={activeDataSource}
selectedColumns={selectedColumns}
searchTerm={debouncedSearch} />
@@ -157,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 a10b71282ce..9188eae8e17 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<AbortController>();
+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<boolean>(false);
const [currentRow, setCurrentRow] = useState<Volume | Record<string,
never>>({});
const [selectedColumns, setSelectedColumns] =
useState<Option[]>(defaultColumns);
const [selectedLimit, setSelectedLimit] = useState<Option>(LIMIT_OPTIONS[0]);
@@ -80,65 +81,39 @@ const Volumes: React.FC<{}> = () => {
const debouncedSearch = useDebounce(searchTerm, 300);
- const loadData = () => {
- setLoading(true);
- // Cancel any previous pending requests
- cancelRequests([cancelSignal.current!]);
-
- const { request, controller } = AxiosGetHelper(
- '/api/v1/volumes',
- cancelSignal.current,
- "",
- { limit: selectedLimit.value }
- );
-
- cancelSignal.current = controller;
- request.then(response => {
- const volumesResponse: VolumesResponse = response.data;
- const volumes: Volume[] = volumesResponse.volumes;
- const data: Volume[] = volumes?.map(volume => {
- return {
- volume: volume.volume,
- owner: volume.owner,
- admin: volume.admin,
- creationTime: volume.creationTime,
- modificationTime: volume.modificationTime,
- quotaInBytes: volume.quotaInBytes,
- quotaInNamespace: volume.quotaInNamespace,
- usedNamespace: volume.usedNamespace,
- acls: volume.acls
- };
- }) ?? [];
+ // Use the modern hooks pattern
+ const volumesData = useApiData<VolumesResponse>(
+ `/api/v1/volumes?limit=${selectedLimit.value}`,
+ DEFAULT_VOLUMES_RESPONSE,
+ {
+ retryAttempts: 2,
+ initialFetch: false,
+ onError: (error) => showDataFetchError(error)
+ }
+ );
+
+ // Process volumes data when it changes
+ useEffect(() => {
+ if (volumesData.data && volumesData.data.volumes) {
+ const volumes: Volume[] = volumesData.data.volumes.map(volume => ({
+ volume: volume.volume,
+ owner: volume.owner,
+ admin: volume.admin,
+ creationTime: volume.creationTime,
+ modificationTime: volume.modificationTime,
+ quotaInBytes: volume.quotaInBytes,
+ quotaInNamespace: volume.quotaInNamespace,
+ usedNamespace: volume.usedNamespace,
+ acls: volume.acls
+ }));
setState({
...state,
- data,
+ data: volumes,
lastUpdated: Number(moment()),
});
- setLoading(false);
- }).catch(error => {
- setLoading(false);
- showDataFetchError(error.toString());
- });
- };
-
- let autoReloadHelper: AutoReloadHelper = new AutoReloadHelper(loadData);
-
- useEffect(() => {
- loadData();
- autoReloadHelper.startPolling();
-
- // Component will unmount
- return (() => {
- autoReloadHelper.stopPolling();
- cancelRequests([cancelSignal.current!]);
- })
- }, []);
-
- // If limit changes, load new data
- useEffect(() => {
- loadData();
- }, [selectedLimit.value]);
+ }
+ }, [volumesData.data]);
function handleColumnChange(selected: ValueType<Option, true>) {
setSelectedColumns(selected as Option[]);
@@ -154,12 +129,18 @@ const Volumes: React.FC<{}> = () => {
)
}
-
function handleAclLinkClick(volume: Volume) {
setCurrentRow(volume);
setShowPanel(true);
}
+ // Create refresh function for auto-reload
+ const loadVolumesData = () => {
+ volumesData.refetch();
+ };
+
+ const autoReload = useAutoReload(loadVolumesData);
+
const {
data, lastUpdated,
columnOptions
@@ -170,10 +151,10 @@ const Volumes: React.FC<{}> = () => {
<div className='page-header-v2'>
Volumes
<AutoReloadPanel
- isLoading={loading}
+ isLoading={volumesData.loading}
lastRefreshed={lastUpdated}
- togglePolling={autoReloadHelper.handleAutoReloadToggle}
- onReload={loadData}
+ togglePolling={autoReload.handleAutoReloadToggle}
+ onReload={loadVolumesData}
/>
</div>
<div className='data-container'>
@@ -209,7 +190,7 @@ const Volumes: React.FC<{}> = () => {
}} />
</div>
<VolumesTable
- loading={loading}
+ loading={volumesData.loading}
data={data}
handleAclClick={handleAclLinkClick}
selectedColumns={selectedColumns}
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 64b215b8245..da49975e0e9 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<Record<string,
object>, 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 4a3c11c11b0..7b0e3418a0a 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<Record<string, object>, 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<Record<string, object>, IDatanode
loading: false
});
decommissionUuids = [];
- showDataFetchError(error.toString());
+ showDataFetchError(error);
}
};
@@ -480,7 +480,7 @@ export class Datanodes extends
React.Component<Record<string, object>, 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 28527c0816e..c4457f50723 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<IDecommissionSummaryProps> {
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 d154359c94f..1f7bba9aa75 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<Record<string, object>, IDUState>
this.setState({
isLoading: false
});
- showDataFetchError(error.toString());
+ showDataFetchError(error);
});
};
@@ -328,7 +328,7 @@ export class DiskUsage extends
React.Component<Record<string, object>, IDUState>
isLoading: false,
showPanel: false
});
- showDataFetchError(error.toString());
+ showDataFetchError(error);
});
return;
}
@@ -480,7 +480,7 @@ export class DiskUsage extends
React.Component<Record<string, object>, 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<Record<string, object>, 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 63f095ff7ca..59a5b48c992 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<Record<string, object>, 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 fd8d7e2da97..58b460be4c2 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<Record<string,
object>, IOmdbInsightsSta
this.setState({
loading: false,
});
- showDataFetchError(error.toString());
+ showDataFetchError(error);
});
};
@@ -594,7 +594,7 @@ export class Om extends React.Component<Record<string,
object>, IOmdbInsightsSta
this.setState({
loading: false
});
- showDataFetchError(error.toString());
+ showDataFetchError(error);
});
};
@@ -653,7 +653,7 @@ export class Om extends React.Component<Record<string,
object>, IOmdbInsightsSta
this.setState({
loading: false,
});
- showDataFetchError(error.toString());
+ showDataFetchError(error);
});
};
@@ -730,7 +730,7 @@ export class Om extends React.Component<Record<string,
object>, IOmdbInsightsSta
this.setState({
loading: false
});
- showDataFetchError(error.toString());
+ showDataFetchError(error);
});
};
@@ -764,7 +764,7 @@ export class Om extends React.Component<Record<string,
object>, IOmdbInsightsSta
this.setState({
loading: false,
});
- showDataFetchError(error.toString());
+ showDataFetchError(error);
});
};
@@ -835,13 +835,7 @@ export class Om extends React.Component<Record<string,
object>, 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 9156d68326e..1b0c784fbe6 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<Record<string, object>, I
this.setState({
loading: false
});
- showDataFetchError(error.toString());
+ showDataFetchError(error);
});
}
@@ -342,7 +342,7 @@ export class MissingContainers extends
React.Component<Record<string, object>, 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 0e8940a928e..817edacdd14 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<Record<string, object>, IOverviewS
this.setState({
loading: false
});
- showDataFetchError(error.toString());
+ showDataFetchError(error);
});
};
@@ -252,7 +252,7 @@ export class Overview extends
React.Component<Record<string, object>, 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 c53be4f7a20..a5001ee0697 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<Record<string, object>, 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 ad1e94c4d6c..2316a0d8579 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<Record<string,
object>, IVolumesSta
loading: false,
showPanel: false
});
- showDataFetchError(error.toString());
+ showDataFetchError(error);
});
};
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]