diff --git a/src/Components/ImagesTable/ClonesTable.js b/src/Components/ImagesTable/ClonesTable.js
new file mode 100644
index 00000000..a99a3531
--- /dev/null
+++ b/src/Components/ImagesTable/ClonesTable.js
@@ -0,0 +1,81 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {
+ TableComposable,
+ Tbody,
+ Td,
+ Th,
+ Thead,
+ Tr,
+} from '@patternfly/react-table';
+import { useSelector } from 'react-redux';
+import { ImageBuildStatus } from './ImageBuildStatus';
+import ImageLink from './ImageLink';
+import {
+ selectClonesById,
+ selectComposeById,
+ selectImageById,
+} from '../../store/composesSlice';
+import { timestampToDisplayString } from '../../Utilities/time';
+
+const Row = ({ imageId }) => {
+ const image = useSelector((state) => selectImageById(state, imageId));
+ return (
+
+
+ | {image.id} |
+
+ {timestampToDisplayString(image.created_at)}
+ |
+ {image.share_with_accounts?.[0]} |
+ {image.region} |
+
+
+ |
+
+
+ |
+
+
+ );
+};
+
+const ClonesTable = ({ composeId }) => {
+ const parentCompose = useSelector((state) =>
+ selectComposeById(state, composeId)
+ );
+ const clones = useSelector((state) => selectClonesById(state, composeId));
+
+ return (
+
+
+
+ | UUID |
+ Created |
+ Account |
+ Region |
+ Status |
+ Instance |
+
+
+
+ {clones.map((clone) => (
+
+ ))}
+
+ );
+};
+
+Row.propTypes = {
+ imageId: PropTypes.string,
+};
+
+ClonesTable.propTypes = {
+ composeId: PropTypes.string,
+};
+
+export default ClonesTable;
diff --git a/src/Components/ImagesTable/ImageBuildStatus.js b/src/Components/ImagesTable/ImageBuildStatus.js
index ffa9aaa2..1982ee19 100644
--- a/src/Components/ImagesTable/ImageBuildStatus.js
+++ b/src/Components/ImagesTable/ImageBuildStatus.js
@@ -12,25 +12,34 @@ import {
} from '@patternfly/react-icons';
import './ImageBuildStatus.scss';
+import { useSelector } from 'react-redux';
+import {
+ selectImageById,
+ selectImageStatusesById,
+} from '../../store/composesSlice';
+import { hoursToExpiration } from '../../Utilities/time';
+import { AWS_S3_EXPIRATION_TIME_IN_HOURS } from '../../constants';
-const ImageBuildStatus = (props) => {
+export const ImageBuildStatus = ({ imageId }) => {
+ const image = useSelector((state) => selectImageById(state, imageId));
+
+ const remainingHours =
+ AWS_S3_EXPIRATION_TIME_IN_HOURS - hoursToExpiration(image.created_at);
+
+ // Messages appear in order of priority
const messages = {
- success: [
- {
- icon: ,
- text: 'Ready',
- },
- ],
failure: [
{
icon: ,
text: 'Image build failed',
+ priority: 6,
},
],
pending: [
{
icon: ,
text: 'Image build is pending',
+ priority: 2,
},
],
// Keep "running" for backward compatibility
@@ -38,31 +47,42 @@ const ImageBuildStatus = (props) => {
{
icon: ,
text: 'Image build in progress',
+ priority: 1,
},
],
building: [
{
icon: ,
text: 'Image build in progress',
+ priority: 3,
},
],
uploading: [
{
icon: ,
text: 'Image upload in progress',
+ priority: 4,
},
],
registering: [
{
icon: ,
text: 'Cloud registration in progress',
+ priority: 5,
+ },
+ ],
+ success: [
+ {
+ icon: ,
+ text: 'Ready',
+ priority: 0,
},
],
expiring: [
{
icon: ,
- text: `Expires in ${props.remainingHours} ${
- props.remainingHours > 1 ? 'hours' : 'hour'
+ text: `Expires in ${remainingHours} ${
+ remainingHours > 1 ? 'hours' : 'hour'
}`,
},
],
@@ -73,10 +93,38 @@ const ImageBuildStatus = (props) => {
},
],
};
+
+ let status;
+ if (image.imageType === 'aws') {
+ const imageStatuses = useSelector((state) =>
+ selectImageStatusesById(state, image.id)
+ );
+ const filteredImageStatuses = imageStatuses.filter(
+ (imageStatus) => imageStatus !== undefined
+ );
+ if (filteredImageStatuses.length === 0) {
+ status = image.status;
+ } else {
+ status = filteredImageStatuses.reduce((prev, current) => {
+ return messages[prev][0].priority > messages[current][0].priority
+ ? prev
+ : current;
+ });
+ }
+ } else if (image.uploadType === 'aws.s3' && image.status === 'success') {
+ // Cloud API currently reports expired images status as 'success'
+ status =
+ hoursToExpiration(image.created_at) >= AWS_S3_EXPIRATION_TIME_IN_HOURS
+ ? 'expired'
+ : 'expiring';
+ } else {
+ status = image.status;
+ }
+
return (
- {messages[props.status] &&
- messages[props.status].map((message, key) => (
+ {messages[status] &&
+ messages[status].map((message, key) => (
{message.icon}
{message.text}
@@ -87,8 +135,5 @@ const ImageBuildStatus = (props) => {
};
ImageBuildStatus.propTypes = {
- status: PropTypes.string,
- remainingHours: PropTypes.number,
+ imageId: PropTypes.string,
};
-
-export default ImageBuildStatus;
diff --git a/src/Components/ImagesTable/ImageLink.js b/src/Components/ImagesTable/ImageLink.js
index 517304f0..b0d918bb 100644
--- a/src/Components/ImagesTable/ImageLink.js
+++ b/src/Components/ImagesTable/ImageLink.js
@@ -1,70 +1,98 @@
-import React, { Suspense } from 'react';
+import React, { Suspense, useState } from 'react';
import PropTypes from 'prop-types';
import { Button } from '@patternfly/react-core';
import { useLoadModule, useScalprum } from '@scalprum/react-core';
+import { useSelector } from 'react-redux';
import ImageLinkDirect from './ImageLinkDirect';
+import { selectImageById } from '../../store/composesSlice';
+import { selectComposeById } from '../../store/composesSlice';
-const ImageLink = ({
- imageId,
- imageName,
- imageType,
- imageStatus,
- ...props
-}) => {
- const scalprum = useScalprum();
- const hasProvisionig = scalprum.initialized && scalprum.config?.provisioning;
- const uploadStatus = imageStatus?.upload_status;
+const ProvisioningLink = ({ imageId, isExpired, isInClonesTable }) => {
+ let image = useSelector((state) => selectImageById(state, imageId));
+ const parent = image.isClone
+ ? useSelector((state) => selectComposeById(state, image.parent))
+ : null;
- if (!uploadStatus) return null;
+ const [wizardOpen, openWizard] = useState(false);
+ const [{ default: ProvisioningWizard }, error] = useLoadModule(
+ {
+ appName: 'provisioning', // optional
+ scope: 'provisioning',
+ module: './ProvisioningWizard',
+ // processor: (val) => val, // optional
+ },
+ {},
+ {}
+ );
- if (hasProvisionig && imageType === 'ami') {
- const [wizardOpen, openWizard] = React.useState(false);
- const [{ default: ProvisioningWizard }, error] = useLoadModule(
- {
- appName: 'provisioning', // optional
- scope: 'provisioning',
- module: './ProvisioningWizard',
- // processor: (val) => val, // optional
- },
- {},
- {}
+ if (!error) {
+ image = image.isClone ? parent : image;
+ return (
+
+
+ {wizardOpen && (
+ openWizard(false)}
+ image={{
+ name: image.imageName,
+ id: image.id,
+ }}
+ />
+ )}
+
);
-
- if (!error) {
- return (
-
-
- {wizardOpen && (
- openWizard(false)}
- image={{ name: imageName, id: imageId }}
- />
- )}
-
- );
- }
}
return (
);
};
+const ImageLink = ({ imageId, isExpired, isInClonesTable }) => {
+ const image = useSelector((state) => selectImageById(state, imageId));
+ const uploadStatus = image.uploadStatus;
+
+ const scalprum = useScalprum();
+ const hasProvisioning = scalprum.initialized && scalprum.config?.provisioning;
+
+ if (!uploadStatus) return null;
+
+ if (hasProvisioning && image.imageType === 'ami') {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+ProvisioningLink.propTypes = {
+ imageId: PropTypes.string,
+ isExpired: PropTypes.bool,
+ isInClonesTable: PropTypes.bool,
+};
+
ImageLink.propTypes = {
imageId: PropTypes.string.isRequired,
- imageName: PropTypes.string.isRequired,
- imageStatus: PropTypes.object,
- imageType: PropTypes.string,
- uploadOptions: PropTypes.object,
isExpired: PropTypes.bool,
- recreateImage: PropTypes.object,
+ isInClonesTable: PropTypes.bool,
};
export default ImageLink;
diff --git a/src/Components/ImagesTable/ImageLinkDirect.js b/src/Components/ImagesTable/ImageLinkDirect.js
index 7dd73812..50f3cf31 100644
--- a/src/Components/ImagesTable/ImageLinkDirect.js
+++ b/src/Components/ImagesTable/ImageLinkDirect.js
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useNavigate } from 'react-router-dom';
+import { useSelector } from 'react-redux';
import {
Button,
@@ -10,10 +11,16 @@ import {
TextVariants,
} from '@patternfly/react-core';
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
+import { RegionsPopover } from './RegionsPopover';
+import { selectImageById } from '../../store/composesSlice';
import { resolveRelPath } from '../../Utilities/path';
-const ImageLinkDirect = ({ uploadStatus, ...props }) => {
+const ImageLinkDirect = ({ imageId, isExpired, isInClonesTable }) => {
const navigate = useNavigate();
+
+ const image = useSelector((state) => selectImageById(state, imageId));
+ const uploadStatus = image.uploadStatus;
+
const fileExtensions = {
vsphere: '.vmdk',
'guest-image': '.qcow2',
@@ -26,27 +33,29 @@ const ImageLinkDirect = ({ uploadStatus, ...props }) => {
uploadStatus.options.region +
'#LaunchInstanceWizard:ami=' +
uploadStatus.options.ami;
- return (
- }
- iconPosition="right"
- isInline
- href={url}
- >
- Launch instance
-
- );
+ if (isInClonesTable) {
+ return (
+
+ );
+ } else {
+ return ;
+ }
} else if (uploadStatus.type === 'azure') {
const url =
'https://portal.azure.com/#@' +
- props.uploadOptions.tenant_id +
+ image.uploadOptions.tenant_id +
'/resource/subscriptions/' +
- props.uploadOptions.subscription_id +
+ image.uploadOptions.subscription_id +
'/resourceGroups/' +
- props.uploadOptions.resource_group +
+ image.uploadOptions.resource_group +
'/providers/Microsoft.Compute/images/' +
uploadStatus.options.image_name;
return (
@@ -89,7 +98,7 @@ const ImageLinkDirect = ({ uploadStatus, ...props }) => {
Shared with
{/* the account the image is shared with is stored in the form type:account so this extracts the account */}
- {props.uploadOptions.share_with_accounts[0].split(':')[1]}
+ {image.uploadOptions.share_with_accounts[0].split(':')[1]}
}
@@ -100,7 +109,7 @@ const ImageLinkDirect = ({ uploadStatus, ...props }) => {
);
} else if (uploadStatus.type === 'aws.s3') {
- if (!props.isExpired) {
+ if (!isExpired) {
return (
);
} else {
@@ -121,7 +130,7 @@ const ImageLinkDirect = ({ uploadStatus, ...props }) => {
onClick={() =>
navigate(resolveRelPath('imagewizard'), {
state: {
- composeRequest: props.recreateImage,
+ composeRequest: image.request,
initialStep: 'review',
},
})
@@ -138,11 +147,9 @@ const ImageLinkDirect = ({ uploadStatus, ...props }) => {
};
ImageLinkDirect.propTypes = {
- uploadStatus: PropTypes.object,
- imageType: PropTypes.string,
+ imageId: PropTypes.string,
isExpired: PropTypes.bool,
- recreateImage: PropTypes.object,
- uploadOptions: PropTypes.object,
+ isInClonesTable: PropTypes.bool,
};
export default ImageLinkDirect;
diff --git a/src/Components/ImagesTable/ImagesTable.js b/src/Components/ImagesTable/ImagesTable.js
index dfb5eb8c..10293ef8 100644
--- a/src/Components/ImagesTable/ImagesTable.js
+++ b/src/Components/ImagesTable/ImagesTable.js
@@ -27,14 +27,20 @@ import {
} from '@patternfly/react-core';
import { PlusCircleIcon } from '@patternfly/react-icons';
import './ImagesTable.scss';
-import ImageBuildStatus from './ImageBuildStatus';
+import { ImageBuildStatus } from './ImageBuildStatus';
import Release from './Release';
import Target from './Target';
import ImageLink from './ImageLink';
import ErrorDetails from './ImageBuildErrorDetails';
+import ClonesTable from './ClonesTable';
import DocumentationButton from '../sharedComponents/DocumentationButton';
import { fetchComposes, fetchComposeStatus } from '../../store/actions/actions';
import { resolveRelPath } from '../../Utilities/path';
+import {
+ hoursToExpiration,
+ timestampToDisplayString,
+} from '../../Utilities/time';
+import { AWS_S3_EXPIRATION_TIME_IN_HOURS } from '../../constants';
const ImagesTable = () => {
const [page, setPage] = useState(1);
@@ -111,65 +117,6 @@ const ImagesTable = () => {
setPage(1);
};
- const timestampToDisplayString = (ts) => {
- // timestamp has format 2021-04-27 12:31:12.794809 +0000 UTC
- // must be converted to ms timestamp and then reformatted to Apr 27, 2021
- if (!ts) {
- return '';
- }
-
- // get YYYY-MM-DD format
- const date = ts.slice(0, 10);
- const ms = Date.parse(date);
- const options = { month: 'short', day: 'numeric', year: 'numeric' };
- const tsDisplay = new Intl.DateTimeFormat('en-US', options).format(ms);
- return tsDisplay;
- };
-
- const convertStringToDate = (createdAtAsString) => {
- if (isNaN(Date.parse(createdAtAsString))) {
- // converts property created_at of the image object from string to UTC
- const [dateValues, timeValues] = createdAtAsString.split(' ');
- const datetimeString = `${dateValues}T${timeValues}Z`;
- return Date.parse(datetimeString);
- } else {
- return Date.parse(createdAtAsString);
- }
- };
-
- const setComposeStatus = (compose) => {
- if (!compose.image_status) {
- return '';
- } else if (
- compose.request.image_requests[0].upload_request.type !== 'aws.s3' ||
- compose.image_status.status !== 'success'
- ) {
- return compose.image_status.status;
- } else if (
- hoursToExpiration(compose.created_at) >= s3ExpirationTimeInHours
- ) {
- return 'expired';
- } else {
- return 'expiring';
- }
- };
-
- const hoursToExpiration = (imageCreatedAt) => {
- if (imageCreatedAt) {
- const currentTime = Date.now();
- // miliseconds in hour - needed for calculating the difference
- // between current date and the date of the image creation
- const msInHour = 1000 * 60 * 60;
- const timeUntilExpiration = Math.floor(
- (currentTime - convertStringToDate(imageCreatedAt)) / msInHour
- );
- return timeUntilExpiration;
- } else {
- // when creating a new image, the compose.created_at can be undefined when first queued
- return 0;
- }
- };
-
const actions = (compose) => [
{
title: 'Recreate image',
@@ -197,8 +144,6 @@ const ImagesTable = () => {
const itemsStartInclusive = (page - 1) * perPage;
const itemsEndExclusive = itemsStartInclusive + perPage;
- const s3ExpirationTimeInHours = 6;
-
return (
{(composes.allIds.length === 0 && (
@@ -258,7 +203,7 @@ const ImagesTable = () => {
|
Image name |
- Created |
+ Created/Updated |
Release |
Target |
Status |
@@ -272,7 +217,7 @@ const ImagesTable = () => {
const compose = composes.byId[id];
return (
-
+
| {
|
-
+
|
-
+
|
=
- s3ExpirationTimeInHours
+ AWS_S3_EXPIRATION_TIME_IN_HOURS
? true
: false
}
- recreateImage={compose.request}
/>
|
@@ -337,11 +258,16 @@ const ImagesTable = () => {
|
|
-
- UUID
- {id}
-
-
+ {compose.request.image_requests[0].upload_request
+ .type === 'aws' ? (
+
+ ) : (
+
+ UUID
+ {id}
+
+
+ )}
|
diff --git a/src/Components/ImagesTable/ImagesTable.scss b/src/Components/ImagesTable/ImagesTable.scss
index e463134c..d56ace76 100644
--- a/src/Components/ImagesTable/ImagesTable.scss
+++ b/src/Components/ImagesTable/ImagesTable.scss
@@ -1,11 +1,4 @@
-@media only screen and (min-width: 768px) {
- .pf-c-table__expandable-row td:first-child {
- // Align with expand/collapse button by duplicating its padding
- padding-left: calc(var(--pf-c-table--cell--first-last-child--PaddingLeft) + var(--pf-global--spacer--md));
- }
-}
-.pf-m-expanded tr:first-child {
- // Remove border between a compose and its expanded detail
+.pf-m-expanded .no-bottom-border {
border-bottom-style: none;
}
diff --git a/src/Components/ImagesTable/RegionsPopover.js b/src/Components/ImagesTable/RegionsPopover.js
new file mode 100644
index 00000000..c18895df
--- /dev/null
+++ b/src/Components/ImagesTable/RegionsPopover.js
@@ -0,0 +1,90 @@
+import React, { useMemo } from 'react';
+import PropTypes from 'prop-types';
+import { Button, Popover } from '@patternfly/react-core';
+import { useSelector } from 'react-redux';
+import { createSelector } from '@reduxjs/toolkit';
+import { selectComposeById, selectImagesById } from '../../store/composesSlice';
+
+export const selectRegions = createSelector(
+ [selectComposeById, selectImagesById],
+ (compose, images) => {
+ const filteredImages = images.filter(
+ (image) =>
+ compose.share_with_accounts &&
+ compose.share_with_accounts[0] === image.share_with_accounts[0]
+ );
+
+ let regions = {};
+ filteredImages.forEach((image) => {
+ if (image.region && image.status === 'success') {
+ if (regions[image.region]) {
+ new Date(image.created_at) <
+ new Date(regions[image.region].created_at)
+ ? null
+ : (regions[image.region] = {
+ ami: image.ami,
+ created_at: image.created_at,
+ });
+ } else {
+ regions[image.region] = {
+ ami: image.ami,
+ created_at: image.created_at,
+ };
+ }
+ }
+ });
+
+ return regions;
+ }
+);
+
+const ImageLinkRegion = ({ region, ami }) => {
+ const url =
+ 'https://console.aws.amazon.com/ec2/v2/home?region=' +
+ region +
+ '#LaunchInstanceWizard:ami=' +
+ ami;
+
+ return (
+
+ );
+};
+
+export const RegionsPopover = ({ composeId }) => {
+ const regions = useSelector((state) => selectRegions(state, composeId));
+
+ const listItems = useMemo(() => {
+ let listItems = [];
+ for (const [key, value] of Object.entries(regions).sort()) {
+ listItems.push(
+
+
+
+ );
+ }
+ return listItems;
+ }, [regions]);
+
+ return (
+ Launch instance}
+ bodyContent={}
+ >
+
+
+ );
+};
+
+ImageLinkRegion.propTypes = {
+ region: PropTypes.string,
+ ami: PropTypes.string,
+};
+
+RegionsPopover.propTypes = {
+ composeId: PropTypes.string,
+};
diff --git a/src/Components/ImagesTable/Target.js b/src/Components/ImagesTable/Target.js
index eee8f525..7699ba92 100644
--- a/src/Components/ImagesTable/Target.js
+++ b/src/Components/ImagesTable/Target.js
@@ -1,7 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
+import { useSelector } from 'react-redux';
+import { selectComposeById } from '../../store/composesSlice';
+
+const Target = ({ composeId }) => {
+ const compose = useSelector((state) => selectComposeById(state, composeId));
-const Target = (props) => {
const targetOptions = {
aws: 'Amazon Web Services',
azure: 'Microsoft Azure',
@@ -12,18 +16,21 @@ const Target = (props) => {
};
let target;
- if (props.uploadType === 'aws.s3') {
- target = targetOptions[props.imageType];
+ if (compose.uploadType === 'aws.s3') {
+ target = targetOptions[compose.imageType];
+ } else if (compose.uploadType === 'aws') {
+ target =
+ targetOptions[compose.uploadType] +
+ ` (${compose.clones.length !== 0 ? compose.clones.length + 1 : 1})`;
} else {
- target = targetOptions[props.uploadType];
+ target = targetOptions[compose.uploadType];
}
return <>{target}>;
};
Target.propTypes = {
- uploadType: PropTypes.string,
- imageType: PropTypes.string,
+ composeId: PropTypes.string,
};
export default Target;
diff --git a/src/Utilities/time.js b/src/Utilities/time.js
index 6a62a7bf..f44b13d6 100644
--- a/src/Utilities/time.js
+++ b/src/Utilities/time.js
@@ -1,15 +1,3 @@
-export const timestampToISO8601 = (timestamp) => {
- if (!timestamp) {
- return '';
- }
-
- const date = timestamp.slice(0, 10);
- const time = timestamp.slice(11, 26);
-
- return `${date}T${time}+0000`;
-};
-
-
export const timestampToDisplayString = (ts) => {
// timestamp has format 2021-04-27 12:31:12.794809 +0000 UTC
// must be converted to ms timestamp and then reformatted to Apr 27, 2021
@@ -24,3 +12,30 @@ export const timestampToDisplayString = (ts) => {
const tsDisplay = new Intl.DateTimeFormat('en-US', options).format(ms);
return tsDisplay;
};
+
+export const convertStringToDate = (createdAtAsString) => {
+ if (isNaN(Date.parse(createdAtAsString))) {
+ // converts property created_at of the image object from string to UTC
+ const [dateValues, timeValues] = createdAtAsString.split(' ');
+ const datetimeString = `${dateValues}T${timeValues}Z`;
+ return Date.parse(datetimeString);
+ } else {
+ return Date.parse(createdAtAsString);
+ }
+};
+
+export const hoursToExpiration = (imageCreatedAt) => {
+ if (imageCreatedAt) {
+ const currentTime = Date.now();
+ // miliseconds in hour - needed for calculating the difference
+ // between current date and the date of the image creation
+ const msInHour = 1000 * 60 * 60;
+ const timeUntilExpiration = Math.floor(
+ (currentTime - convertStringToDate(imageCreatedAt)) / msInHour
+ );
+ return timeUntilExpiration;
+ } else {
+ // when creating a new image, the compose.created_at can be undefined when first queued
+ return 0;
+ }
+};
diff --git a/src/constants.js b/src/constants.js
index af20d250..d830600f 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -43,3 +43,5 @@ export const AWS_REGIONS = [
{ description: 'Middle East (UAE)', value: 'me-central-1' },
{ description: 'South America (S\u00e3o Paolo)', value: 'sa-east-1' },
];
+
+export const AWS_S3_EXPIRATION_TIME_IN_HOURS = 6;
diff --git a/src/test/Components/ImagesTable/ImagesTable.test.js b/src/test/Components/ImagesTable/ImagesTable.test.js
index a8a18c24..c297346f 100644
--- a/src/test/Components/ImagesTable/ImagesTable.test.js
+++ b/src/test/Components/ImagesTable/ImagesTable.test.js
@@ -46,10 +46,12 @@ const mockComposes = {
image_requests: [
{
architecture: 'x86_64',
- image_type: 'ami',
+ image_type: 'vhd',
upload_request: {
- type: 'aws',
- options: {},
+ type: 'gcp',
+ options: {
+ share_with_accounts: ['serviceAccount:test@email.com'],
+ },
},
},
],
@@ -247,7 +249,13 @@ const mockStatus = {
// kept "running" for backward compatibility
'c1cfa347-4c37-49b5-8e73-6aa1d1746cfa': {
image_status: {
- status: 'running',
+ status: 'failure',
+ error: {
+ reason: 'A dependency error occured',
+ details: {
+ reason: 'Error in depsolve job',
+ },
+ },
},
},
'edbae1c2-62bc-42c1-ae0c-3110ab718f58': {
@@ -537,17 +545,17 @@ describe('Images Table', () => {
const { getAllByRole } = within(table);
const rows = getAllByRole('row');
- const errorToggle = within(rows[7]).getByRole('button', {
+ const errorToggle = within(rows[2]).getByRole('button', {
name: /details/i,
});
expect(
- screen.getAllByText(/61b0effa-c901-4ee5-86b9-2010b47f1b22/i)[1]
+ screen.getAllByText(/c1cfa347-4c37-49b5-8e73-6aa1d1746cfa/i)[1]
).not.toBeVisible();
userEvent.click(errorToggle);
expect(
- screen.getAllByText(/61b0effa-c901-4ee5-86b9-2010b47f1b22/i)[1]
+ screen.getAllByText(/c1cfa347-4c37-49b5-8e73-6aa1d1746cfa/i)[1]
).toBeVisible();
expect(screen.getAllByText(/Error in depsolve job/i)[0]).toBeVisible();
});