Skip to content

Commit

Permalink
[MTV-1880] Fix 'Project' select issues, add 'Plan name' back to step …
Browse files Browse the repository at this point in the history
…1 of migration plan wizard

Signed-off-by: Jeff Puzzo <[email protected]>
  • Loading branch information
jpuzz0 committed Jan 13, 2025
1 parent cc62d29 commit 5b5f5d5
Show file tree
Hide file tree
Showing 16 changed files with 168 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@
"Edit migration plan transfer network": "Edit migration plan transfer network",
"Edit NetworkMap": "Edit NetworkMap",
"Edit Plan": "Edit Plan",
"Edit plan name": "Edit plan name",
"Edit Precopy interval (minutes)": "Edit Precopy interval (minutes)",
"Edit Provider": "Edit Provider",
"Edit Provider Credentials": "Edit Provider Credentials",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ProjectNameSelect';
1 change: 1 addition & 0 deletions packages/forklift-console-plugin/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @index(['./*', /style/g], f => `export * from '${f.path}';`)
export * from './actions';
export * from './cells';
export * from './common';
export * from './ConsoleTimestamp';
export * from './empty-states';
export * from './FilterableSelect';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,28 @@ import { useForkliftTranslation } from 'src/utils/i18n';
import { PlanModelRef } from '@kubev2v/types';
import { Button } from '@patternfly/react-core';

export const PlansAddButton: React.FC<{ namespace: string; dataTestId?: string }> = ({
namespace,
dataTestId,
}) => {
interface PlansAddButtonProps {
namespace?: string;
dataTestId?: string;
}

export const PlansAddButton: React.FC<PlansAddButtonProps> = ({ namespace, dataTestId }) => {
const { t } = useForkliftTranslation();
const history = useHistory();
const { setData } = useCreateVmMigrationData();
const hasSufficientProviders = useHasSufficientProviders(namespace);

const PlansListURL = getResourceUrl({
const plansListURL = getResourceUrl({
reference: PlanModelRef,
namespace: namespace,
namespace,
namespaced: namespace !== undefined,
});

const onClick = () => {
setData({
selectedVms: [],
});
history.push(`${PlansListURL}/~new`);
history.push(`${plansListURL}/~new`);
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
// Get optional initial state context
const { data } = useCreateVmMigrationData();
const history = useHistory();
const startAtStep = data?.provider !== undefined ? 2 : 1;
const [activeNamespace, setActiveNamespace] = useActiveNamespace();
const defaultNamespace = process?.env?.DEFAULT_NAMESPACE || 'default';
const projectName =
Expand All @@ -40,7 +39,7 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
groupVersionKind: ProviderModelGroupVersionKind,
namespaced: true,
isList: true,
namespace,
namespace: namespace || projectName,
});

const selectedProvider =
Expand All @@ -59,6 +58,13 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
});
useSaveEffect(state, dispatch);

const isFirstStepValid =
state.underConstruction.plan.metadata.name &&
state.validation.planName !== 'error' &&
state.underConstruction.projectName &&
state.validation.projectName !== 'error' &&
filterState?.selectedVMs?.length > 0;

const steps = [
{
id: 'step-1',
Expand All @@ -74,7 +80,7 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
selectedProvider={selectedProvider}
/>
),
enableNext: filterState?.selectedVMs?.length > 0,
enableNext: isFirstStepValid,
},
{
id: 'step-2',
Expand All @@ -93,7 +99,7 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
Object.values(state?.validation || []).some((validation) => validation === 'error') ||
state?.validation?.planName === 'default'
),
canJumpTo: filterState?.selectedVMs?.length > 0,
canJumpTo: isFirstStepValid,
nextButtonText: 'Create migration plan',
},
];
Expand All @@ -116,7 +122,6 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) =
dispatch(startCreate());
}}
onClose={() => history.goBack()}
startAtStep={startAtStep}
/>
</PageSection>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react';
import { ProjectNameSelect, useProjectNameSelectOptions } from 'src/components/common';
import { SelectableCard } from 'src/modules/Providers/utils/components/Gallery/SelectableCard';
import { SelectableGallery } from 'src/modules/Providers/utils/components/Gallery/SelectableGallery';
import { VmData } from 'src/modules/Providers/views';
import { useCreateVmMigrationData } from 'src/modules/Providers/views/migrate';
import {
PageAction,
setPlanName,
setProjectName as setProjectNameAction,
} from 'src/modules/Providers/views/migrate/reducer/actions';
import { CreateVmMigrationPageState } from 'src/modules/Providers/views/migrate/types';
Expand All @@ -19,7 +21,8 @@ import { PlanCreatePageState } from '../states';
import { ChipsToolbarProviders } from './ChipsToolbarProviders';
import { createProviderCardItems } from './createProviderCardItems';
import { FiltersToolbarProviders } from './FiltersToolbarProviders';
import { ProjectNameSelect } from './ProjectNameSelect';
import { PlanNameTextField } from './PlanNameTextField';
import { ProviderCardEmptyState } from './ProvidersEmptyState';

