diff --git a/libs/i18n/locales/en/translation.json b/libs/i18n/locales/en/translation.json index a77d83e7..df534785 100644 --- a/libs/i18n/locales/en/translation.json +++ b/libs/i18n/locales/en/translation.json @@ -881,6 +881,8 @@ "Pushing": "Pushing", "Complete": "Complete", "Failed": "Failed", + "Canceling": "Canceling", + "Canceled": "Canceled", "Converting": "Converting", "Image built successfully": "Image built successfully", "Export images": "Export images", @@ -895,8 +897,8 @@ "OpenStack/KVM (QCOW2)": "OpenStack/KVM (QCOW2)", "OpenShift Virtualization (QCOW2)": "OpenShift Virtualization (QCOW2)", "Metal installer (ISO)": "Metal installer (ISO)", - "In progress": "In progress", "Completed": "Completed", + "In progress": "In progress", "Failed to load logs": "Failed to load logs", "For built and failed export tasks, only the last 500 lines are available.": "For built and failed export tasks, only the last 500 lines are available.", "Export image": "Export image", diff --git a/libs/types/imagebuilder/models/ImageBuildConditionReason.ts b/libs/types/imagebuilder/models/ImageBuildConditionReason.ts index d2a32d70..5ac87562 100644 --- a/libs/types/imagebuilder/models/ImageBuildConditionReason.ts +++ b/libs/types/imagebuilder/models/ImageBuildConditionReason.ts @@ -11,4 +11,6 @@ export enum ImageBuildConditionReason { ImageBuildConditionReasonPushing = 'Pushing', ImageBuildConditionReasonCompleted = 'Completed', ImageBuildConditionReasonFailed = 'Failed', + ImageBuildConditionReasonCanceling = 'Canceling', + ImageBuildConditionReasonCanceled = 'Canceled', } diff --git a/libs/types/imagebuilder/models/ImageExportConditionReason.ts b/libs/types/imagebuilder/models/ImageExportConditionReason.ts index 75535081..755b5b08 100644 --- a/libs/types/imagebuilder/models/ImageExportConditionReason.ts +++ b/libs/types/imagebuilder/models/ImageExportConditionReason.ts @@ -11,4 +11,6 @@ export enum ImageExportConditionReason { ImageExportConditionReasonPushing = 'Pushing', ImageExportConditionReasonCompleted = 'Completed', ImageExportConditionReasonFailed = 'Failed', + ImageExportConditionReasonCanceling = 'Canceling', + ImageExportConditionReasonCanceled = 'Canceled', } diff --git a/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.tsx b/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.tsx index 37efc017..1f05b491 100644 --- a/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.tsx +++ b/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.tsx @@ -13,7 +13,7 @@ import { } from '@patternfly/react-core'; import { Formik, FormikErrors } from 'formik'; -import { ExportFormatType, ImageBuild } from '@flightctl/types/imagebuilder'; +import { ExportFormatType, ImageBuild, ImageBuildConditionReason } from '@flightctl/types/imagebuilder'; import { RESOURCE, VERB } from '../../../types/rbac'; import { useTranslation } from '../../../hooks/useTranslation'; import { Link, ROUTE, useNavigate } from '../../../hooks/useNavigate'; @@ -34,7 +34,7 @@ import CreateImageBuildWizardFooter from './CreateImageBuildWizardFooter'; import { useFetch } from '../../../hooks/useFetch'; import { useEditImageBuild } from './useEditImageBuild'; import { OciRegistriesContextProvider, useOciRegistriesContext } from '../OciRegistriesContext'; -import { hasImageBuildFailed } from '../../../utils/imageBuilds'; +import { getImageBuildStatusReason } from '../../../utils/imageBuilds'; const orderedIds = [sourceImageStepId, outputImageStepId, registrationStepId, reviewStepId]; @@ -73,11 +73,14 @@ const CreateImageBuildWizard = () => { const { isLoading: registriesLoading, error: registriesError } = useOciRegistriesContext(); const isEdit = !!imageBuildId; - const hasFailed = imageBuild ? hasImageBuildFailed(imageBuild) : false; + const buildReason = imageBuild ? getImageBuildStatusReason(imageBuild) : undefined; let title: string; if (isEdit) { - title = hasFailed ? t('Retry image build') : t('Duplicate image build'); + title = + buildReason === ImageBuildConditionReason.ImageBuildConditionReasonFailed + ? t('Retry image build') + : t('Duplicate image build'); } else { title = t('Build new image'); } diff --git a/libs/ui-components/src/components/ImageBuilds/ImageBuildAndExportStatus.tsx b/libs/ui-components/src/components/ImageBuilds/ImageBuildAndExportStatus.tsx index f3228512..21ec3c76 100644 --- a/libs/ui-components/src/components/ImageBuilds/ImageBuildAndExportStatus.tsx +++ b/libs/ui-components/src/components/ImageBuilds/ImageBuildAndExportStatus.tsx @@ -48,6 +48,10 @@ const getImageBuildStatusInfo = (condition: ImageBuildCondition | undefined, t: return { level: 'success', label: t('Complete') }; case ImageBuildConditionReason.ImageBuildConditionReasonFailed: return { level: 'danger', label: t('Failed') }; + case ImageBuildConditionReason.ImageBuildConditionReasonCanceling: + return { level: 'warning', label: t('Canceling') }; + case ImageBuildConditionReason.ImageBuildConditionReasonCanceled: + return { level: 'warning', label: t('Canceled') }; default: return { level: 'unknown', label: t('Unknown') }; } @@ -68,6 +72,10 @@ const getImageExportStatusInfo = (condition: ImageExportCondition | undefined, t return { level: 'success', label: t('Complete') }; case ImageExportConditionReason.ImageExportConditionReasonFailed: return { level: 'danger', label: t('Failed') }; + case ImageExportConditionReason.ImageExportConditionReasonCanceling: + return { level: 'warning', label: t('Canceling') }; + case ImageExportConditionReason.ImageExportConditionReasonCanceled: + return { level: 'warning', label: t('Canceled') }; default: return { level: 'unknown', label: t('Unknown') }; } diff --git a/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx b/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx index c7989efb..b693fb05 100644 --- a/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx +++ b/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx @@ -1,12 +1,14 @@ import * as React from 'react'; import { DropdownItem, DropdownList, Tab } from '@patternfly/react-core'; +import { ImageBuildConditionReason } from '@flightctl/types/imagebuilder'; import { RESOURCE, VERB } from '../../../types/rbac'; import PageWithPermissions from '../../common/PageWithPermissions'; import { useTranslation } from '../../../hooks/useTranslation'; import { ROUTE, useNavigate } from '../../../hooks/useNavigate'; import { usePermissionsContext } from '../../common/PermissionsContext'; import { useAppContext } from '../../../hooks/useAppContext'; +import { getImageBuildStatusReason } from '../../../utils/imageBuilds'; import DetailsPage from '../../DetailsPage/DetailsPage'; import DetailsPageActions from '../../DetailsPage/DetailsPageActions'; import DeleteImageBuildModal from '../DeleteImageBuildModal/DeleteImageBuildModal'; @@ -17,7 +19,6 @@ import ImageBuildYaml from './ImageBuildYaml'; import ImageBuildDetailsTab from './ImageBuildDetailsTab'; import ImageBuildExportsGallery from './ImageBuildExportsGallery'; import ImageBuildLogsTab from './ImageBuildLogsTab'; -import { hasImageBuildFailed } from '../../../utils/imageBuilds'; const imageBuildDetailsPermissions = [ { kind: RESOURCE.IMAGE_BUILD, verb: VERB.CREATE }, @@ -36,7 +37,7 @@ const ImageBuildDetailsPageContent = () => { const [isDeleteModalOpen, setIsDeleteModalOpen] = React.useState(); const { checkPermissions } = usePermissionsContext(); const [canCreate, canDelete] = checkPermissions(imageBuildDetailsPermissions); - const hasFailed = imageBuild ? hasImageBuildFailed(imageBuild) : false; + const buildReason = imageBuild ? getImageBuildStatusReason(imageBuild) : undefined; return ( { {canCreate && ( navigate({ route: ROUTE.IMAGE_BUILD_EDIT, postfix: imageBuildId })}> - {hasFailed ? t('Retry') : t('Duplicate')} + {buildReason === ImageBuildConditionReason.ImageBuildConditionReasonFailed + ? t('Retry') + : t('Duplicate')} )} {canDelete && ( diff --git a/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.tsx b/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.tsx index 0d5cb657..431c83c0 100644 --- a/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.tsx +++ b/libs/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.tsx @@ -22,14 +22,13 @@ import { ToolbarItem, } from '@patternfly/react-core'; -import { ExportFormatType } from '@flightctl/types/imagebuilder'; +import { ExportFormatType, ImageBuildConditionReason, ImageExportConditionReason } from '@flightctl/types/imagebuilder'; import { useTranslation } from '../../../hooks/useTranslation'; import { - hasImageBuildFailed, - isImageBuildComplete, - isImageExportFailed, - shouldHaveImageBuildLogs, - shouldHaveImageExportLogs, + getImageBuildStatusReason, + getImageExportStatusReason, + isImageBuildActiveReason, + isImageExportActiveReason, } from '../../../utils/imageBuilds'; import { ImageBuildWithExports } from '../../../types/extraTypes'; import { LogResourceType, useImageBuildLogs } from '../useImageBuildLogs'; @@ -41,7 +40,7 @@ type LogEntity = { id: string; label: string; isActive: boolean; - isFailed: boolean; + status: ImageBuildConditionReason | ImageExportConditionReason; }; const getExportFormatText = (t: TFunction, format: ExportFormatType) => { @@ -57,19 +56,29 @@ const getExportFormatText = (t: TFunction, format: ExportFormatType) => { } }; -const ImageBuildAndExportLogStatus = ({ isActive, isFailed }: { isActive: boolean; isFailed: boolean }) => { +const ImageBuildAndExportLogStatus = ({ + status, +}: { + status: ImageBuildConditionReason | ImageExportConditionReason; +}) => { const { t } = useTranslation(); let label: string; let level: StatusLevel; - if (isFailed) { + if (status === ImageBuildConditionReason.ImageBuildConditionReasonCompleted) { + level = 'success'; + label = t('Completed'); + } else if (status === ImageBuildConditionReason.ImageBuildConditionReasonFailed) { level = 'danger'; label = t('Failed'); - } else if (isActive) { + } else if ( + status === ImageBuildConditionReason.ImageBuildConditionReasonCanceling || + status === ImageBuildConditionReason.ImageBuildConditionReasonCanceled + ) { + level = 'warning'; + label = t('Canceled'); + } else { level = 'info'; label = t('In progress'); - } else { - level = 'success'; - label = t('Completed'); } return ; }; @@ -82,16 +91,18 @@ const ImageBuildLogsTab = ({ imageBuild }: { imageBuild: ImageBuildWithExports } const { selectableEntities, hasExports } = React.useMemo(() => { const entities: LogEntity[] = []; const buildName = imageBuild.metadata.name as string; + + const buildReason = getImageBuildStatusReason(imageBuild); entities.push({ type: LogResourceType.BUILD, id: buildName, label: buildName, - isActive: shouldHaveImageBuildLogs(imageBuild), - isFailed: hasImageBuildFailed(imageBuild), + isActive: isImageBuildActiveReason(buildReason), + status: buildReason, }); // ImageExports can only exist once the ImageBuild is complete - if (!isImageBuildComplete(imageBuild)) { + if (buildReason !== ImageBuildConditionReason.ImageBuildConditionReasonCompleted) { return { selectableEntities: entities, availableExportFormats: [] as ExportFormatType[], failedExportsCount: 0 }; } @@ -99,14 +110,14 @@ const ImageBuildLogsTab = ({ imageBuild }: { imageBuild: ImageBuildWithExports } imageBuild.imageExports.forEach((ie) => { const format = ie?.spec.format; if (format) { - const isFailed = isImageExportFailed(ie); + const exportReason = getImageExportStatusReason(ie); hasExports = true; entities.push({ type: LogResourceType.EXPORT, id: ie.metadata.name as string, label: getExportFormatText(t, format), - isActive: shouldHaveImageExportLogs(ie), - isFailed, + isActive: isImageExportActiveReason(exportReason), + status: exportReason, }); } }); @@ -237,7 +248,7 @@ const ImageBuildLogsTab = ({ imageBuild }: { imageBuild: ImageBuildWithExports } - + diff --git a/libs/ui-components/src/components/ImageBuilds/ImageBuildRow.tsx b/libs/ui-components/src/components/ImageBuilds/ImageBuildRow.tsx index 5561db7f..567032bb 100644 --- a/libs/ui-components/src/components/ImageBuilds/ImageBuildRow.tsx +++ b/libs/ui-components/src/components/ImageBuilds/ImageBuildRow.tsx @@ -3,15 +3,15 @@ import { Button, Content, Flex, FlexItem, Icon, Stack, StackItem, Title } from ' import { ActionsColumn, ExpandableRowContent, IAction, OnSelect, Tbody, Td, Tr } from '@patternfly/react-table'; import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/js/icons/exclamation-circle-icon'; -import { ImageBuild } from '@flightctl/types/imagebuilder'; +import { ImageBuild, ImageBuildConditionReason } from '@flightctl/types/imagebuilder'; import { ImageBuildWithExports } from '../../types/extraTypes'; import { useTranslation } from '../../hooks/useTranslation'; import { ROUTE, useNavigate } from '../../hooks/useNavigate'; -import { getImageBuildImage, hasImageBuildFailed } from '../../utils/imageBuilds'; +import { getImageBuildImage, getImageBuildStatusReason } from '../../utils/imageBuilds'; import { getDateDisplay } from '../../utils/dates'; import ResourceLink from '../common/ResourceLink'; -import { ImageBuildStatusDisplay } from './ImageBuildAndExportStatus'; import ImageBuildExportsGallery from './ImageBuildDetails/ImageBuildExportsGallery'; +import { ImageBuildStatusDisplay } from './ImageBuildAndExportStatus'; type ImageBuildRowProps = { imageBuild: ImageBuildWithExports; @@ -37,6 +37,7 @@ const ImageBuildRow = ({ const [isExpanded, setIsExpanded] = React.useState(false); const imageBuildName = imageBuild.metadata.name || ''; + const buildReason = getImageBuildStatusReason(imageBuild); const actions: IAction[] = [ { @@ -48,7 +49,7 @@ const ImageBuildRow = ({ ]; actions.push({ - title: hasImageBuildFailed(imageBuild) ? t('Retry') : t('Duplicate'), + title: buildReason === ImageBuildConditionReason.ImageBuildConditionReasonFailed ? t('Retry') : t('Duplicate'), onClick: () => { navigate({ route: ROUTE.IMAGE_BUILD_EDIT, postfix: imageBuildName }); }, @@ -64,8 +65,6 @@ const ImageBuildRow = ({ const sourceImage = getImageBuildImage(imageBuild.spec.source); const destinationImage = getImageBuildImage(imageBuild.spec.destination); - const hasError = hasImageBuildFailed(imageBuild); - return ( @@ -108,7 +107,7 @@ const ImageBuildRow = ({ {t('Build information')} - {hasError && ( + {buildReason === ImageBuildConditionReason.ImageBuildConditionReasonFailed && ( diff --git a/libs/ui-components/src/components/ImageBuilds/ImageExportCards.tsx b/libs/ui-components/src/components/ImageBuilds/ImageExportCards.tsx index f0b75fe7..0764fe92 100644 --- a/libs/ui-components/src/components/ImageBuilds/ImageExportCards.tsx +++ b/libs/ui-components/src/components/ImageBuilds/ImageExportCards.tsx @@ -21,13 +21,8 @@ import { CloudSecurityIcon } from '@patternfly/react-icons/dist/js/icons/cloud-s import { ServerGroupIcon } from '@patternfly/react-icons/dist/js/icons/server-group-icon'; import { BuilderImageIcon } from '@patternfly/react-icons/dist/js/icons/builder-image-icon'; -import { ExportFormatType, ImageExport } from '@flightctl/types/imagebuilder'; -import { - getExportFormatDescription, - getExportFormatLabel, - isImageExportCompleted, - isImageExportFailed, -} from '../../utils/imageBuilds'; +import { ExportFormatType, ImageExport, ImageExportConditionReason } from '@flightctl/types/imagebuilder'; +import { getExportFormatDescription, getExportFormatLabel, getImageExportStatusReason } from '../../utils/imageBuilds'; import { getDateDisplay } from '../../utils/dates'; import { useTranslation } from '../../hooks/useTranslation'; import { ImageExportStatusDisplay } from './ImageBuildAndExportStatus'; @@ -115,8 +110,8 @@ export const ViewImageBuildExportCard = ({ } = useAppContext(); const routerNavigate = useRouterNavigate(); const exists = !!imageExport; - const failedExport = exists && isImageExportFailed(imageExport); - const completedExport = exists && isImageExportCompleted(imageExport); + + const exportReason = exists ? getImageExportStatusReason(imageExport) : undefined; const title = getExportFormatLabel(t, format); const description = getExportFormatDescription(t, format); @@ -141,7 +136,11 @@ export const ViewImageBuildExportCard = ({ )} @@ -159,7 +158,7 @@ export const ViewImageBuildExportCard = ({ - {failedExport && ( + {exportReason === ImageExportConditionReason.ImageExportConditionReasonFailed && (