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

frontend: Improve error handling in list pages #2771

Merged
merged 5 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
frontend: Update ClusterGroupErrorMessage to render errors array
Signed-off-by: Oleksandr Dubenko <[email protected]>
  • Loading branch information
sniok committed Jan 29, 2025
commit da35b8c7d2ea5fed885116f5def20dc3efe03009
Original file line number Diff line number Diff line change
@@ -1,40 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react';
import { ApiError } from '../../lib/k8s/api/v2/ApiError';
import { TestContext } from '../../test';
import {
ClusterGroupErrorMessage,
ClusterGroupErrorMessageProps,
} from './ClusterGroupErrorMessage';

const meta: Meta<typeof ClusterGroupErrorMessage> = {
component: ClusterGroupErrorMessage,
decorators: [
Story => (
<TestContext>
<Story />
</TestContext>
),
],
};

export default meta;
type Story = StoryObj<ClusterGroupErrorMessageProps>;

export const WithClusterErrors: Story = {
args: {
clusterErrors: {
cluster1: 'Error in cluster 1',
cluster3: 'Error in cluster 3',
},
errors: [
new ApiError('Error in cluster 1', { cluster: 'cluster1' }),
new ApiError('Error in cluster 3', { cluster: 'cluster3' }),
],
},
};

export const WithMessageUsed: Story = {
export const WithMutipleErrorsPerCluster: Story = {
args: {
message: 'This message is used and not clusterErrors.',
clusterErrors: {
cluster1: 'Error in cluster 1',
cluster3: 'Error in cluster 3',
},
},
};

export const WithDetailedClusterErrors: Story = {
args: {
clusterErrors: {
cluster1: 'Error in cluster 1',
cluster3: null,
},
errors: [
new ApiError('Error A in cluster 1', { cluster: 'cluster1' }),
new ApiError('Error B in cluster 1', { cluster: 'cluster1' }),
],
},
};
95 changes: 49 additions & 46 deletions frontend/src/components/cluster/ClusterGroupErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,66 @@
import { InlineIcon } from '@iconify/react';
import { Box, Typography, useTheme } from '@mui/material';
import { Alert, AlertTitle, Box, Button } from '@mui/material';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ApiError } from '../../lib/k8s/apiProxy';
import { useClusterGroup } from '../../lib/k8s';
import { ApiError } from '../../lib/k8s/api/v2/ApiError';

export interface ClusterGroupErrorMessageProps {
/**
* A message to display when clusters fail to load resources.
* This is used if it is passed in, otherwise a message made from clusterErrors is used.
* Array of errors
*/
message?: string;
/**
* Either an array of errors, or keyed by cluster name, valued by the error for that cluster.
*/
clusterErrors?: { [cluster: string]: string | ApiError | null };
errors?: ApiError[] | null;
}

/**
* filter out null errors
* @returns errors, but without any that have null values.
*/
function cleanNullErrors(errors: ClusterGroupErrorMessageProps['clusterErrors']) {
if (!errors) {
return {};
export function ClusterGroupErrorMessage({ errors }: ClusterGroupErrorMessageProps) {
if (!errors || errors?.length === 0) {
return null;
}
const cleanedErrors: ClusterGroupErrorMessageProps['clusterErrors'] = {};
Object.entries(errors).forEach(([cluster, error]) => {
if (error !== null) {
cleanedErrors[cluster] = error;
}
});

return cleanedErrors;

return errors.map((error, i) => <ErrorMessage error={error} key={error.stack ?? i} />);
}

export function ClusterGroupErrorMessage({
clusterErrors,
message,
}: ClusterGroupErrorMessageProps) {
function ErrorMessage({ error }: { error: ApiError }) {
const { t } = useTranslation();
const theme = useTheme();
const clusterObj = cleanNullErrors(typeof clusterErrors === 'object' ? clusterErrors : {});
const showClusterName = useClusterGroup().length > 1;
const [showMessage, setShowMessage] = useState(false);

if ((!clusterErrors && !message) || Object.keys(clusterObj).length === 0) {
return null;
const defaultTitle = t('Failed to load resources');
const forbiddenTitle = t("You don't have permissions to view this resource");
const notFoundTitile = t('Resource not found');

const isForbidden = error.status === 403;

let title = defaultTitle;
if (error.status === 404) {
title = notFoundTitile;
} else if (isForbidden) {
title = forbiddenTitle;
}

const severity = isForbidden ? 'info' : 'warning';

return (
<Box p={1} style={{ background: theme.palette.warning.light }}>
<Typography style={{ color: theme.palette.warning.main }}>
<InlineIcon icon="mdi:alert" color={theme.palette.warning.main} />
&nbsp;
<>{message}</>
{!message &&
(Object.keys(clusterObj).length > 2
? t('Failed to load resources from some of the clusters in the group.')
: t('Failed to load resources from the following clusters: {{ clusterList }}', {
clusterList: Object.keys(clusterObj).join(', '),
}))}
</Typography>
</Box>
<Alert
severity={severity}
sx={{ mb: 1 }}
action={
<Button
size="small"
color={severity}
onClick={() => setShowMessage(it => !it)}
sx={{ whiteSpace: 'nowrap' }}
>
{showMessage ? t('Hide details') : t('Show details')}
</Button>
}
>
<AlertTitle sx={{ mb: showMessage ? undefined : 0 }}>{title}</AlertTitle>
{showMessage && (
<>
{showClusterName ? <Box>Cluster: {error.cluster}</Box> : null}
{error.message}
</>
)}
</Alert>
);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,90 @@
<body>
<div>
<div
class="MuiBox-root css-hpgf8j"
style="background: rgb(255, 243, 224);"
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation0 MuiAlert-root MuiAlert-colorWarning MuiAlert-standardWarning MuiAlert-standard css-18jeh35-MuiPaper-root-MuiAlert-root"
role="alert"
>
<p
class="MuiTypography-root MuiTypography-body1 css-1ezega9-MuiTypography-root"
style="color: rgb(196, 69, 0);"
<div
class="MuiAlert-icon css-1ytlwq5-MuiAlert-icon"
>

Failed to load resources from the following clusters: cluster1, cluster3
</p>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeInherit css-1vooibu-MuiSvgIcon-root"
data-testid="ReportProblemOutlinedIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 5.99L19.53 19H4.47L12 5.99M12 2L1 21h22L12 2zm1 14h-2v2h2v-2zm0-6h-2v4h2v-4z"
/>
</svg>
</div>
<div
class="MuiAlert-message css-1pxa9xg-MuiAlert-message"
>
<div
class="MuiTypography-root MuiTypography-body1 MuiTypography-gutterBottom MuiAlertTitle-root css-m946el-MuiTypography-root-MuiAlertTitle-root"
>
Failed to load resources
</div>
</div>
<div
class="MuiAlert-action css-ki1hdl-MuiAlert-action"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textWarning MuiButton-sizeSmall MuiButton-textSizeSmall MuiButton-colorWarning MuiButton-root MuiButton-text MuiButton-textWarning MuiButton-sizeSmall MuiButton-textSizeSmall MuiButton-colorWarning css-wucs85-MuiButtonBase-root-MuiButton-root"
tabindex="0"
type="button"
>
Show details
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation0 MuiAlert-root MuiAlert-colorWarning MuiAlert-standardWarning MuiAlert-standard css-18jeh35-MuiPaper-root-MuiAlert-root"
role="alert"
>
<div
class="MuiAlert-icon css-1ytlwq5-MuiAlert-icon"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeInherit css-1vooibu-MuiSvgIcon-root"
data-testid="ReportProblemOutlinedIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 5.99L19.53 19H4.47L12 5.99M12 2L1 21h22L12 2zm1 14h-2v2h2v-2zm0-6h-2v4h2v-4z"
/>
</svg>
</div>
<div
class="MuiAlert-message css-1pxa9xg-MuiAlert-message"
>
<div
class="MuiTypography-root MuiTypography-body1 MuiTypography-gutterBottom MuiAlertTitle-root css-m946el-MuiTypography-root-MuiAlertTitle-root"
>
Failed to load resources
</div>
</div>
<div
class="MuiAlert-action css-ki1hdl-MuiAlert-action"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textWarning MuiButton-sizeSmall MuiButton-textSizeSmall MuiButton-colorWarning MuiButton-root MuiButton-text MuiButton-textWarning MuiButton-sizeSmall MuiButton-textSizeSmall MuiButton-colorWarning css-wucs85-MuiButtonBase-root-MuiButton-root"
tabindex="0"
type="button"
>
Show details
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
</body>

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<body>
<div>
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation0 MuiAlert-root MuiAlert-colorWarning MuiAlert-standardWarning MuiAlert-standard css-18jeh35-MuiPaper-root-MuiAlert-root"
role="alert"
>
<div
class="MuiAlert-icon css-1ytlwq5-MuiAlert-icon"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeInherit css-1vooibu-MuiSvgIcon-root"
data-testid="ReportProblemOutlinedIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 5.99L19.53 19H4.47L12 5.99M12 2L1 21h22L12 2zm1 14h-2v2h2v-2zm0-6h-2v4h2v-4z"
/>
</svg>
</div>
<div
class="MuiAlert-message css-1pxa9xg-MuiAlert-message"
>
<div
class="MuiTypography-root MuiTypography-body1 MuiTypography-gutterBottom MuiAlertTitle-root css-m946el-MuiTypography-root-MuiAlertTitle-root"
>
Failed to load resources
</div>
</div>
<div
class="MuiAlert-action css-ki1hdl-MuiAlert-action"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textWarning MuiButton-sizeSmall MuiButton-textSizeSmall MuiButton-colorWarning MuiButton-root MuiButton-text MuiButton-textWarning MuiButton-sizeSmall MuiButton-textSizeSmall MuiButton-colorWarning css-wucs85-MuiButtonBase-root-MuiButton-root"
tabindex="0"
type="button"
>
Show details
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation0 MuiAlert-root MuiAlert-colorWarning MuiAlert-standardWarning MuiAlert-standard css-18jeh35-MuiPaper-root-MuiAlert-root"
role="alert"
>
<div
class="MuiAlert-icon css-1ytlwq5-MuiAlert-icon"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeInherit css-1vooibu-MuiSvgIcon-root"
data-testid="ReportProblemOutlinedIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 5.99L19.53 19H4.47L12 5.99M12 2L1 21h22L12 2zm1 14h-2v2h2v-2zm0-6h-2v4h2v-4z"
/>
</svg>
</div>
<div
class="MuiAlert-message css-1pxa9xg-MuiAlert-message"
>
<div
class="MuiTypography-root MuiTypography-body1 MuiTypography-gutterBottom MuiAlertTitle-root css-m946el-MuiTypography-root-MuiAlertTitle-root"
>
Failed to load resources
</div>
</div>
<div
class="MuiAlert-action css-ki1hdl-MuiAlert-action"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textWarning MuiButton-sizeSmall MuiButton-textSizeSmall MuiButton-colorWarning MuiButton-root MuiButton-text MuiButton-textWarning MuiButton-sizeSmall MuiButton-textSizeSmall MuiButton-colorWarning css-wucs85-MuiButtonBase-root-MuiButton-root"
tabindex="0"
type="button"
>
Show details
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
</body>
Loading