From f866925d20f5a635e6b44200e3c03a86dbf41061 Mon Sep 17 00:00:00 2001 From: Jeff Puzzo Date: Thu, 12 Dec 2024 22:42:29 -0500 Subject: [PATCH] [MTV-1686] Simplify/update migration plan status cell Signed-off-by: Jeff Puzzo --- .../en/plugin__forklift-console-plugin.json | 11 +- .../modules/Plans/hooks/usePlanMigration.ts | 4 +- .../components/StatusDetailsItem.tsx | 4 +- .../src/modules/Plans/views/list/PlanRow.tsx | 4 +- .../Plans/views/list/PlansListPage.tsx | 4 +- .../views/list/components/ActionsCell.tsx | 42 +----- .../views/list/components/PlanStatusCell.tsx | 130 ++++++++++++++++++ .../list/components/PlanStatusVmCount.tsx | 43 ++++++ .../views/list/components/StatusCell.tsx | 29 ---- .../Plans/views/list/components/VMsCell.tsx | 12 +- .../views/list/components/VMsProgressCell.tsx | 104 -------------- .../Plans/views/list/components/index.ts | 3 +- 12 files changed, 197 insertions(+), 193 deletions(-) create mode 100644 packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell.tsx create mode 100644 packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusVmCount.tsx delete mode 100644 packages/forklift-console-plugin/src/modules/Plans/views/list/components/StatusCell.tsx delete mode 100644 packages/forklift-console-plugin/src/modules/Plans/views/list/components/VMsProgressCell.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 dbe3d2f99..5826254a1 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 @@ -1,17 +1,15 @@ { "{{Canceled}} canceled": "{{Canceled}} canceled", - "{{canceled}} VMs canceled": "{{canceled}} VMs canceled", "{{completed}} / {{total}}": "{{completed}} / {{total}}", "{{dateLabel}} Failed: {{value}}": "{{dateLabel}} Failed: {{value}}", "{{dateLabel}} Running: {{value}}": "{{dateLabel}} Running: {{value}}", "{{dateLabel}} Succeeded: {{value}}": "{{dateLabel}} Succeeded: {{value}}", - "{{error}} VMs failed": "{{error}} VMs failed", "{{label}} field is missing from the secret data.": "{{label}} field is missing from the secret data.", "{{name}} Details": "{{name}} Details", "{{selectedLength}} hosts selected.": "{{selectedLength}} hosts selected.", "{{success}} of {{total}} VMs migrated": "{{success}} of {{total}} VMs migrated", - "{{success}} VMs succeeded": "{{success}} VMs succeeded", - "{{total}} VMs": "{{total}} VMs", + "{{total}} VM": "{{total}} VM", + "{{total}} VM_plural": "{{total}} VMs", "{{vmCount}} VMs selected ": "{{vmCount}} VMs selected ", "{children}": "{children}", "24 hours": "24 hours", @@ -253,6 +251,7 @@ "Migration plan state information and progress": "Migration plan state information and progress", "Migration plans are used to plan migration or virtualization workloads from source providers to target providers.": "Migration plans are used to plan migration or virtualization workloads from source providers to target providers.", "Migration started": "Migration started", + "Migration status": "Migration status", "Migration Toolkit for Virtualization": "Migration Toolkit for Virtualization", "Migrations": "Migrations", "Migrations (last 24 hours)": "Migrations (last 24 hours)", @@ -401,7 +400,6 @@ "Remove virtual machines": "Remove virtual machines", "Reorder": "Reorder", "Resources": "Resources", - "Restart": "Restart", "Restart migration": "Restart migration", "Restore default columns": "Restore default columns", "Return to the providers list page": "Return to the providers list page", @@ -456,7 +454,6 @@ "Start migration": "Start migration", "Started at": "Started at", "Status": "Status", - "Status details": "Status details", "Storage": "Storage", "Storage classes": "Storage classes", "Storage domains": "Storage domains", @@ -511,7 +508,6 @@ "Token": "Token", "Total CPU count:": "Total CPU count:", "Total memory:": "Total memory:", - "Total of {{total}} VMs are planned for migration:": "Total of {{total}} VMs are planned for migration:", "Total virtual machines": "Total virtual machines", "Total: {{length}}": "Total: {{length}}", "Transfer Network": "Transfer Network", @@ -536,6 +532,7 @@ "URL of the providers API endpoint. The URL must be a valid endpoint for the provider type, see\n the documentation for each provider type to learn more about the URL format.": "URL of the providers API endpoint. The URL must be a valid endpoint for the provider type, see\n the documentation for each provider type to learn more about the URL format.", "User ID": "User ID", "Username": "Username", + "Validating...": "Validating...", "Validation Failed": "Validation Failed", "vCenter": "vCenter", "VDDK init image": "VDDK init image", diff --git a/packages/forklift-console-plugin/src/modules/Plans/hooks/usePlanMigration.ts b/packages/forklift-console-plugin/src/modules/Plans/hooks/usePlanMigration.ts index ac185e98e..1a5f90f4c 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/hooks/usePlanMigration.ts +++ b/packages/forklift-console-plugin/src/modules/Plans/hooks/usePlanMigration.ts @@ -1,7 +1,7 @@ import { MigrationModelGroupVersionKind, V1beta1Migration, V1beta1Plan } from '@kubev2v/types'; -import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; +import { useK8sWatchResource, WatchK8sResult } from '@openshift-console/dynamic-plugin-sdk'; -export const usePlanMigration = (plan: V1beta1Plan) => { +export const usePlanMigration = (plan: V1beta1Plan): WatchK8sResult => { const [migrations, migrationLoaded, migrationLoadError] = useK8sWatchResource( { groupVersionKind: MigrationModelGroupVersionKind, diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/components/DetailsSection/components/StatusDetailsItem.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/details/components/DetailsSection/components/StatusDetailsItem.tsx index 273c47345..75cb5952c 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/views/details/components/DetailsSection/components/StatusDetailsItem.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/components/DetailsSection/components/StatusDetailsItem.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StatusCell } from 'src/modules/Plans/views/list'; +import { PlanStatusCell } from 'src/modules/Plans/views/list'; import { DetailsItem } from 'src/modules/Providers/utils'; import { useForkliftTranslation } from 'src/utils/i18n'; @@ -12,7 +12,7 @@ export const StatusDetailsItem: React.FC = ({ resource }) } + content={} /> ); }; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/list/PlanRow.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/list/PlanRow.tsx index 127d712fb..afa782c19 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/views/list/PlanRow.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/views/list/PlanRow.tsx @@ -12,8 +12,8 @@ import { CellProps, NamespaceCell, PlanCell, + PlanStatusCell, ProviderLinkCell, - StatusCell, VMsCell, } from './components'; @@ -47,7 +47,7 @@ const cellRenderers: Record> = { }, ['destination']: ProviderLinkCell, ['source']: ProviderLinkCell, - ['phase']: StatusCell, + ['phase']: PlanStatusCell, ['vms']: VMsCell, ['description']: ({ data }: CellProps) => {data?.obj?.spec?.description}, ['actions']: ActionsCell, 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 814b510ae..ff932b781 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 @@ -74,12 +74,12 @@ export const fieldsMetadataFactory: ResourceFieldFactory = (t) => [ { resourceFieldId: 'phase', jsonPath: getPlanPhase, - label: t('Status'), + label: t('Migration status'), isVisible: true, filter: { type: 'enum', primary: true, - placeholderLabel: t('Status'), + placeholderLabel: t('Migration status'), values: planPhases, }, sortable: true, diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/ActionsCell.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/ActionsCell.tsx index 8a9fe3527..c9bcd791f 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/ActionsCell.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/ActionsCell.tsx @@ -1,20 +1,12 @@ import React from 'react'; import { PlanActionsDropdown } from 'src/modules/Plans/actions'; -import { PlanCutoverMigrationModal, PlanStartMigrationModal } from 'src/modules/Plans/modals'; -import { - canPlanReStart, - canPlanStart, - isPlanArchived, - isPlanExecuting, -} from 'src/modules/Plans/utils'; +import { PlanCutoverMigrationModal } from 'src/modules/Plans/modals'; +import { isPlanArchived, isPlanExecuting } from 'src/modules/Plans/utils'; import { useModal } from 'src/modules/Providers/modals'; import { useForkliftTranslation } from 'src/utils/i18n'; -import { PlanModel } from '@kubev2v/types'; import { Button, Flex, FlexItem } from '@patternfly/react-core'; import CutoverIcon from '@patternfly/react-icons/dist/esm/icons/migration-icon'; -import StartIcon from '@patternfly/react-icons/dist/esm/icons/play-icon'; -import ReStartIcon from '@patternfly/react-icons/dist/esm/icons/redo-icon'; import { CellProps } from './CellProps'; @@ -23,46 +15,20 @@ export const ActionsCell = ({ data }: CellProps) => { const { showModal } = useModal(); const plan = data.obj; - - const canStart = canPlanStart(plan); - const canReStart = canPlanReStart(plan); - const isWarmAndExecuting = plan?.spec?.warm && isPlanExecuting(plan); const isArchived = isPlanArchived(plan); - const buttonStartLabel = canReStart ? t('Restart') : t('Start'); - const buttonStartIcon = canReStart ? : ; - const buttonCutoverIcon = ; - - const onClickPlanStartMigration = () => { - showModal( - , - ); - }; - const onClickPlanCutoverMigration = () => { - showModal(); + showModal(); }; return ( - {canStart && ( - - - - )} - {isWarmAndExecuting && !isArchived && ( - 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.tsx new file mode 100644 index 000000000..b023cd402 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusCell.tsx @@ -0,0 +1,130 @@ +import React from 'react'; +import { usePlanMigration } from 'src/modules/Plans/hooks'; +import { PlanStartMigrationModal } from 'src/modules/Plans/modals'; +import { + getMigrationVmsCounts, + getPlanPhase, + isPlanArchived, + isPlanExecuting, +} 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 StartIcon from '@patternfly/react-icons/dist/esm/icons/play-icon'; + +import { CellProps } from './CellProps'; +import { PlanStatusVmCount } from './PlanStatusVmCount'; + +type VmPipelineTask = { + vmName: string; + task: string; + status: string; +}; + +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 [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) => { + acc.push({ vmName: migrationVm.name, task: pipelineStep.name, status: pipelineStep.phase }); + }); + + return acc; + }, + [], + ); + + const phase = getPlanPhase(data); + const isPlanLoading = !isWaitingForCutover && (phase === 'Running' || phase === 'Archiving'); + const planURL = getResourceUrl({ + reference: PlanModelRef, + name: plan?.metadata?.name, + namespace: plan?.metadata?.namespace, + }); + + // All VM count links point to the same place for now, + // but will be updated to target only affected VMs in the future. + // Could possibly use a querystring to dictate a table filter for the list of VMs. + const vmCountLinkPath = `${planURL}/vms`; + + if (phase === 'Ready') { + return ( + + ); + } + + const vmCount = getMigrationVmsCounts(vmStatuses); + const completedVmPipelineTasks = vmPipelineTasks?.filter( + (pipelineTask) => pipelineTask.status === 'Completed', + ); + const progressValue = vmPipelineTasks?.length + ? (100 * completedVmPipelineTasks.length) / vmPipelineTasks.length + : 0; + + return ( + + {isPlanLoading ? ( + + ) : phase === 'NotReady' ? ( + t('Validating...') + ) : ( + + )} + + {progressValue !== 0 && isPlanLoading && ( + {Math.trunc(progressValue)}% + )} + + + {vmCount?.success > 0 && ( + + + + )} + + {phase !== 'Running' && + phase !== 'NotReady' && + vms?.length && + !vmCount?.error && + !vmCount.success && ( + + + + )} + + {vmCount?.error > 0 && ( + + + + )} + + + ); +}; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusVmCount.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusVmCount.tsx new file mode 100644 index 000000000..0fcfe21ff --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/PlanStatusVmCount.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { useForkliftTranslation } from 'src/utils'; + +import { Flex, FlexItem, Icon, IconComponentProps } from '@patternfly/react-core'; +import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon'; +import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; +import ExclamationTriangleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; + +interface PlanStatusVmCountProps { + count: number; + status: IconComponentProps['status']; + linkPath: string; +} + +export const PlanStatusVmCount: React.FC = ({ + count, + status, + linkPath, +}) => { + const { t } = useForkliftTranslation(); + + const statusIcon = React.useMemo(() => { + switch (status) { + case 'success': + return ; + case 'warning': + return ; + case 'danger': + return ; + } + }, [status]); + + return ( + + {statusIcon} + + + {t('{{total}} VM', { count, total: count })} + + + ); +}; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/StatusCell.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/StatusCell.tsx deleted file mode 100644 index 2a53170e7..000000000 --- a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/StatusCell.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { getPhaseLabel, getPlanPhase } from 'src/modules/Plans/utils'; -import { TableIconCell } from 'src/modules/Providers/utils'; - -import { CellProps } from './CellProps'; -import { ErrorStatusCell } from './ErrorStatusCell'; -import { PlanStatusIcon } from './PlanStatusIcon'; -import { VMsProgressCell } from './VMsProgressCell'; - -export const StatusCell: React.FC = (props) => { - const { data } = props; - - const phase = getPlanPhase(data); - const phaseLabel = getPhaseLabel(phase); - - switch (phase) { - case 'Error': - case 'Warning': - return ErrorStatusCell(props); - case 'Failed': - case 'Canceled': - case 'Running': - case 'Succeeded': - case 'vmError': - return VMsProgressCell(props); - } - - return }>{phaseLabel}; -}; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/VMsCell.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/VMsCell.tsx index 4075a312b..a287dedd5 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/VMsCell.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/VMsCell.tsx @@ -11,13 +11,13 @@ import { CellProps } from './CellProps'; export const VMsCell: React.FC = ({ data }) => { const { t } = useForkliftTranslation(); - - const specVms = data?.obj?.spec?.vms; + const plan = data?.obj; + const specVms = plan?.spec?.vms; const planURL = getResourceUrl({ reference: PlanModelRef, - name: data?.obj?.metadata?.name, - namespace: data?.obj?.metadata?.namespace, + name: plan?.metadata?.name, + namespace: plan?.metadata?.namespace, }); return ( @@ -26,7 +26,9 @@ export const VMsCell: React.FC = ({ data }) => { - {t('{{total}} VMs', { total: specVms?.length })} + + {t('{{total}} VM', { count: specVms?.length, total: specVms?.length })} + ); diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/VMsProgressCell.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/VMsProgressCell.tsx deleted file mode 100644 index a566d8978..000000000 --- a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/VMsProgressCell.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; -import { - getMigrationVmsCounts, - getPhaseLabel, - getPlanPhase, - getPlanProgressVariant, - MigrationVmsCounts, -} from 'src/modules/Plans/utils'; -import { getResourceUrl } from 'src/modules/Providers/utils'; -import { useForkliftTranslation } from 'src/utils/i18n'; - -import { PlanModelRef } from '@kubev2v/types'; -import { - Button, - Popover, - Progress, - ProgressMeasureLocation, - ProgressSize, - Split, - SplitItem, -} from '@patternfly/react-core'; -import { HelpIcon, VirtualMachineIcon } from '@patternfly/react-icons'; - -import { CellProps } from './CellProps'; - -type PlanStatusDetailsProps = { - counters: MigrationVmsCounts; -}; - -const PlanStatusDetails: React.FC = (props) => { - const { t } = useForkliftTranslation(); - - return ( -
- {t('Total of {{total}} VMs are planned for migration:', props.counters)} -
- {t('{{success}} VMs succeeded', props.counters)} -
- {t('{{error}} VMs failed', props.counters)} -
- {t('{{canceled}} VMs canceled', props.counters)} -
-
- ); -}; - -export const VMsProgressCell: React.FC = ({ data }) => { - const { t } = useForkliftTranslation(); - - const specVms = data?.obj?.spec?.vms; - const vms = data?.obj?.status?.migration?.vms; - - const phase = getPlanPhase(data); - const phaseLabel = t(getPhaseLabel(phase)); - - const planURL = getResourceUrl({ - reference: PlanModelRef, - name: data?.obj?.metadata?.name, - namespace: data?.obj?.metadata?.namespace, - }); - - if (!vms) { - return ( - - - - - - {t('{{total}} VMs', { total: specVms?.length || 0 })} - - - ); - } - - const counters = getMigrationVmsCounts(vms); - const progressVariant = getPlanProgressVariant(phase); - - return ( -
- {t('{{success}} of {{total}} VMs migrated', counters)} - } - valueText={t('{{success}} of {{total}} VMs migrated', counters)} - value={counters?.total > 0 ? (100 * counters?.success) / counters?.total : 0} - title={ - {t('Status details')}
} - bodyContent={} - > - - - } - size={ProgressSize.sm} - measureLocation={ProgressMeasureLocation.top} - variant={progressVariant} - /> - - ); -}; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/index.ts b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/index.ts index 3f8035961..f95b2466e 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/views/list/components/index.ts +++ b/packages/forklift-console-plugin/src/modules/Plans/views/list/components/index.ts @@ -5,9 +5,8 @@ export * from './ErrorStatusCell'; export * from './NamespaceCell'; export * from './NetworkMapLinkCell'; export * from './PlanCell'; +export * from './PlanStatusCell'; export * from './PlanStatusIcon'; export * from './ProviderLinkCell'; -export * from './StatusCell'; export * from './VMsCell'; -export * from './VMsProgressCell'; // @endindex