From ba019b8cf7af74c89befb623e922f59caaa9115e Mon Sep 17 00:00:00 2001 From: Jeff Puzzo Date: Wed, 22 Jan 2025 14:18:39 -0500 Subject: [PATCH] [MTV-1686] Simplify/update migration plan status cell (part 2) Signed-off-by: Jeff Puzzo --- .../en/plugin__forklift-console-plugin.json | 5 + .../src/__mocks__/plans.ts | 230 ++++++++++++++++++ .../src/__mocks__/react-i18next.tsx | 15 +- .../actions/PlanActionsDropdownItems.tsx | 86 ++++--- .../modules/Plans/utils/constants/index.ts | 1 + .../utils/constants/planSummaryStatuses.ts | 11 + .../utils/helpers/getPlanSummaryStatus.ts | 74 ++++++ .../src/modules/Plans/utils/helpers/index.ts | 1 + .../Plans/utils/types/PlanCondition.ts | 70 ++++++ .../Plans/utils/types/PlanSummaryStatus.ts | 16 ++ .../src/modules/Plans/utils/types/index.ts | 1 + .../Plans/views/list/PlansListPage.tsx | 6 +- .../Plans/views/list/components/PlanCell.tsx | 8 +- .../PlanStatusCell/PlanStatusCell.style.scss | 3 + .../{ => PlanStatusCell}/PlanStatusCell.tsx | 101 ++++---- .../PlanStatusCell/PlanStatusCellLabel.tsx | 35 +++ .../list/components/PlanStatusCell/index.ts | 1 + .../list/components/PlanStatusVmCount.tsx | 10 +- .../Plans/views/list/components/VMsCell.tsx | 4 +- .../__tests__/PlanStatusCell.test.tsx | 230 ++++++++++++++++++ 20 files changed, 811 insertions(+), 97 deletions(-) create mode 100644 packages/forklift-console-plugin/src/__mocks__/plans.ts create mode 100644 packages/forklift-console-plugin/src/modules/Plans/utils/constants/planSummaryStatuses.ts create mode 100644 packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanSummaryStatus.ts create mode 100644 packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanCondition.ts create mode 100644 packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanSummaryStatus.ts create mode 100644 packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell/PlanStatusCell.style.scss rename packages/forklift-console-plugin/src/modules/Plans/views/list/components/{ => PlanStatusCell}/PlanStatusCell.tsx (59%) create mode 100644 packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell/PlanStatusCellLabel.tsx create mode 100644 packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell/index.ts create mode 100644 packages/forklift-console-plugin/src/modules/Plans/views/list/components/__tests__/PlanStatusCell.test.tsx diff --git a/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json b/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json index 65463ecf8..009c98ac8 100644 --- a/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json +++ b/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json @@ -20,6 +20,7 @@ "31 days": "31 days", "404: Not Found": "404: Not Found", "7 days": "7 days", + "99+ VMs": "99+ VMs", "A CA certificate to be trusted when connecting to Openshift API endpoint. Ensure the CA certificate format is in a PEM encoded X.509 format. To use a CA certificate, drag the file to the text box or browse for it. To use the system CA certificate, leave the field empty.": "A CA certificate to be trusted when connecting to Openshift API endpoint. Ensure the CA certificate format is in a PEM encoded X.509 format. To use a CA certificate, drag the file to the text box or browse for it. To use the system CA certificate, leave the field empty.", "A CA certificate to be trusted when connecting to the ESXi API endpoint. Ensure the CA certificate format is in a PEM encoded X.509 format. To use a CA certificate, drag the file to the text box or browse for it. To use the system CA certificate, leave the field empty.": "A CA certificate to be trusted when connecting to the ESXi API endpoint. Ensure the CA certificate format is in a PEM encoded X.509 format. To use a CA certificate, drag the file to the text box or browse for it. To use the system CA certificate, leave the field empty.", "A CA certificate to be trusted when connecting to the OpenStack Identity (Keystone) endpoint. Ensure the CA certificate format is valid. To use a CA certificate, drag the file to the text box or browse for it. To use the system CA certificate, leave the field empty.": "A CA certificate to be trusted when connecting to the OpenStack Identity (Keystone) endpoint. Ensure the CA certificate format is valid. To use a CA certificate, drag the file to the text box or browse for it. To use the system CA certificate, leave the field empty.", @@ -72,6 +73,7 @@ "Cancel virtual machines from migration plan": "Cancel virtual machines from migration plan", "Canceled": "Canceled", "Cannot retrieve certificate": "Cannot retrieve certificate", + "Cannot start": "Cannot start", "Category": "Category", "Certificate change detected": "Certificate change detected", "choose a different mapping for your migration plan": "choose a different mapping for your migration plan", @@ -85,6 +87,7 @@ "Click to unselect.": "Click to unselect.", "Cluster": "Cluster", "Clusters": "Clusters", + "Complete": "Complete", "Completed {{completed}} of {{total}} {{name}} tasks": "Completed {{completed}} of {{total}} {{name}} tasks", "Completed at": "Completed at", "Concern": "Concern", @@ -217,6 +220,7 @@ "If true, the provider's TLS certificate won't be validated.": "If true, the provider's TLS certificate won't be validated.", "Image": "Image", "In warm migration, the VM disks are copied incrementally using changed block tracking (CBT) snapshots.\n The snapshots are created at one-hour intervals by default.\n You can change the snapshot interval by updating the forklift-controller deployment.": "In warm migration, the VM disks are copied incrementally using changed block tracking (CBT) snapshots.\n The snapshots are created at one-hour intervals by default.\n You can change the snapshot interval by updating the forklift-controller deployment.", + "Incomplete": "Incomplete", "Incomplete mapping": "Incomplete mapping", "Information concerns": "Information concerns", "Invalid name, name should be unique in this namespace and a valid Kubernetes name": "Invalid name, name should be unique in this namespace and a valid Kubernetes name", @@ -396,6 +400,7 @@ "Providers default": "Providers default", "Providers for virtualization": "Providers for virtualization", "Ready": "Ready", + "Ready to start": "Ready to start", "Reason": "Reason", "Red Hat Virtualization Manager UI": "Red Hat Virtualization Manager UI", "Region": "Region", diff --git a/packages/forklift-console-plugin/src/__mocks__/plans.ts b/packages/forklift-console-plugin/src/__mocks__/plans.ts new file mode 100644 index 000000000..6fded85d8 --- /dev/null +++ b/packages/forklift-console-plugin/src/__mocks__/plans.ts @@ -0,0 +1,230 @@ +/* eslint-disable @cspell/spellchecker */ +import { PlanData } from 'src/modules/Plans/utils'; +import { + PlanConditionCategory, + PlanConditionType, +} from 'src/modules/Plans/utils/types/PlanCondition'; + +import { V1beta1Migration, V1beta1MigrationStatusVms } from '@kubev2v/types'; + +export const mockFailedVm: V1beta1MigrationStatusVms = { + started: '2025-01-14T06:37:20Z', + pipeline: [], + error: { + phase: 'CopyDisks', + reasons: [ + 'Unable to process data: Unable to transfer source data to scratch space: failed to pull image: unexpected EOF', + ], + }, + completed: '2025-01-14T08:38:41Z', + name: 'winweb01', + conditions: [ + { + category: PlanConditionCategory.Advisory, + durable: true, + lastTransitionTime: '2025-01-14T08:38:38Z', + message: 'The VM migration has FAILED.', + status: 'True', + type: PlanConditionType.Failed, + }, + ], + restorePowerState: 'On', + id: '616e985a-170f-471b-b538-c39f8d848559', + namespace: 'vmimported', + phase: 'Completed', +}; + +export const mockSucceededVm: V1beta1MigrationStatusVms = { + started: '2025-01-07T18:42:50Z', + pipeline: [], + completed: '2025-01-07T18:54:14Z', + name: 'haproxy-cf5d4', + luks: {}, + conditions: [ + { + category: PlanConditionCategory.Advisory, + durable: true, + lastTransitionTime: '2025-01-07T18:54:14Z', + message: 'The VM migration has SUCCEEDED.', + status: 'True', + type: PlanConditionType.Succeeded, + }, + ], + restorePowerState: 'Off', + id: 'vm-146146', + phase: 'Completed', +}; + +export const mockPlanData: PlanData = { + obj: { + kind: 'Plan', + apiVersion: 'forklift.konveyor.io/v1beta1', + metadata: { + annotations: { populatorLabels: 'True' }, + creationTimestamp: '2025-01-14T15:29:38Z', + generation: 2, + managedFields: [], + name: 'testing', + namespace: 'openshift-mtv', + resourceVersion: '20558300', + uid: '342f3ad5-eac9-4f89-8f1e-a37bee91f16a', + }, + spec: { + map: { + network: { + apiVersion: 'forklift.konveyor.io/v1beta1', + kind: 'NetworkMap', + name: 'vmware-wd2q7', + namespace: 'openshift-mtv', + uid: '2fe5e6cc-3a63-4034-8828-a7d38ac8f3c2', + }, + storage: { + apiVersion: 'forklift.konveyor.io/v1beta1', + kind: 'StorageMap', + name: 'vmware-k2bmn', + namespace: 'openshift-mtv', + uid: '7cf7f014-7d5e-47a4-99a4-57c14fdceeb6', + }, + }, + provider: { + destination: { + apiVersion: 'forklift.konveyor.io/v1beta1', + kind: 'Provider', + name: 'host', + namespace: 'openshift-mtv', + uid: 'adf42f04-b5dd-41a6-8626-642686c0145a', + }, + source: { + apiVersion: 'forklift.konveyor.io/v1beta1', + kind: 'Provider', + name: 'vmware', + namespace: 'openshift-mtv', + uid: 'a14fc19a-0332-4cf2-bd91-6ceb3a7e8f47', + }, + }, + targetNamespace: 'openshift-mtv', + vms: [{ id: 'vm-146147', name: 'database-cf5d4' }], + warm: true, + }, + status: { + conditions: [ + { + category: PlanConditionCategory.Critical, + items: [" id:vm-146147 name:'database-cf5d4' "], + lastTransitionTime: '2025-01-14T15:29:38Z', + message: 'Target VM already exists.', + reason: 'NotUnique', + status: 'True', + type: PlanConditionType.VMAlreadyExists, + }, + ], + migration: {}, + observedGeneration: 2, + }, + }, + permissions: { canCreate: true, canPatch: true, canDelete: true, canGet: true, loading: false }, +}; + +export const mockMigration: V1beta1Migration = { + kind: 'Migration', + apiVersion: 'forklift.konveyor.io/v1beta1', + metadata: { + generateName: 'test1-', + resourceVersion: '28653414', + name: 'test1-j5w4l', + uid: '193b4e36-6b14-4a09-8675-6d587019a747', + creationTimestamp: '2025-01-14T06:44:23Z', + generation: 1, + managedFields: [], + namespace: 'openshift-mtv', + ownerReferences: [], + }, + spec: { + plan: { + name: 'test1', + namespace: 'openshift-mtv', + uid: 'fd4b5f8b-d28c-4b51-a663-c3e011bfb666', + }, + }, + status: { + conditions: [ + { + category: PlanConditionCategory.Required, + lastTransitionTime: '2025-01-14T06:44:23Z', + message: 'The migration is ready.', + status: 'True', + type: PlanConditionType.Ready, + }, + { + category: PlanConditionCategory.Advisory, + lastTransitionTime: '2025-01-14T06:44:23Z', + message: 'The migration is RUNNING.', + status: 'True', + type: PlanConditionType.Running, + }, + ], + started: '2025-01-14T06:44:23Z', + vms: [ + { + id: '637c61c3-8501-405e-a35a-873520085a6b', + luks: {}, + name: 'database', + namespace: 'vmimported', + phase: 'CopyDisks', + pipeline: [ + { + completed: '2025-01-14T06:44:47Z', + description: 'Initialize migration.', + name: 'Initialize', + phase: 'Completed', + progress: { + completed: 0, + total: 1, + }, + started: '2025-01-14T06:44:23Z', + }, + { + annotations: { + unit: 'MB', + }, + description: 'Transfer disks.', + name: 'DiskTransfer', + phase: 'Running', + progress: { + completed: 0, + total: 16384, + }, + started: '2025-01-14T06:44:50Z', + tasks: [ + { + annotations: { + unit: 'MB', + }, + name: 'vmimported/database', + phase: 'Running', + progress: { + completed: 0, + total: 16384, + }, + reason: + 'Error; Unable to connect to http data source: expected status code 200, got 404. Status: 404 Not Found', + started: '2025-01-14T06:45:26Z', + }, + ], + }, + { + description: 'Create VM.', + name: 'VirtualMachineCreation', + phase: 'Pending', + progress: { + completed: 0, + total: 1, + }, + }, + ], + restorePowerState: 'On', + started: '2025-01-14T06:44:23Z', + }, + ], + }, +}; diff --git a/packages/forklift-console-plugin/src/__mocks__/react-i18next.tsx b/packages/forklift-console-plugin/src/__mocks__/react-i18next.tsx index efcdeb5f4..3ceba81eb 100644 --- a/packages/forklift-console-plugin/src/__mocks__/react-i18next.tsx +++ b/packages/forklift-console-plugin/src/__mocks__/react-i18next.tsx @@ -3,10 +3,21 @@ import React from 'react'; /** * Mock translation utility * - * @returns {{ t: (k: string) => string; }} + * @returns {{ t: (k: string): string; }} */ export const useTranslation = () => ({ - t: (k: string) => k, + t: (key, options) => { + // Resolve interpolated strings + if (options?.interpolation || options?.total || options?.count) { + const interpolatedString = key.replace(/{{(\w+)}}/g, (match, variable) => { + return options[variable] || options.interpolation?.[variable] || match; + }); + + return interpolatedString; + } else { + return key; + } + }, i18n: { resolvedLanguage: 'en', }, diff --git a/packages/forklift-console-plugin/src/modules/Plans/actions/PlanActionsDropdownItems.tsx b/packages/forklift-console-plugin/src/modules/Plans/actions/PlanActionsDropdownItems.tsx index 541d95c15..4e2c799bb 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/actions/PlanActionsDropdownItems.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/actions/PlanActionsDropdownItems.tsx @@ -7,21 +7,13 @@ import { useForkliftTranslation } from 'src/utils/i18n'; import { PlanModel, PlanModelRef } from '@kubev2v/types'; import { DropdownItem } from '@patternfly/react-core/deprecated'; -import { - ArchiveModal, - DuplicateModal, - PlanCutoverMigrationModal, - PlanDeleteModal, - PlanStartMigrationModal, -} from '../modals'; +import { ArchiveModal, DuplicateModal, PlanDeleteModal, PlanStartMigrationModal } from '../modals'; import { canPlanReStart, canPlanStart, - getPlanPhase, - isPlanArchived, - isPlanExecuting, + getPlanSummaryStatus, PlanData, - PlanPhase, + PlanSummaryStatus, } from '../utils'; export const PlanActionsDropdownItems = ({ data }: PlanActionsDropdownItemsProps) => { @@ -36,12 +28,10 @@ export const PlanActionsDropdownItems = ({ data }: PlanActionsDropdownItemsProps namespace: plan?.metadata?.namespace, }); - const phase = getPlanPhase(data); - + const status = getPlanSummaryStatus(data); const canStart = canPlanStart(plan); const canReStart = canPlanReStart(plan); - const isWarmAndExecuting = plan?.spec?.warm && isPlanExecuting(plan); - const isArchived = isPlanArchived(plan); + const isPlanValidating = !status; const buttonStartLabel = canReStart ? t('Restart migration') : t('Start migration'); @@ -51,10 +41,6 @@ export const PlanActionsDropdownItems = ({ data }: PlanActionsDropdownItemsProps ); }; - const onClickPlanCutover = () => { - showModal(); - }; - const onClickDuplicate = () => { showModal(); }; @@ -67,37 +53,69 @@ export const PlanActionsDropdownItems = ({ data }: PlanActionsDropdownItemsProps showModal(); }; - return [ - - {t('Edit Plan')} - , + const startActionDescription = React.useMemo(() => { + if (isPlanValidating) { + return t('The plan is being validated'); + } + + switch (status) { + case PlanSummaryStatus.Archived: + return t('Archived plans cannot be started'); + case PlanSummaryStatus.Complete: + return t('All VMs were migrated'); + case PlanSummaryStatus.Running: + return t('The plan is currently in progress'); + case PlanSummaryStatus.CannotStart: + return t('The plan cannot be started'); + } + }, [status, isPlanValidating]); + + const duplicateActionDescription = React.useMemo(() => { + if (isPlanValidating) { + return t('The plan is being validated'); + } + + if (status === PlanSummaryStatus.CannotStart) { + return t('The plan cannot be duplicated'); + } + }, [status, isPlanValidating]); - + return [ + {buttonStartLabel} , - - {t('Cutover')} - , + {t('Edit Plan')} + , {t('Duplicate Plan')} , {t('Archive Plan')} , diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/constants/index.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/constants/index.ts index 20851eb72..d63da5536 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/utils/constants/index.ts +++ b/packages/forklift-console-plugin/src/modules/Plans/utils/constants/index.ts @@ -1,3 +1,4 @@ // @index(['./*', /style/g], f => `export * from '${f.path}';`) export * from './planPhases'; +export * from './planSummaryStatuses'; // @endindex diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/constants/planSummaryStatuses.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/constants/planSummaryStatuses.ts new file mode 100644 index 000000000..030ecf799 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/utils/constants/planSummaryStatuses.ts @@ -0,0 +1,11 @@ +import { PlanSummaryStatus } from '../types'; + +export const planSummaryStatuses: { id: PlanSummaryStatus; label: PlanSummaryStatus }[] = [ + { id: PlanSummaryStatus.Archived, label: PlanSummaryStatus.Archived }, + { id: PlanSummaryStatus.Canceled, label: PlanSummaryStatus.Canceled }, + { id: PlanSummaryStatus.CannotStart, label: PlanSummaryStatus.CannotStart }, + { id: PlanSummaryStatus.Complete, label: PlanSummaryStatus.Complete }, + { id: PlanSummaryStatus.Incomplete, label: PlanSummaryStatus.Incomplete }, + { id: PlanSummaryStatus.ReadyToStart, label: PlanSummaryStatus.ReadyToStart }, + { id: PlanSummaryStatus.Running, label: PlanSummaryStatus.Running }, +]; diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanSummaryStatus.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanSummaryStatus.ts new file mode 100644 index 000000000..e7cd1c4ea --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/getPlanSummaryStatus.ts @@ -0,0 +1,74 @@ +import { PlanData, PlanSummaryStatus } from '../types'; +import { + PlanConditionCategory, + PlanConditionStatus, + PlanConditionType, +} from '../types/PlanCondition'; + +export const getPlanSummaryStatus = (data: PlanData): PlanSummaryStatus => { + const plan = data?.obj; + const conditionTypes = plan?.status?.conditions?.reduce((acc, condition) => { + if (condition.status === PlanConditionStatus.True) { + acc.push(condition.type); + } + + return acc; + }, []); + + if (!conditionTypes?.length) { + return; + } + + const isArchiving = plan?.spec?.archived && !conditionTypes.includes(PlanConditionType.Archived); + + // Archived + if (isArchiving || conditionTypes.includes(PlanConditionType.Archived)) { + return PlanSummaryStatus.Archived; + } + + // Canceled + if (conditionTypes.includes(PlanConditionType.Canceled)) { + return PlanSummaryStatus.Canceled; + } + + // Running + if ( + conditionTypes.includes(PlanConditionType.Executing) || + conditionTypes.includes(PlanConditionType.Running) + ) { + return PlanSummaryStatus.Running; + } + + // Incomplete + const vmError = plan?.status?.migration?.vms?.find((vm) => vm?.error); + + if (conditionTypes.includes(PlanConditionType.Failed) || vmError) { + return PlanSummaryStatus.Incomplete; + } + + // Cannot start + const isCritical = plan?.status?.conditions?.find( + (condition) => + condition.category === PlanConditionCategory.Critical && + condition.status === PlanConditionStatus.True, + ); + const isWarn = plan?.status?.conditions?.find( + (condition) => + condition.category === PlanConditionCategory.Warn && + condition.status === PlanConditionStatus.True, + ); + + if (isCritical || isWarn) { + return PlanSummaryStatus.CannotStart; + } + + // Complete + if (conditionTypes.includes(PlanConditionType.Succeeded)) { + return PlanSummaryStatus.Complete; + } + + // Ready to start + if (conditionTypes.includes(PlanConditionType.Ready)) { + return PlanSummaryStatus.ReadyToStart; + } +}; diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/index.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/index.ts index f499b37f3..82c590a02 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/index.ts +++ b/packages/forklift-console-plugin/src/modules/Plans/utils/helpers/index.ts @@ -3,4 +3,5 @@ export * from './getMigrationPhase'; export * from './getMigrationVmsCounts'; export * from './getPlanPhase'; export * from './getPlanProgressVariant'; +export * from './getPlanSummaryStatus'; // @endindex diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanCondition.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanCondition.ts new file mode 100644 index 000000000..81067b3e1 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanCondition.ts @@ -0,0 +1,70 @@ +// Reference: https://github.com/kubev2v/forklift/blob/3113e867374c560ffbdcd34ef1627cb867893ba5/pkg/controller/plan/validation.go + +export enum PlanConditionType { + Ready = 'Ready', + NotReady = 'NotReady', + WarmMigrationNotReady = 'WarmMigrationNotReady', + NamespaceNotValid = 'NamespaceNotValid', + TransferNetNotValid = 'TransferNetworkNotValid', + NetRefNotValid = 'NetworkMapRefNotValid', + NetMapNotReady = 'NetworkMapNotReady', + DsMapNotReady = 'StorageMapNotReady', + DsRefNotValid = 'StorageRefNotValid', + VMRefNotValid = 'VMRefNotValid', + VMNotFound = 'VMNotFound', + VMAlreadyExists = 'VMAlreadyExists', + VMNetworksNotMapped = 'VMNetworksNotMapped', + VMStorageNotMapped = 'VMStorageNotMapped', + VMStorageNotSupported = 'VMStorageNotSupported', + VMMultiplePodNetworkMappings = 'VMMultiplePodNetworkMappings', + VMMissingGuestIPs = 'VMMissingGuestIPs', + VMMissingChangedBlockTracking = 'VMMissingChangedBlockTracking', + HostNotReady = 'HostNotReady', + DuplicateVM = 'DuplicateVM', + NameNotValid = 'TargetNameNotValid', + HookNotValid = 'HookNotValid', + HookNotReady = 'HookNotReady', + HookStepNotValid = 'HookStepNotValid', + Executing = 'Executing', + Succeeded = 'Succeeded', + Failed = 'Failed', + Canceled = 'Canceled', + Deleted = 'Deleted', + Paused = 'Paused', + Pending = 'Pending', + Running = 'Running', + Blocked = 'Blocked', + Archived = 'Archived', + unsupportedVersion = 'UnsupportedVersion', + VDDKInvalid = 'VDDKInvalid', + ValidatingVDDK = 'ValidatingVDDK', + VDDKInitImageNotReady = 'VDDKInitImageNotReady', + VDDKInitImageUnavailable = 'VDDKInitImageUnavailable', +} + +export enum PlanConditionCategory { + Required = 'Required', + Advisory = 'Advisory', + Critical = 'Critical', + Error = 'Error', + Warn = 'Warn', +} + +export enum PlanConditionReason { + NotSet = 'NotSet', + NotFound = 'NotFound', + NotUnique = 'NotUnique', + NotSupported = 'NotSupported', + Ambiguous = 'Ambiguous', + NotValid = 'NotValid', + Modified = 'Modified', + UserRequested = 'UserRequested', + InMaintenanceMode = 'InMaintenanceMode', + MissingGuestInfo = 'MissingGuestInformation', + MissingChangedBlockTracking = 'MissingChangedBlockTracking', +} + +export enum PlanConditionStatus { + True = 'True', + False = 'False', +} diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanSummaryStatus.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanSummaryStatus.ts new file mode 100644 index 000000000..a2be0cb89 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/utils/types/PlanSummaryStatus.ts @@ -0,0 +1,16 @@ +export enum PlanSummaryStatus { + // t('Archived') + Archived = 'Archived', + // t('Canceled') + Canceled = 'Canceled', + // t('Cannot start') + CannotStart = 'Cannot start', + // t('Complete') + Complete = 'Complete', + // t('Incomplete') + Incomplete = 'Incomplete', + // t('Ready to start') + ReadyToStart = 'Ready to start', + // t('Running') + Running = 'Running', +} diff --git a/packages/forklift-console-plugin/src/modules/Plans/utils/types/index.ts b/packages/forklift-console-plugin/src/modules/Plans/utils/types/index.ts index 10219d2fb..ede94e874 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/utils/types/index.ts +++ b/packages/forklift-console-plugin/src/modules/Plans/utils/types/index.ts @@ -2,4 +2,5 @@ export * from './MigrationPhase'; export * from './PlanData'; export * from './PlanPhase'; +export * from './PlanSummaryStatus'; // @endindex diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/list/PlansListPage.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/list/PlansListPage.tsx index ff932b781..49ee3dba2 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/views/list/PlansListPage.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/views/list/PlansListPage.tsx @@ -11,7 +11,7 @@ import { HelperText, HelperTextItem } from '@patternfly/react-core'; import { PlansAddButton } from '../../components'; import PlansEmptyState from '../../components/PlansEmptyState'; -import { getPlanPhase, PlanData, planPhases } from '../../utils'; +import { getPlanSummaryStatus, PlanData, planSummaryStatuses } from '../../utils'; import PlanRow from './PlanRow'; @@ -73,14 +73,14 @@ export const fieldsMetadataFactory: ResourceFieldFactory = (t) => [ }, { resourceFieldId: 'phase', - jsonPath: getPlanPhase, + jsonPath: getPlanSummaryStatus, label: t('Migration status'), isVisible: true, filter: { type: 'enum', primary: true, placeholderLabel: t('Migration status'), - values: planPhases, + values: planSummaryStatuses, }, sortable: true, }, diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanCell.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanCell.tsx index c760936cb..4b8529ac7 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanCell.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanCell.tsx @@ -19,24 +19,18 @@ export const PlanCell: React.FC = ({ data }) => { const { name, namespace } = obj?.metadata || {}; const warm = obj?.spec?.warm; - const archived = obj?.status?.conditions?.find((c) => c.type === 'Archived') !== undefined; if (warm) { labelColors.push('orange'); labels.push(t('Warm')); } - if (archived) { - labelColors.push('grey'); - labels.push(t('Archived')); - } - return ( diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell/PlanStatusCell.style.scss b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell/PlanStatusCell.style.scss new file mode 100644 index 000000000..dc5cf40cb --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell/PlanStatusCell.style.scss @@ -0,0 +1,3 @@ +.plan-status-cell-label-section { + width: 120px; +} diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell/PlanStatusCell.tsx similarity index 59% rename from packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell.tsx rename to packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell/PlanStatusCell.tsx index 0aba151a9..6afdc3df3 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell/PlanStatusCell.tsx @@ -3,21 +3,25 @@ import { usePlanMigration } from 'src/modules/Plans/hooks'; import { PlanStartMigrationModal } from 'src/modules/Plans/modals'; import { getMigrationVmsCounts, - getPlanPhase, + getPlanSummaryStatus, isPlanArchived, isPlanExecuting, - PlanPhase, + PlanSummaryStatus, } from 'src/modules/Plans/utils'; import { useModal } from 'src/modules/Providers/modals'; import { getResourceUrl } from 'src/modules/Providers/utils'; import { useForkliftTranslation } from 'src/utils/i18n'; import { PlanModel, PlanModelRef } from '@kubev2v/types'; -import { Button, Flex, FlexItem, Label, Spinner, Split, SplitItem } from '@patternfly/react-core'; +import { Button, Flex, FlexItem, Split, SplitItem } from '@patternfly/react-core'; import StartIcon from '@patternfly/react-icons/dist/esm/icons/play-icon'; -import { CellProps } from './CellProps'; -import { PlanStatusVmCount } from './PlanStatusVmCount'; +import { CellProps } from '../CellProps'; +import { PlanStatusVmCount } from '../PlanStatusVmCount'; + +import { PlanStatusCellLabel } from './PlanStatusCellLabel'; + +import './PlanStatusCell.style.scss'; type VmPipelineTask = { vmName: string; @@ -28,15 +32,10 @@ type VmPipelineTask = { export const PlanStatusCell: React.FC = ({ data }) => { const { t } = useForkliftTranslation(); const { showModal } = useModal(); - const plan = data?.obj; - const vms = plan?.spec?.vms; - const vmStatuses = plan?.status?.migration?.vms; + const plan = data?.obj; const [lastMigration] = usePlanMigration(plan); - const isWarmAndExecuting = plan.spec?.warm && isPlanExecuting(plan); - const isWaitingForCutover = isWarmAndExecuting && !isPlanArchived(plan); - const vmPipelineTasks = lastMigration?.status.vms?.reduce( (acc: VmPipelineTask[], migrationVm) => { migrationVm.pipeline.forEach((pipelineStep) => { @@ -48,9 +47,11 @@ export const PlanStatusCell: React.FC = ({ data }) => { [], ); - const phase = getPlanPhase(data); - const isPlanLoading = - !isWaitingForCutover && (phase === PlanPhase.Running || phase === PlanPhase.Archiving); + const vmStatuses = lastMigration?.status.vms; + const planStatus = getPlanSummaryStatus(data); + const isWarmAndExecuting = plan.spec?.warm && isPlanExecuting(plan); + const isWaitingForCutover = isWarmAndExecuting && !isPlanArchived(plan); + const isPlanLoading = !isWaitingForCutover && planStatus === PlanSummaryStatus.Running; const planURL = getResourceUrl({ reference: PlanModelRef, name: plan?.metadata?.name, @@ -62,7 +63,7 @@ export const PlanStatusCell: React.FC = ({ data }) => { // Could possibly use a querystring to dictate a table filter for the list of VMs. const vmCountLinkPath = `${planURL}/vms`; - if (phase === PlanPhase.Ready) { + if (planStatus === PlanSummaryStatus.ReadyToStart) { return (