ImagesTable: Add popovers with error details for failed builds

This removes error details from the image detail and moves them to popovers activated by clicking on "Image build failed" status.

Popovers were also added for clones which didn't include any error details previously.
This commit is contained in:
regexowl 2023-04-18 09:51:18 +02:00 committed by Lucas Garfield
parent 951e5cc035
commit 031fd08b91
6 changed files with 89 additions and 17 deletions

View file

@ -49,7 +49,7 @@ const Row = ({ imageId }) => {
<Td dataLabel="Account">{getAccount(image)}</Td>
<Td dataLabel="Region">{image.region}</Td>
<Td dataLabel="Status">
<ImageBuildStatus imageId={image.id} />
<ImageBuildStatus imageId={image.id} imageRegion={image.region} />
</Td>
<Td dataLabel="Instance">
<ImageLink imageId={image.id} isInClonesTable={true} />

View file

@ -1,6 +1,7 @@
import React from 'react';
import { Alert } from '@patternfly/react-core';
import { Button } from '@patternfly/react-core';
import { CopyIcon } from '@patternfly/react-icons';
import PropTypes from 'prop-types';
const useGetErrorReason = (err) => {
@ -24,9 +25,14 @@ const ErrorDetails = ({ status }) => {
return (
<div className="pf-u-mt-sm">
<strong>Status</strong>
<Alert variant="danger" title="Image build failed" isInline isPlain />
<p className="pf-u-danger-color-200 pf-u-w-33-on-md">{reason}</p>
<p>{reason}</p>
<Button
variant="link"
onClick={() => navigator.clipboard.writeText(reason)}
className="pf-u-pl-0 pf-u-mt-md"
>
Copy error text to clipboard <CopyIcon />
</Button>
</div>
);
};

View file

@ -1,6 +1,13 @@
import React from 'react';
import { Flex } from '@patternfly/react-core';
import {
Alert,
Button,
Flex,
Panel,
PanelMain,
Popover,
} from '@patternfly/react-core';
import {
CheckCircleIcon,
ExclamationCircleIcon,
@ -13,6 +20,8 @@ import PropTypes from 'prop-types';
import './ImageBuildStatus.scss';
import { useSelector } from 'react-redux';
import ErrorDetails from './ImageBuildErrorDetails';
import { AWS_S3_EXPIRATION_TIME_IN_HOURS } from '../../constants';
import {
selectImageById,
@ -20,12 +29,30 @@ import {
} from '../../store/composesSlice';
import { hoursToExpiration } from '../../Utilities/time';
export const ImageBuildStatus = ({ imageId, isImagesTableRow }) => {
export const ImageBuildStatus = ({
imageId,
isImagesTableRow,
imageStatus,
imageRegion,
}) => {
const image = useSelector((state) => selectImageById(state, imageId));
const remainingHours =
AWS_S3_EXPIRATION_TIME_IN_HOURS - hoursToExpiration(image.created_at);
const cloneErrorMessage = () => {
let region = '';
hasFailedClone.includes(image.id)
? (region = 'one or more regions')
: (region = imageRegion);
return {
error: {
reason: `Failed to share image to ${region}.`,
},
status: 'failure',
};
};
// Messages appear in order of priority
const messages = {
failure: [
@ -94,6 +121,7 @@ export const ImageBuildStatus = ({ imageId, isImagesTableRow }) => {
],
};
const hasFailedClone = [];
let status;
if (
isImagesTableRow &&
@ -108,6 +136,9 @@ export const ImageBuildStatus = ({ imageId, isImagesTableRow }) => {
const imageStatuses = useSelector((state) =>
selectImageStatusesById(state, image.id)
);
if (imageStatuses.includes('failure')) {
hasFailedClone.push(image.id);
}
const filteredImageStatuses = imageStatuses.filter(
(imageStatus) => imageStatus !== undefined
);
@ -136,7 +167,39 @@ export const ImageBuildStatus = ({ imageId, isImagesTableRow }) => {
messages[status].map((message, key) => (
<Flex key={key} className="pf-u-align-items-baseline pf-m-nowrap">
<div className="pf-u-mr-sm">{message.icon}</div>
{message.text}
{status === 'failure' ? (
<Popover
position="bottom"
minWidth="30rem"
bodyContent={
<>
<Alert
variant="danger"
title="Image build failed"
isInline
isPlain
/>
<Panel isScrollable>
<PanelMain maxHeight="25rem">
<ErrorDetails
status={
!imageStatus || hasFailedClone.includes(image.id)
? cloneErrorMessage()
: imageStatus
}
/>
</PanelMain>
</Panel>
</>
}
>
<Button variant="link" className="pf-u-p-0 pf-u-font-size-sm">
<div className="failure-button">{message.text}</div>
</Button>
</Popover>
) : (
message.text
)}
</Flex>
))}
</React.Fragment>
@ -146,4 +209,6 @@ export const ImageBuildStatus = ({ imageId, isImagesTableRow }) => {
ImageBuildStatus.propTypes = {
imageId: PropTypes.string,
isImagesTableRow: PropTypes.bool,
imageStatus: PropTypes.object,
imageRegion: PropTypes.string,
};

View file

@ -10,3 +10,10 @@
.expiring {
color: var(--pf-global--warning-color--100);
}
.failure-button {
color: var(--pf-global--Color--100);
text-decoration: underline;
text-decoration-style: dotted;
text-decoration-color: grey;
}

View file

@ -30,7 +30,6 @@ import { Link, useNavigate } from 'react-router-dom';
import './ImagesTable.scss';
import ClonesTable from './ClonesTable';
import ErrorDetails from './ImageBuildErrorDetails';
import { ImageBuildStatus } from './ImageBuildStatus';
import ImageLink from './ImageLink';
import Release from './Release';
@ -252,6 +251,7 @@ const ImagesTable = () => {
<ImageBuildStatus
imageId={id}
isImagesTableRow={true}
imageStatus={compose.image_status}
/>
</Td>
<Td dataLabel="Instance">
@ -283,7 +283,6 @@ const ImagesTable = () => {
<ExpandableRowContent>
<strong>UUID</strong>
<div>{id}</div>
<ErrorDetails status={compose.image_status} />
</ExpandableRowContent>
)}
</Td>

View file

@ -633,18 +633,13 @@ describe('Images Table', () => {
const { getAllByRole } = within(table);
const rows = getAllByRole('row');
const errorToggle = within(rows[2]).getByRole('button', {
name: /details/i,
});
const errorPopover = within(rows[2]).getByText(/image build failed/i);
expect(
screen.getAllByText(/c1cfa347-4c37-49b5-8e73-6aa1d1746cfa/i)[1]
).not.toBeVisible();
await user.click(errorToggle);
await user.click(errorPopover);
expect(
screen.getAllByText(/c1cfa347-4c37-49b5-8e73-6aa1d1746cfa/i)[1]
).toBeVisible();
expect(screen.getAllByText(/Error in depsolve job/i)[0]).toBeVisible();
});
});