Skip to content

Commit

Permalink
[MTV-1686] Simplify/update migration plan status cell
Browse files Browse the repository at this point in the history
Signed-off-by: Jeff Puzzo <[email protected]>
  • Loading branch information
jpuzz0 committed Dec 13, 2024
1 parent 4000d26 commit f866925
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 193 deletions.
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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)",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<V1beta1Migration> => {
const [migrations, migrationLoaded, migrationLoadError] = useK8sWatchResource<V1beta1Migration[]>(
{
groupVersionKind: MigrationModelGroupVersionKind,
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -12,7 +12,7 @@ export const StatusDetailsItem: React.FC<PlanDetailsItemProps> = ({ resource })
<DetailsItem
title={t('Status')}
helpContent={t('Migration plan state information and progress')}
content={<StatusCell data={{ obj: resource }} fieldId={''} fields={[]} />}
content={<PlanStatusCell data={{ obj: resource }} fieldId={''} fields={[]} />}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
CellProps,
NamespaceCell,
PlanCell,
PlanStatusCell,
ProviderLinkCell,
StatusCell,
VMsCell,
} from './components';

Expand Down Expand Up @@ -47,7 +47,7 @@ const cellRenderers: Record<string, React.FC<CellProps>> = {
},
['destination']: ProviderLinkCell,
['source']: ProviderLinkCell,
['phase']: StatusCell,
['phase']: PlanStatusCell,
['vms']: VMsCell,
['description']: ({ data }: CellProps) => <TableCell>{data?.obj?.spec?.description}</TableCell>,
['actions']: ActionsCell,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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 ? <ReStartIcon /> : <StartIcon />;
const buttonCutoverIcon = <CutoverIcon />;

const onClickPlanStartMigration = () => {
showModal(
<PlanStartMigrationModal resource={data.obj} model={PlanModel} title={buttonStartLabel} />,
);
};

const onClickPlanCutoverMigration = () => {
showModal(<PlanCutoverMigrationModal resource={data.obj} />);
showModal(<PlanCutoverMigrationModal resource={plan} />);
};

return (
<Flex flex={{ default: 'flex_3' }} flexWrap={{ default: 'nowrap' }}>
<FlexItem grow={{ default: 'grow' }}></FlexItem>

{canStart && (
<FlexItem align={{ default: 'alignRight' }}>
<Button variant="secondary" icon={buttonStartIcon} onClick={onClickPlanStartMigration}>
{buttonStartLabel}
</Button>
</FlexItem>
)}

{isWarmAndExecuting && !isArchived && (
<FlexItem align={{ default: 'alignRight' }}>
<Button
variant="secondary"
icon={buttonCutoverIcon}
onClick={onClickPlanCutoverMigration}
>
<Button variant="secondary" icon={<CutoverIcon />} onClick={onClickPlanCutoverMigration}>
{t('Cutover')}
</Button>
</FlexItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CellProps> = ({ 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 (
<Button
variant="secondary"
icon={<StartIcon />}
onClick={() =>
showModal(
<PlanStartMigrationModal resource={plan} model={PlanModel} title={t('Start')} />,
)
}
>
{t('Start')}
</Button>
);
}

const vmCount = getMigrationVmsCounts(vmStatuses);
const completedVmPipelineTasks = vmPipelineTasks?.filter(
(pipelineTask) => pipelineTask.status === 'Completed',
);
const progressValue = vmPipelineTasks?.length
? (100 * completedVmPipelineTasks.length) / vmPipelineTasks.length
: 0;

return (
<Flex alignItems={{ default: 'alignItemsCenter' }} spaceItems={{ default: 'spaceItemsSm' }}>
{isPlanLoading ? (
<Spinner size="md" />
) : phase === 'NotReady' ? (
t('Validating...')
) : (
<Label isCompact>{phase}</Label>
)}

{progressValue !== 0 && isPlanLoading && (
<FlexItem className="pf-v5-u-font-size-sm">{Math.trunc(progressValue)}%</FlexItem>
)}

<Split hasGutter>
{vmCount?.success > 0 && (
<SplitItem>
<PlanStatusVmCount
count={vmCount.success}
status="success"
linkPath={vmCountLinkPath}
/>
</SplitItem>
)}

{phase !== 'Running' &&
phase !== 'NotReady' &&
vms?.length &&
!vmCount?.error &&
!vmCount.success && (
<SplitItem>
<PlanStatusVmCount count={vms.length} status="warning" linkPath={vmCountLinkPath} />
</SplitItem>
)}

{vmCount?.error > 0 && (
<SplitItem>
<PlanStatusVmCount count={vmCount?.error} status="danger" linkPath={vmCountLinkPath} />
</SplitItem>
)}
</Split>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -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<PlanStatusVmCountProps> = ({
count,
status,
linkPath,
}) => {
const { t } = useForkliftTranslation();

const statusIcon = React.useMemo(() => {
switch (status) {
case 'success':
return <CheckCircleIcon />;
case 'warning':
return <ExclamationTriangleIcon />;
case 'danger':
return <ExclamationCircleIcon />;
}
}, [status]);

return (
<Flex alignItems={{ default: 'alignItemsCenter' }} spaceItems={{ default: 'spaceItemsSm' }}>
<Icon status={status}>{statusIcon}</Icon>

<FlexItem>
<Link to={linkPath}>{t('{{total}} VM', { count, total: count })}</Link>
</FlexItem>
</Flex>
);
};

This file was deleted.

Loading

0 comments on commit f866925

Please sign in to comment.