export type PlanCreateFormProps = {
providers: V1beta1Provider[];
Expand All @@ -40,35 +43,48 @@ export type PlanCreateFormProps = {
export const PlanCreateForm: React.FC<PlanCreateFormProps> = ({
providers,
filterState,
state,
projectName,
filterDispatch,
dispatch,
}) => {
const { t } = useForkliftTranslation();
const { data, setData } = useCreateVmMigrationData();
const providerCardItems = createProviderCardItems(providers);
const providerNamespaces = [
...new Set(providers.map((provider) => provider.metadata?.namespace)),
];
const projectNameOptions = useProjectNameSelectOptions(projectName);
const providerCardItems = createProviderCardItems(
providers.filter((provider) => provider.metadata.namespace === projectName),
);

const onChange = (id: string) => {
const onProviderChange = React.useCallback((id: string) => {
filterDispatch({ type: 'SELECT_PROVIDER', payload: id || '' });
};
}, []);

return (
<div className="forklift-create-provider-edit-section">
<Form isWidthLimited className="forklift-section-secret-edit">
<PlanNameTextField
isRequired
value={state.underConstruction.plan.metadata.name}
validated={state.validation.planName}
isDisabled={state.flow.editingDone}
onChange={(_, value) => {
dispatch(setPlanName(value?.trim() ?? ''));
setData({ ...data, planName: value });
}}
/>

<ProjectNameSelect
value={projectName}
options={providerNamespaces.map((namespace) => ({
value: namespace,
content: namespace,
}))}
options={projectNameOptions}
onSelect={(value) => {
dispatch(setProjectNameAction(value));
setData({ ...data, projectName: value });

// Reset provider when target project name changes
if (value !== projectName) {
onProviderChange('');
}
}}
isDisabled={!providers.length}
popoverHelpContent={
<ForkliftTrans>
The project that your migration plan will be created in. Only projects with providers
Expand All @@ -77,47 +93,51 @@ export const PlanCreateForm: React.FC<PlanCreateFormProps> = ({
}
/>

<FormGroupWithHelpText fieldId="type">
<FiltersToolbarProviders
className="forklift--create-plan--filters-toolbar"
filterState={filterState}
filterDispatch={filterDispatch}
/>
<ChipsToolbarProviders filterState={filterState} filterDispatch={filterDispatch} />

{filterState.selectedProviderUID ? (
<Flex>
<FlexItem className="forklift--create-provider-edit-card-selected">
<SelectableCard
title={providerCardItems[filterState.selectedProviderUID]?.title}
titleLogo={providerCardItems[filterState.selectedProviderUID]?.logo}
onChange={() => onChange('')}
isSelected
isCompact
content={
<Tooltip
content={
<div>{t('Click to select a different provider from the list.')}</div>
}
>
<HelperText>
<HelperTextItem variant="indeterminate">
{t('Click to unselect.')}
</HelperTextItem>
</HelperText>
</Tooltip>
}
/>
</FlexItem>
</Flex>
) : (
<SelectableGallery
selectedID={filterState.selectedProviderUID}
items={providerCardItems}
onChange={onChange}
{Object.values(providerCardItems).length ? (
<FormGroupWithHelpText fieldId="type">
<FiltersToolbarProviders
className="forklift--create-plan--filters-toolbar"
filterState={filterState}
filterDispatch={filterDispatch}
/>
)}
</FormGroupWithHelpText>
<ChipsToolbarProviders filterState={filterState} filterDispatch={filterDispatch} />

{filterState.selectedProviderUID ? (
<Flex>
<FlexItem className="forklift--create-provider-edit-card-selected">
<SelectableCard
title={providerCardItems[filterState.selectedProviderUID]?.title}
titleLogo={providerCardItems[filterState.selectedProviderUID]?.logo}
onChange={() => onProviderChange('')}
isSelected
isCompact
content={
<Tooltip
content={
<div>{t('Click to select a different provider from the list.')}</div>
}
>
<HelperText>
<HelperTextItem variant="indeterminate">
{t('Click to unselect.')}
</HelperTextItem>
</HelperText>
</Tooltip>
}
/>
</FlexItem>
</Flex>
) : (
<SelectableGallery
selectedID={filterState.selectedProviderUID}
items={providerCardItems}
onChange={onProviderChange}
/>
)}
</FormGroupWithHelpText>
) : (
<ProviderCardEmptyState projectName={projectName} />
)}
</Form>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Validation } from 'src/modules/Providers';
import { ForkliftTrans, useForkliftTranslation } from 'src/utils';
import { useForkliftTranslation } from 'src/utils';

import { FormGroupWithHelpText } from '@kubev2v/common';
import { TextInput } from '@patternfly/react-core';
Expand Down Expand Up @@ -30,10 +30,8 @@ export const PlanNameTextField: React.FC<PlanNameTextFieldProps> = ({
fieldId="planName"
{...(isUpdated && {
validated: validated,
helperTextInvalid: (
<ForkliftTrans>
Name is required and must be a unique within a namespace and valid Kubernetes name.
</ForkliftTrans>
helperTextInvalid: t(
'Name is required and must be a unique within a namespace and valid Kubernetes name.',
),
})}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { ProvidersAddButton } from 'src/modules/Providers';
import { ForkliftTrans, useForkliftTranslation } from 'src/utils';

import {
EmptyState,
EmptyStateActions,
EmptyStateBody,
EmptyStateFooter,
EmptyStateHeader,
EmptyStateIcon,
} from '@patternfly/react-core';
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';

interface ProviderCardEmptyStateProps {
projectName: string;
}

export const ProviderCardEmptyState: React.FC<ProviderCardEmptyStateProps> = ({ projectName }) => {
const { t } = useForkliftTranslation();

return (
<EmptyState>
<EmptyStateHeader
titleText={t('No providers found')}
headingLevel="h4"
icon={<EmptyStateIcon icon={SearchIcon} />}
/>
<EmptyStateBody>
<ForkliftTrans>
{projectName} project does not contain any providers. To select a source provider, create
a provider in this project or select a different project.
</ForkliftTrans>
</EmptyStateBody>
<EmptyStateFooter>
<EmptyStateActions>
<ProvidersAddButton buttonProps={{ variant: 'link' }} />
</EmptyStateActions>
</EmptyStateFooter>
</EmptyState>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,7 @@ const PlansListPage: React.FC<{

const EmptyState = (
<EmptyState_
AddButton={
<PlansAddButton namespace={namespace} dataTestId="add-network-map-button-empty-state" />
}
AddButton={<PlansAddButton dataTestId="add-network-map-button-empty-state" />}
namespace={namespace}
/>
);
Expand All @@ -167,11 +165,7 @@ const PlansListPage: React.FC<{
<ModalHOC>
<StandardPage
data-testid="network-maps-list"
addButton={
permissions.canCreate && (
<PlansAddButton namespace={namespace} dataTestId="add-network-map-button" />
)
}
addButton={permissions.canCreate && <PlansAddButton dataTestId="add-network-map-button" />}
dataSource={[data || [], plansLoaded, plansLoadError]}
RowMapper={PlanRow}
fieldsMetadata={fieldsMetadataFactory(t)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,15 @@ export const ProvidersCreatePage: React.FC<{
// try to create a provider with secret
// add spec.secret
try {
provider = await createProvider(state.newProvider, secret);
provider = await createProvider(
{
...state.newProvider,
...(state.projectName && {
metadata: { ...state.newProvider.metadata, namespace: state.projectName },
}),
},
secret,
);
} catch (err) {
dispatch({
type: 'SET_API_ERROR',
Expand Down
Loading

0 comments on commit 5b5f5d5

Please sign in to comment.