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:
parent
951e5cc035
commit
031fd08b91
6 changed files with 89 additions and 17 deletions
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue