From f9dad67e22b4922b8bd0c153bede33c67e032571 Mon Sep 17 00:00:00 2001 From: Alexander Wels Date: Mon, 28 Oct 2024 08:05:51 -0500 Subject: [PATCH] Behave the same as 1.8.4 on a new migration plan (#1526) The loading behavior was modified in 1.8.5 and the grid loading was not behaving the same as 1.8.4 when creating a new direct volume migration plan. The spinner was not covering the grid and the grid was showing the empty state. Instead revert to covering the entire grid. And only show the validating spinner when the grid selection changed. Signed-off-by: Alexander Wels --- .../PlansPage/components/PlanStatusIcon.tsx | 22 ++++++++-- .../components/Wizard/VolumesForm.tsx | 41 ++++++++++++++++++- .../components/Wizard/VolumesTable.tsx | 23 +++++++---- .../components/Wizard/WizardComponent.tsx | 4 +- src/app/home/pages/PlansPage/helpers.ts | 2 + src/app/plan/duck/helpers.ts | 6 ++- src/app/plan/duck/sagas.ts | 20 ++++++++- src/app/plan/duck/types.ts | 1 + 8 files changed, 103 insertions(+), 16 deletions(-) diff --git a/src/app/home/pages/PlansPage/components/PlanStatusIcon.tsx b/src/app/home/pages/PlansPage/components/PlanStatusIcon.tsx index f90ffc209..90f7c27d4 100644 --- a/src/app/home/pages/PlansPage/components/PlanStatusIcon.tsx +++ b/src/app/home/pages/PlansPage/components/PlanStatusIcon.tsx @@ -1,16 +1,15 @@ import React from 'react'; -import WarningTriangleIcon from '@patternfly/react-icons/dist/js/icons/warning-triangle-icon'; -import OutlinedCircleIcon from '@patternfly/react-icons/dist/js/icons/outlined-circle-icon'; import ExclamationCircleIcon from '@patternfly/react-icons/dist/js/icons/exclamation-circle-icon'; +import OutlinedCircleIcon from '@patternfly/react-icons/dist/js/icons/outlined-circle-icon'; import ResourcesAlmostEmptyIcon from '@patternfly/react-icons/dist/js/icons/resources-almost-empty-icon'; import ResourcesFullIcon from '@patternfly/react-icons/dist/js/icons/resources-full-icon'; import { Popover, PopoverPosition } from '@patternfly/react-core'; import { Spinner } from '@patternfly/react-core'; -import { ICondition, IPlan } from '../../../../plan/duck/types'; import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/js/icons/exclamation-triangle-icon'; +import { IPlan } from '../../../../plan/duck/types'; const styles = require('./PlanStatus.module').default; interface IProps { @@ -29,6 +28,7 @@ const PlanStatusIcon: React.FunctionComponent = ({ plan }) => { hasConflictCondition = null, latestIsFailed = null, hasCriticalCondition = null, + hasNotSupportedCondition = null, hasWarnCondition = null, hasDVMBlockedCondition = null, } = plan?.PlanStatus; @@ -43,7 +43,21 @@ const PlanStatusIcon: React.FunctionComponent = ({ plan }) => { return ( + + + + + ); + } else if (hasNotSupportedCondition) { + return ( + = (props) => { }; }); } + const selectedPVs = mappedPVs + .filter((pv) => pv.selection.action !== 'skip') + .map((pv) => pv.name); + setFieldValue('selectedPVs', selectedPVs); + //Set initial PVs from pv discovery setFieldValue('persistentVolumes', mappedPVs); } @@ -101,6 +114,32 @@ const VolumesForm: React.FunctionComponent = (props) => { ); } + if ( + !planState.currentPlan?.spec.persistentVolumes || + planState.currentPlan.spec.persistentVolumes?.length == 0 + ) { + return ( + + +
+ +
+ + Discovering persistent volumes attached to source projects... + +
+
+ ); + } + if (planState.currentPlanStatus.state === 'Critical') { + return ( + + + + + + ); + } return ( = ({ : 'Choose to move or copy persistent volumes associated with selected namespaces.'} - {planState.currentPlanStatus.state === 'Critical' && !planState.currentPlan.spec.refresh ? ( + {planState.currentPlanStatus?.state === 'Warn' && !planState.currentPlan?.spec.refresh ? ( + + + + + + ) : null} + {planState.currentPlanStatus?.state === 'Critical' && + !planState.currentPlan?.spec.refresh ? ( ) : null} - {planState.isFetchingPVResources || - planState.isPollingStatus || - planState.currentPlanStatus.state === 'Pending' || - planState.currentPlan.spec.refresh ? ( + {planState.currentPlan?.spec.persistentVolumes?.length > 0 && + (planState.isFetchingPVResources || + planState.isPollingStatus || + planState.currentPlanStatus.state === 'Pending' || + planState.currentPlan.spec.refresh) ? ( @@ -481,7 +490,7 @@ const VolumesTable: React.FunctionComponent = ({ ) : null} - {isSCC && values.persistentVolumes.length === 0 ? ( + {isSCC && values.persistentVolumes.length === 0 && planState.currentPlan?.spec.refresh ? ( = ({ ) : null} - {isSCC && storageClasses.length < 2 ? ( + {isSCC && storageClasses.length < 2 && planState.currentPlan?.spec.refresh ? ( { const onMove: WizardStepFunctionType = ({ id, name }, { prevId, prevName }) => { dispatch(PlanActions.pvUpdatePollStop()); - if (stepIdReached < id) { + if (stepIdReached < (id as number)) { setStepIdReached(id as number); } @@ -348,7 +348,7 @@ const WizardComponent = (props: IOtherProps) => { !isFetchingPVList && currentPlanStatus.state !== 'Pending' && currentPlanStatus.state !== 'Critical' && - !planState.currentPlan.spec.refresh && + !planState.currentPlan?.spec.refresh && (values.migrationType.value !== 'scc' || (values.selectedPVs.length > 0 && storageClasses.length > 1)) ); diff --git a/src/app/home/pages/PlansPage/helpers.ts b/src/app/home/pages/PlansPage/helpers.ts index 6a5a3f049..ab0792938 100644 --- a/src/app/home/pages/PlansPage/helpers.ts +++ b/src/app/home/pages/PlansPage/helpers.ts @@ -41,6 +41,7 @@ export const getPlanStatusText = (plan: IPlan) => { hasCanceledCondition = null, hasCancelingCondition = null, hasCriticalCondition = null, + hasNotSupportedCondition = null, latestAction = null, latestIsFailed = null, hasConflictCondition = null, @@ -62,6 +63,7 @@ export const getPlanStatusText = (plan: IPlan) => { if (hasCanceledCondition) return `${latestActionStr} canceled`; if (hasNotReadyCondition || !hasReadyCondition) return 'Not Ready'; if (hasSucceededRollback) return 'Rollback succeeded'; + if (hasNotSupportedCondition) return 'Storage live migration not supported'; if (hasDVMBlockedCondition) return 'In progress'; if (hasSucceededCutover && hasWarnCondition) return 'Migration completed with warnings'; if (hasSucceededWithWarningsCondition) return `${latestActionStr} completed with warnings`; diff --git a/src/app/plan/duck/helpers.ts b/src/app/plan/duck/helpers.ts index b16af2e4b..3b08c46aa 100644 --- a/src/app/plan/duck/helpers.ts +++ b/src/app/plan/duck/helpers.ts @@ -1,9 +1,10 @@ -import { IPlan, IMigPlan, IMigration, ICondition, IStatus } from '../../plan/duck/types'; +import { ICondition } from '../../plan/duck/types'; interface IPlanConditionStatuses { hasClosedCondition: boolean; hasReadyCondition: boolean; hasNotReadyCondition: boolean; hasConflictCondition: boolean; + hasNotSupportedCondition: boolean; conflictErrorMsg: string; hasPODWarnCondition: boolean; hasPVWarnCondition: boolean; @@ -27,6 +28,9 @@ export const filterPlanConditions = (conditions: ICondition[]): IPlanConditionSt hasReadyCondition: conditions.some((c) => c.type === 'Ready'), hasNotReadyCondition: conditions.some((c) => c.category === 'Critical'), hasConflictCondition: conditions.some((c) => c.type === 'PlanConflict'), + hasNotSupportedCondition: conditions.some( + (c) => c.type === 'KubeVirtStorageLiveMigrationNotEnabled' + ), hasPODWarnCondition: conditions.some((c) => c.type === 'PodLimitExceeded'), hasPVWarnCondition: conditions.some((c) => c.type === 'PVLimitExceeded'), conflictErrorMsg: conditions.find((c) => c.type === 'PlanConflict')?.message, diff --git a/src/app/plan/duck/sagas.ts b/src/app/plan/duck/sagas.ts index 2488dc2a5..acae6d31c 100644 --- a/src/app/plan/duck/sagas.ts +++ b/src/app/plan/duck/sagas.ts @@ -560,6 +560,9 @@ function* checkPlanStatus(action: any): any { const hasConflictCondition = !!updatedPlan.status.conditions.some((cond) => { return cond.type === 'PlanConflict'; }); + const hasNotSupportedCondition = !!updatedPlan.status.conditions.some((cond) => { + return cond.type === 'KubeVirtStorageLiveMigrationNotEnabled'; + }); if (hasReadyCondition) { if (hasWarnCondition) { const warnCondition = updatedPlan.status.conditions.find((cond) => { @@ -595,7 +598,8 @@ function* checkPlanStatus(action: any): any { }); yield put( PlanActions.updateCurrentPlanStatus({ - state: CurrentPlanState.Critical, + state: + errorCond.reason === 'Conflict' ? CurrentPlanState.Warn : CurrentPlanState.Critical, errorMessage: errorCond.message, }) ); @@ -616,6 +620,20 @@ function* checkPlanStatus(action: any): any { yield put(PlanActions.stopPlanStatusPolling(action.planName)); } + + if (hasNotSupportedCondition) { + const unsupportedCond = updatedPlan.status.conditions.find((cond) => { + return cond.type === 'KubeVirtStorageLiveMigrationNotEnabled'; + }); + yield put( + PlanActions.updateCurrentPlanStatus({ + state: CurrentPlanState.Warn, + errorMessage: unsupportedCond.message, + }) + ); + + yield put(PlanActions.stopPlanStatusPolling(action.planName)); + } } } else { planStatusComplete = true; diff --git a/src/app/plan/duck/types.ts b/src/app/plan/duck/types.ts index 4d8d52b72..336ede2d8 100644 --- a/src/app/plan/duck/types.ts +++ b/src/app/plan/duck/types.ts @@ -164,6 +164,7 @@ export interface IPlan { conflictErrorMsg?: string; hasCanceledCondition?: boolean; hasCriticalCondition?: boolean; + hasNotSupportedCondition?: boolean; hasCancelingCondition?: boolean; hasClosedCondition?: boolean; hasErrorCondition?: boolean;