Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HDDS-11150. Recon Overview page crashes due to failed API Calls #6944

Merged
merged 9 commits into from
Jul 19, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,14 @@ class OverviewCard extends React.Component<IOverviewCardProps> {

render() {
let { icon, data, title, loading, hoverable, storageReport, linkToUrl, error } = this.props;

let meta = <Meta title={data} description={title} />;
const errorClass = error ? 'card-error' : '';
let errorClass = error ? 'card-error' : '';

if (typeof data === 'string' && data === 'N/A'){
errorClass = 'card-error';
}

if (storageReport) {
meta = (
<div className='ant-card-percentage'>
Expand All @@ -156,7 +162,7 @@ class OverviewCard extends React.Component<IOverviewCardProps> {

return (
<OverviewCardWrapper linkToUrl={linkToUrl}>
<Card className={`overview-card ${ errorClass }`} loading={loading} hoverable={hoverable}>
<Card className={`overview-card ${errorClass}`} loading={loading} hoverable={hoverable}>
<Row type='flex' justify='space-between'>
<Col span={18}>
<Row>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ export const AxiosPutHelper = (
}
}

export const AxiosAllGetHelper = (
export const PromiseAllSettledGetHelper = (
urls: string[],
controller: AbortController,
message: string = ''
): { requests: Promise<AxiosResponse<any, any>[]>; controller: AbortController } => {
): { requests: Promise<PromiseSettledResult<AxiosResponse<any, any>>[]>; controller: AbortController } => {

controller && controller.abort(message);
controller = new AbortController(); // generate new AbortController for the upcoming request
Expand All @@ -64,7 +64,7 @@ export const AxiosAllGetHelper = (
});

return {
requests: axios.all(axiosGetRequests),
requests: Promise.allSettled(axiosGetRequests),
controller: controller
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,12 +556,6 @@ export class DiskUsage extends React.Component<Record<string, object>, IDUState>
</Menu>
)

console.log(plotData);
console.log(plotData.map((value) => {
return {
name: value.name
}
}))
const eChartsOptions = {
title: {
text: `Disk Usage for ${returnPath} (Total Size: ${byteToSize(duResponse.size, 1)})`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
*/

import React from 'react';
import axios from 'axios';
import axios, { CanceledError, AxiosError } from 'axios';
import filesize from 'filesize';
import { Row, Col, Tabs } from 'antd';
import { Row, Col, Tabs, message } from 'antd';
import { LoadingOutlined } from '@ant-design/icons';
import { ActionMeta, ValueType } from 'react-select';
import { format, type EChartsOption } from 'echarts';
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 { AxiosAllGetHelper } from '@/utils/axiosRequestHelper';
import { PromiseAllSettledGetHelper, PromiseAllSettledError } from '@/utils/axiosRequestHelper';

import './insights.less';

Expand Down Expand Up @@ -58,6 +58,8 @@ interface IInsightsState {
bucketOptions: IOption[];
volumeOptions: IOption[];
isBucketSelectionDisabled: boolean;
fileCountError: string | undefined;
containerSizeError: string | undefined;
}

const allVolumesOption: IOption = {
Expand Down Expand Up @@ -86,7 +88,9 @@ export class Insights extends React.Component<Record<string, object>, IInsightsS
selectedVolumes: [],
bucketOptions: [],
volumeOptions: [],
isBucketSelectionDisabled: false
isBucketSelectionDisabled: false,
fileCountError: undefined,
containerSizeError: undefined
};
}

Expand Down Expand Up @@ -195,9 +199,81 @@ export class Insights extends React.Component<Record<string, object>, IInsightsS
return (size(value));
});

console.log(xyFileCountMap);
console.log(xContainerCountValues);
console.log(xyContainerCountMap);
let renderFileCountError = (this.state.fileCountError) ? {
type: 'group',
left: 'center',
top: 'middle',
z: 100,
children: [
{
type: 'rect',
left: 'center',
top: 'middle',
z: 100,
shape: {
width: 500,
height: 40
},
style: {
fill: '#FC909B'
}
},
{
type: 'text',
left: 'center',
top: 'middle',
z: 100,
style: {
text: `No data available. ${this.state.fileCountError}`,
font: '20px sans-serif'
}
}
]
} : undefined
let renderContainerSizeError = (this.state.containerSizeError) ? {
type: 'group',
left: 'center',
top: 'middle',
z: 100,
children: [
{
type: 'rect',
left: 'center',
top: 'middle',
z: 100,
shape: {
width: 500,
height: 500
},
style: {
fill: 'rgba(256, 256, 256, 0.5)'
}
},
{
type: 'rect',
left: 'center',
top: 'middle',
z: 100,
shape: {
width: 500,
height: 40
},
style: {
fill: '#FC909B'
}
},
{
type: 'text',
left: 'center',
top: 'middle',
z: 100,
style: {
text: `No data available. ${this.state.containerSizeError}`,
font: '20px sans-serif'
}
}
]
} : undefined

this.setState({
fileCountData: {
Expand All @@ -224,7 +300,8 @@ export class Insights extends React.Component<Record<string, object>, IInsightsS
},
data: Array.from(xyFileCountMap.values()),
type: 'bar'
}
},
graphic: renderFileCountError
},
containerCountData: {
title: {
Expand All @@ -250,7 +327,8 @@ export class Insights extends React.Component<Record<string, object>, IInsightsS
name: xContainerCountValues[idx]
}
}),
}
},
graphic: renderContainerSizeError
}
});
}
Expand All @@ -261,15 +339,50 @@ export class Insights extends React.Component<Record<string, object>, IInsightsS
this.setState({
isLoading: true
});
const { requests, controller } = AxiosAllGetHelper([
const { requests, controller } = PromiseAllSettledGetHelper([
'/api/v1/utilization/fileCount',
'/api/v1/utilization/containerCount'
], cancelInsightSignal);

cancelInsightSignal = controller;
requests.then(axios.spread((fileCountresponse, containerCountresponse) => {
const fileCountsResponse: IFileCountResponse[] = fileCountresponse.data;
const containerCountResponse: IContainerCountResponse[] = containerCountresponse.data;
requests.then(axios.spread((
fileCountresponse: Awaited<Promise<any>>,
containerCountresponse: Awaited<Promise<any>>
) => {
let fileAPIError = undefined;
let containerAPIError = undefined;
let responseError = [
fileCountresponse,
containerCountresponse
].filter((resp) => resp.status === 'rejected');

if (responseError.length !== 0) {
responseError.forEach((err) => {
if (err.reason.toString().includes("CanceledError")) {
throw new CanceledError('canceled', "ERR_CANCELED");
}
else {
if (err.reason.config.url.includes("fileCount")) {
fileAPIError = err.reason.toString();
}
else {
containerAPIError = err.reason.toString();
}
}
})
}

const fileCountsResponse: IFileCountResponse[] = fileCountresponse.value?.data ?? [{
volume: '0',
bucket: '0',
fileSize: '0',
count: 0
}];
const containerCountResponse: IContainerCountResponse[] = containerCountresponse.value?.data ?? [{
containerSize: 0,
count: 0
}];

// Construct volume -> bucket[] map for populating filters
// Ex: vol1 -> [bucket1, bucket2], vol2 -> [bucket1]
const volumeBucketMap: Map<string, Set<string>> = fileCountsResponse.reduce(
Expand All @@ -280,7 +393,7 @@ export class Insights extends React.Component<Record<string, object>, IInsightsS
const buckets = Array.from(map.get(volume)!);
map.set(volume, new Set([...buckets, bucket]));
} else {
map.set(volume, new Set().add(bucket));
map.set(volume, new Set<string>().add(bucket));
}

return map;
Expand All @@ -297,15 +410,17 @@ export class Insights extends React.Component<Record<string, object>, IInsightsS
volumeBucketMap,
fileCountsResponse,
containerCountResponse,
volumeOptions
volumeOptions,
fileCountError: fileAPIError,
containerSizeError: containerAPIError
}, () => {
this.updatePlotData();
// Select all volumes by default
this.handleVolumeChange([allVolumesOption, ...volumeOptions], { action: 'select-option' });
});
})).catch(error => {
this.setState({
isLoading: false
isLoading: false,
});
showDataFetchError(error.toString());
});
Expand Down
Loading