Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MTV-1686] Simplify/update migration plan status cell #1411

Merged
merged 1 commit into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we will remove this in a followup ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, at least for now, I think the design plan is to move this button to a "Migration type" column as seen here:
image
(can be seen in figma here)

{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 }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice refactor of the component name, status cell was too general

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
Loading