From 5b5f5d57ff9adebabc331f453d365e2b9b3af3e8 Mon Sep 17 00:00:00 2001 From: Jeff Puzzo Date: Mon, 13 Jan 2025 16:19:04 -0500 Subject: [PATCH] [MTV-1880] Fix 'Project' select issues, add 'Plan name' back to step 1 of migration plan wizard Signed-off-by: Jeff Puzzo --- .../en/plugin__forklift-console-plugin.json | 1 - .../common}/ProjectNameSelect.tsx | 0 .../src/components/common/index.ts | 1 + .../src/components/index.ts | 1 + .../Plans/components/PlansAddButton.tsx | 16 ++- .../Plans/views/create/PlanCreatePage.tsx | 15 ++- .../create/components/PlanCreateForm.tsx | 124 ++++++++++-------- .../create/components/PlanNameTextField.tsx | 8 +- .../create/components/ProvidersEmptyState.tsx | 42 ++++++ .../Plans/views/list/PlansListPage.tsx | 10 +- .../views/create/ProvidersCreatePage.tsx | 10 +- .../create/components/ProviderCreateForm.tsx | 10 +- .../components/MigrationAction.tsx | 7 +- .../views/list/ProvidersListPage.tsx | 10 +- .../list/components/ProvidersAddButton.tsx | 13 +- .../migrate/components/PlansCreateForm.tsx | 46 +------ 16 files changed, 168 insertions(+), 146 deletions(-) rename packages/forklift-console-plugin/src/{modules/Plans/views/create/components => components/common}/ProjectNameSelect.tsx (100%) create mode 100644 packages/forklift-console-plugin/src/components/common/index.ts create mode 100644 packages/forklift-console-plugin/src/modules/Plans/views/create/components/ProvidersEmptyState.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 65463ecf8..9c8f77c46 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 @@ -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", diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/create/components/ProjectNameSelect.tsx b/packages/forklift-console-plugin/src/components/common/ProjectNameSelect.tsx similarity index 100% rename from packages/forklift-console-plugin/src/modules/Plans/views/create/components/ProjectNameSelect.tsx rename to packages/forklift-console-plugin/src/components/common/ProjectNameSelect.tsx diff --git a/packages/forklift-console-plugin/src/components/common/index.ts b/packages/forklift-console-plugin/src/components/common/index.ts new file mode 100644 index 000000000..a20b16e99 --- /dev/null +++ b/packages/forklift-console-plugin/src/components/common/index.ts @@ -0,0 +1 @@ +export * from './ProjectNameSelect'; diff --git a/packages/forklift-console-plugin/src/components/index.ts b/packages/forklift-console-plugin/src/components/index.ts index e794bfd38..9d75fb969 100644 --- a/packages/forklift-console-plugin/src/components/index.ts +++ b/packages/forklift-console-plugin/src/components/index.ts @@ -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'; diff --git a/packages/forklift-console-plugin/src/modules/Plans/components/PlansAddButton.tsx b/packages/forklift-console-plugin/src/modules/Plans/components/PlansAddButton.tsx index dfdcbb06d..82b8156b1 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/components/PlansAddButton.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/components/PlansAddButton.tsx @@ -8,18 +8,20 @@ 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 = ({ 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, }); @@ -27,7 +29,7 @@ export const PlansAddButton: React.FC<{ namespace: string; dataTestId?: string } setData({ selectedVms: [], }); - history.push(`${PlansListURL}/~new`); + history.push(`${plansListURL}/~new`); }; return ( diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/create/PlanCreatePage.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/create/PlanCreatePage.tsx index dc0dbe9f6..3b98d05ba 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/views/create/PlanCreatePage.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/views/create/PlanCreatePage.tsx @@ -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 = @@ -40,7 +39,7 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) = groupVersionKind: ProviderModelGroupVersionKind, namespaced: true, isList: true, - namespace, + namespace: namespace || projectName, }); const selectedProvider = @@ -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', @@ -74,7 +80,7 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) = selectedProvider={selectedProvider} /> ), - enableNext: filterState?.selectedVMs?.length > 0, + enableNext: isFirstStepValid, }, { id: 'step-2', @@ -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', }, ]; @@ -116,7 +122,6 @@ export const PlanCreatePage: React.FC<{ namespace: string }> = ({ namespace }) = dispatch(startCreate()); }} onClose={() => history.goBack()} - startAtStep={startAtStep} /> diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanCreateForm.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanCreateForm.tsx index 72941d918..46247360d 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanCreateForm.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanCreateForm.tsx @@ -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'; @@ -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[]; @@ -40,35 +43,48 @@ export type PlanCreateFormProps = { export const PlanCreateForm: React.FC = ({ 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 (
+ { + dispatch(setPlanName(value?.trim() ?? '')); + setData({ ...data, planName: value }); + }} + /> + ({ - 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={ The project that your migration plan will be created in. Only projects with providers @@ -77,47 +93,51 @@ export const PlanCreateForm: React.FC = ({ } /> - - - - - {filterState.selectedProviderUID ? ( - - - onChange('')} - isSelected - isCompact - content={ - {t('Click to select a different provider from the list.')}
- } - > - - - {t('Click to unselect.')} - - - - } - /> - - - ) : ( - + - )} - + + + {filterState.selectedProviderUID ? ( + + + onProviderChange('')} + isSelected + isCompact + content={ + {t('Click to select a different provider from the list.')} + } + > + + + {t('Click to unselect.')} + + + + } + /> + + + ) : ( + + )} + + ) : ( + + )} ); diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanNameTextField.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanNameTextField.tsx index 79ef68ffd..0b538d51d 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanNameTextField.tsx +++ b/packages/forklift-console-plugin/src/modules/Plans/views/create/components/PlanNameTextField.tsx @@ -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'; @@ -30,10 +30,8 @@ export const PlanNameTextField: React.FC = ({ fieldId="planName" {...(isUpdated && { validated: validated, - helperTextInvalid: ( - - Name is required and must be a unique within a namespace and valid Kubernetes name. - + helperTextInvalid: t( + 'Name is required and must be a unique within a namespace and valid Kubernetes name.', ), })} > diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/create/components/ProvidersEmptyState.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/create/components/ProvidersEmptyState.tsx new file mode 100644 index 000000000..c0a7d4dbf --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/create/components/ProvidersEmptyState.tsx @@ -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 = ({ projectName }) => { + const { t } = useForkliftTranslation(); + + return ( + + } + /> + + + {projectName} project does not contain any providers. To select a source provider, create + a provider in this project or select a different project. + + + + + + + + + ); +}; 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 ff932b781..8a4aa9cf0 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 @@ -156,9 +156,7 @@ const PlansListPage: React.FC<{ const EmptyState = ( - } + AddButton={} namespace={namespace} /> ); @@ -167,11 +165,7 @@ const PlansListPage: React.FC<{ - ) - } + addButton={permissions.canCreate && } dataSource={[data || [], plansLoaded, plansLoadError]} RowMapper={PlanRow} fieldsMetadata={fieldsMetadataFactory(t)} diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/create/ProvidersCreatePage.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/create/ProvidersCreatePage.tsx index 8aa9f1f1c..d41b0c530 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/create/ProvidersCreatePage.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/create/ProvidersCreatePage.tsx @@ -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', diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/create/components/ProviderCreateForm.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/create/components/ProviderCreateForm.tsx index f66380901..0bc2d23f3 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/create/components/ProviderCreateForm.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/create/components/ProviderCreateForm.tsx @@ -1,9 +1,6 @@ import React, { useReducer } from 'react'; import { Base64 } from 'js-base64'; -import { - ProjectNameSelect, - useProjectNameSelectOptions, -} from 'src/modules/Plans/views/create/components/ProjectNameSelect'; +import { ProjectNameSelect, useProjectNameSelectOptions } from 'src/components/common'; import { ModalHOC } from 'src/modules/Providers/modals'; import { validateK8sName, ValidationMsg } from 'src/modules/Providers/utils'; import { SelectableCard } from 'src/modules/Providers/utils/components/Gallery/SelectableCard'; @@ -46,8 +43,7 @@ export const ProvidersCreateForm: React.FC = ({ providerNames = [], }) => { const { t } = useForkliftTranslation(); - - const projects = useProjectNameSelectOptions(projectName); + const projectNameOptions = useProjectNameSelectOptions(projectName); const initialState = { validation: { @@ -116,7 +112,7 @@ export const ProvidersCreateForm: React.FC = ({
The project that your provider will be created in. diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/components/MigrationAction.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/components/MigrationAction.tsx index b8e220492..2b1d567b4 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/components/MigrationAction.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/components/MigrationAction.tsx @@ -16,15 +16,14 @@ export const MigrationAction: FC<{ }> = ({ selectedVms, provider, className }) => { const { t } = useForkliftTranslation(); const history = useHistory(); - const namespace = provider?.metadata?.namespace; const planListURL = getResourceUrl({ reference: PlanModelRef, - namespace, - namespaced: namespace !== undefined, + namespaced: false, }); const { setData } = useCreateVmMigrationData(); + const onClick = () => { - setData({ selectedVms, provider }); + setData({ selectedVms, provider, projectName: provider?.metadata?.namespace }); history.push(`${planListURL}/~new`); }; diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/list/ProvidersListPage.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/list/ProvidersListPage.tsx index 47da8e737..edf8eeccd 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/list/ProvidersListPage.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/list/ProvidersListPage.tsx @@ -198,9 +198,7 @@ const ProvidersListPage: React.FC<{ const EmptyState = ( - } + AddButton={} namespace={namespace} /> ); @@ -208,11 +206,7 @@ const ProvidersListPage: React.FC<{ return ( data-testid="providers-list" - addButton={ - permissions.canCreate && ( - - ) - } + addButton={permissions.canCreate && } dataSource={[data || [], providersLoaded, providersLoadError]} RowMapper={ProviderRow} fieldsMetadata={fieldsMetadataFactory(t)} diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/list/components/ProvidersAddButton.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/list/components/ProvidersAddButton.tsx index d54127560..c70817c22 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/list/components/ProvidersAddButton.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/list/components/ProvidersAddButton.tsx @@ -4,11 +4,18 @@ import { getResourceUrl } from 'src/modules/Providers/utils'; import { useForkliftTranslation } from 'src/utils/i18n'; import { ProviderModelRef } from '@kubev2v/types'; -import { Button } from '@patternfly/react-core'; +import { Button, ButtonProps } from '@patternfly/react-core'; -export const ProvidersAddButton: React.FC<{ namespace: string; dataTestId?: string }> = ({ +interface ProvidersAddButtonProps { + namespace?: string; + dataTestId?: string; + buttonProps?: ButtonProps; +} + +export const ProvidersAddButton: React.FC = ({ namespace, dataTestId, + buttonProps, }) => { const { t } = useForkliftTranslation(); const history = useHistory(); @@ -24,7 +31,7 @@ export const ProvidersAddButton: React.FC<{ namespace: string; dataTestId?: stri }; return ( - ); diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/migrate/components/PlansCreateForm.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/migrate/components/PlansCreateForm.tsx index bb61cdf2a..f34e00fcc 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/migrate/components/PlansCreateForm.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/migrate/components/PlansCreateForm.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useState } from 'react'; +import React, { ReactNode } from 'react'; import { FilterableSelect } from 'src/components'; import SectionHeading from 'src/components/headers/SectionHeading'; import { ForkliftTrans, useForkliftTranslation } from 'src/utils/i18n'; @@ -21,7 +21,6 @@ import { FormSelectOption, Stack, StackItem, - TextInput, } from '@patternfly/react-core'; import { DetailsItem, getIsTarget } from '../../../utils'; @@ -34,13 +33,11 @@ import { removeAlert, replaceNetworkMapping, replaceStorageMapping, - setPlanName, setPlanTargetNamespace, setPlanTargetProvider, } from '../reducer/actions'; import { CreateVmMigrationPageState, NetworkAlerts, StorageAlerts } from '../types'; -import { EditableDescriptionItem } from './EditableDescriptionItem'; import { MappingList } from './MappingList'; import { MappingListHeader } from './MappingListHeader'; import { StateAlerts } from './StateAlerts'; @@ -143,17 +140,9 @@ export const PlansCreateForm = ({ alerts, } = state; - const [isNameEdited, setIsNameEdited] = useState(true); - const networkMessages = buildNetworkMessages(t); const storageMessages = buildStorageMessages(t); - const onChangePlan: (value: string, event: React.FormEvent) => void = ( - value, - ) => { - dispatch(setPlanName(value?.trim() ?? '')); - }; - const onChangeTargetProvider: ( value: string, event: React.FormEvent, @@ -170,39 +159,6 @@ export const PlansCreateForm = ({ default: '1Col', }} > - {isNameEdited || validation.planName === 'error' ? ( - - - onChangePlan(v, e)} - /> - - - ) : ( - setIsNameEdited(true)} - isDisabled={flow.editingDone} - /> - )} -