Skip to content

Commit 6cd748c

Browse files
committed
Resolves: MTV-3742 | Standartize plan mappings tab
Signed-off-by: Aviv Turgeman <[email protected]>
1 parent 39793c3 commit 6cd748c

File tree

62 files changed

+1410
-2066
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1410
-2066
lines changed

locales/en/plugin__forklift-console-plugin.json

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@
210210
"Click on \"Create migration plan\".": "Click on \"Create migration plan\".",
211211
"Click the pencil for setting provider web UI link": "Click the pencil for setting provider web UI link",
212212
"Click the update credentials button to save your changes, button is disabled until a change is detected.": "Click the update credentials button to save your changes, button is disabled until a change is detected.",
213-
"Click the update mappings button to save your changes, button is disabled until a change is detected.": "Click the update mappings button to save your changes, button is disabled until a change is detected.",
214213
"Close": "Close",
215214
"Cloud computing platform that controls large pools of resources": "Cloud computing platform that controls large pools of resources",
216215
"Cloud computing virtualization platform": "Cloud computing virtualization platform",
@@ -292,7 +291,6 @@
292291
"Defines the CPU limits allocated to the main container in the controller pod. The default value is 500 milliCPU.": "Defines the CPU limits allocated to the main container in the controller pod. The default value is 500 milliCPU.",
293292
"Delete": "Delete",
294293
"Delete {{model.label}}": "Delete {{model.label}}",
295-
"Delete mapping": "Delete mapping",
296294
"Delete network map": "Delete network map",
297295
"Delete plan": "Delete plan",
298296
"Delete provider": "Delete provider",
@@ -336,7 +334,6 @@
336334
"Edit default transfer network": "Edit default transfer network",
337335
"Edit description": "Edit description",
338336
"Edit labels": "Edit labels",
339-
"Edit mappings": "Edit mappings",
340337
"Edit migration plan target project": "Edit migration plan target project",
341338
"Edit migration plan transfer network": "Edit migration plan transfer network",
342339
"Edit network map": "Edit network map",
@@ -629,7 +626,6 @@
629626
"Network map is required.": "Network map is required.",
630627
"Network map name": "Network map name",
631628
"Network map YAML": "Network map YAML",
632-
"Network map:": "Network map:",
633629
"Network maps": "Network maps",
634630
"Network maps ensure that the network configurations of your migrating virtual machines (VMs) are correctly translated and applied in the target environment.": "Network maps ensure that the network configurations of your migrating virtual machines (VMs) are correctly translated and applied in the target environment.",
635631
"Network name template": "Network name template",
@@ -652,7 +648,6 @@
652648
"No network mappings selected": "No network mappings selected",
653649
"No network maps found": "No network maps found",
654650
"No network maps found in project <1>{namespace}</1>.": "No network maps found in project <1>{namespace}</1>.",
655-
"No networks in this category": "No networks in this category",
656651
"No node selectors defined": "No node selectors defined",
657652
"No options available": "No options available",
658653
"No owner": "No owner",
@@ -673,7 +668,6 @@
673668
"No storage maps found": "No storage maps found",
674669
"No storage maps found in project <1>{namespace}</1>.": "No storage maps found in project <1>{namespace}</1>.",
675670
"No storages available": "No storages available",
676-
"No storages in this category": "No storages in this category",
677671
"No value for provider web UI link": "No value for provider web UI link",
678672
"No VMs have been selected": "No VMs have been selected",
679673
"Node": "Node",
@@ -682,7 +676,6 @@
682676
"Node labels": "Node labels",
683677
"Node selector enables constraining virtual machines scheduling to specific nodes, based on node labels. Adding labels below will schedule virtual machines only on nodes which contain the specified labels.": "Node selector enables constraining virtual machines scheduling to specific nodes, based on node labels. Adding labels below will schedule virtual machines only on nodes which contain the specified labels.",
684678
"None": "None",
685-
"Not available": "Not available",
686679
"Not in": "Not in",
687680
"Not Ready": "Not Ready",
688681
"not started": "not started",
@@ -735,10 +728,8 @@
735728
"OpenStack token for authentication using a user name. [required]": "OpenStack token for authentication using a user name. [required]",
736729
"Operator": "Operator",
737730
"Other networks present on the source provider": "Other networks present on the source provider",
738-
"Other networks present on the source provider ": "Other networks present on the source provider ",
739731
"Other settings (optional)": "Other settings (optional)",
740732
"Other storages present on the source provider": "Other storages present on the source provider",
741-
"Other storages present on the source provider ": "Other storages present on the source provider ",
742733
"OvaPath": "OvaPath",
743734
"Overview": "Overview",
744735
"Owner": "Owner",
@@ -969,7 +960,6 @@
969960
"Storage map name": "Storage map name",
970961
"Storage map type": "Storage map type",
971962
"Storage map YAML": "Storage map YAML",
972-
"Storage map:": "Storage map:",
973963
"Storage maps": "Storage maps",
974964
"Storage maps define how the storage of source VMs will be provisioned on the target cluster by linking source storage entities to target storage classes.": "Storage maps define how the storage of source VMs will be provisioned on the target cluster by linking source storage entities to target storage classes.",
975965
"Storage product": "Storage product",
@@ -1004,7 +994,6 @@
1004994
"The commercial product name or model of the storage system being used. This helps ensure the correct features and APIs will be used.": "The commercial product name or model of the storage system being used. This helps ensure the correct features and APIs will be used.",
1005995
"The current certificate does not match the certificate fetched from URL. Manually validate the fingerprint before proceeding.": "The current certificate does not match the certificate fetched from URL. Manually validate the fingerprint before proceeding.",
1006996
"The default setting is \"Providers default,\" which uses the default network configured for your source and target providers. You can select a specific NetworkAttachmentDefinition from the dropdown list if you want to use a dedicated network for migration data to improve performance or security.": "The default setting is \"Providers default,\" which uses the default network configured for your source and target providers. You can select a specific NetworkAttachmentDefinition from the dropdown list if you want to use a dedicated network for migration data to improve performance or security.",
1007-
"The edit mappings button is disabled if the plan started running and at least one virtual machine was migrated successfully or when the plan status does not enable editing.": "The edit mappings button is disabled if the plan started running and at least one virtual machine was migrated successfully or when the plan status does not enable editing.",
1008997
"The Manager CA certificate unless it was replaced by a third-party certificate, in which case, enter the Manager Apache CA certificate.": "The Manager CA certificate unless it was replaced by a third-party certificate, in which case, enter the Manager Apache CA certificate.",
1009998
"The mapping data from the inventory is not available, {{resourcesError}}.": "The mapping data from the inventory is not available, {{resourcesError}}.",
1010999
"The password for the ESXi host admin": "The password for the ESXi host admin",
@@ -1030,10 +1019,8 @@
10301019
"The provider is not ready.": "The provider is not ready.",
10311020
"The repository or virtualization platform you want to migrate your virtual machines from into the OpenShift cluster.": "The repository or virtualization platform you want to migrate your virtual machines from into the OpenShift cluster.",
10321021
"The Secret was not loaded. Try reloading the page or check if the secret exists. ": "The Secret was not loaded. Try reloading the page or check if the secret exists. ",
1033-
"The source mapping data from the inventory is not available: {{resourcesError}}.": "The source mapping data from the inventory is not available: {{resourcesError}}.",
10341022
"The storage device or partition that contains the root filesystem. For example, naming a root device \"sdb2/boot/dev\" would mean to use the second partition on the first hard drive.": "The storage device or partition that contains the root filesystem. For example, naming a root device \"sdb2/boot/dev\" would mean to use the second partition on the first hard drive.",
10351023
"The target project is the project, within your selected target provider, that your virtual machines will be migrated to. This is different from the project that your migration plan will be created in and where your provider was created.": "The target project is the project, within your selected target provider, that your virtual machines will be migrated to. This is different from the project that your migration plan will be created in and where your provider was created.",
1036-
"The target storage mapping data from the inventory is not available: {{resourcesError}}.": "The target storage mapping data from the inventory is not available: {{resourcesError}}.",
10371024
"The uploaded file name extension should be .ova": "The uploaded file name extension should be .ova",
10381025
"The URL is invalid. URL should include the schema, for example: https://example.com:6443.": "The URL is invalid. URL should include the schema, for example: https://example.com:6443.",
10391026
"The URL is required. URL should include the schema and path, for example: https://rhv-host-example.com/ovirt-engine/api": "The URL is required. URL should include the schema and path, for example: https://rhv-host-example.com/ovirt-engine/api",
@@ -1087,7 +1074,6 @@
10871074
"Unknown power state": "Unknown power state",
10881075
"Unsupported provider type": "Unsupported provider type",
10891076
"Update credentials": "Update credentials",
1090-
"Update mappings": "Update mappings",
10911077
"Updated": "Updated",
10921078
"updating the hooks will override the current configuration.": "updating the hooks will override the current configuration.",
10931079
"updating the LUKS decryption keys will override the current configuration.": "updating the LUKS decryption keys will override the current configuration.",

src/components/headers/SectionHeadingWithEdit.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type SectionHeadingWithEditProps = Omit<
1717
>;
1818

1919
const SectionHeadingWithEdit: FC<SectionHeadingWithEditProps> = ({
20+
children,
2021
'data-testid': dataTestId,
2122
editable = true,
2223
onClick,
@@ -41,7 +42,9 @@ const SectionHeadingWithEdit: FC<SectionHeadingWithEditProps> = ({
4142
</Button>
4243
</Flex>
4344
}
44-
/>
45+
>
46+
{children}
47+
</SectionHeading>
4548
);
4649
};
4750

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { useMemo } from 'react';
2+
import {
3+
type Control,
4+
Controller,
5+
type FieldValues,
6+
type Path,
7+
type UseFormTrigger,
8+
useWatch,
9+
} from 'react-hook-form';
10+
import EmptyCategorySelectOption from 'src/plans/components/EmptyCategorySelectOption';
11+
import {
12+
NetworkMapFieldId,
13+
type NetworkMapping,
14+
} from 'src/plans/create/steps/network-map/constants';
15+
import type { MappingValue } from 'src/plans/create/types';
16+
17+
import Select from '@components/common/Select';
18+
import { SelectGroup, SelectList, SelectOption } from '@patternfly/react-core';
19+
import { getDuplicateValues, isEmpty } from '@utils/helpers';
20+
import { useForkliftTranslation } from '@utils/i18n';
21+
22+
import { isNetworkMappingDisabled } from './utils/utils';
23+
24+
type SourceNetworkFieldProps<T extends FieldValues> = {
25+
fieldId: Path<T>;
26+
control: Control<T>;
27+
trigger: UseFormTrigger<T>;
28+
usedSourceNetworks: MappingValue[];
29+
otherSourceNetworks: MappingValue[];
30+
};
31+
32+
const SourceNetworkField = <T extends FieldValues>({
33+
control,
34+
fieldId,
35+
otherSourceNetworks,
36+
trigger,
37+
usedSourceNetworks,
38+
}: SourceNetworkFieldProps<T>) => {
39+
const { t } = useForkliftTranslation();
40+
const networkMappings: NetworkMapping[] = useWatch({
41+
control,
42+
name: NetworkMapFieldId.NetworkMap as Path<T>,
43+
});
44+
45+
const allNetworks = useMemo(
46+
() => [...usedSourceNetworks, ...otherSourceNetworks],
47+
[usedSourceNetworks, otherSourceNetworks],
48+
);
49+
const duplicateNames = useMemo(
50+
() => getDuplicateValues(allNetworks, (network) => network.name),
51+
[allNetworks],
52+
);
53+
54+
return (
55+
<Controller
56+
name={fieldId}
57+
control={control}
58+
render={({ field }) => (
59+
<Select
60+
ref={field.ref}
61+
id={fieldId}
62+
testId={`source-network-${fieldId}`}
63+
value={(field.value as MappingValue).name}
64+
onSelect={async (_event, value) => {
65+
field.onChange(value);
66+
await trigger(fieldId);
67+
}}
68+
placeholder={t('Select source network')}
69+
>
70+
<SelectGroup label={t('Networks used by the selected VMs')}>
71+
<SelectList>
72+
{isEmpty(usedSourceNetworks) ? (
73+
<EmptyCategorySelectOption resourceName="networks" />
74+
) : (
75+
usedSourceNetworks.map((usedNetwork) => (
76+
<SelectOption
77+
key={usedNetwork.name}
78+
value={usedNetwork}
79+
description={duplicateNames.has(usedNetwork.name) ? usedNetwork.id : null}
80+
isDisabled={isNetworkMappingDisabled(networkMappings, usedNetwork)}
81+
>
82+
{usedNetwork.name}
83+
</SelectOption>
84+
))
85+
)}
86+
</SelectList>
87+
</SelectGroup>
88+
89+
<SelectGroup label={t('Other networks present on the source provider')}>
90+
<SelectList>
91+
{isEmpty(otherSourceNetworks) ? (
92+
<EmptyCategorySelectOption resourceName="networks" />
93+
) : (
94+
otherSourceNetworks?.map((otherNetwork) => (
95+
<SelectOption
96+
key={otherNetwork.name}
97+
value={otherNetwork}
98+
description={duplicateNames.has(otherNetwork.name) ? otherNetwork.id : null}
99+
isDisabled={isNetworkMappingDisabled(networkMappings, otherNetwork)}
100+
>
101+
{otherNetwork.name}
102+
</SelectOption>
103+
))
104+
)}
105+
</SelectList>
106+
</SelectGroup>
107+
</Select>
108+
)}
109+
/>
110+
);
111+
};
112+
113+
export default SourceNetworkField;
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { type FC, useMemo } from 'react';
2+
import { Controller, useFormContext } from 'react-hook-form';
3+
import type { MappingValue } from 'src/plans/create/types';
4+
import { IgnoreNetwork } from 'src/plans/details/tabs/Mappings/utils/constants';
5+
6+
import Select from '@components/common/Select';
7+
import { Divider, SelectList, SelectOption } from '@patternfly/react-core';
8+
import { isEmpty } from '@utils/helpers';
9+
import { useForkliftTranslation } from '@utils/i18n';
10+
11+
type TargetNetworkFieldProps = {
12+
fieldId: string;
13+
targetNetworks: MappingValue[] | Record<string, MappingValue>;
14+
showIgnoreNetworkOption?: boolean;
15+
emptyStateMessage?: string;
16+
isDisabled?: boolean;
17+
triggerFieldId?: string;
18+
};
19+
20+
const TargetNetworkField: FC<TargetNetworkFieldProps> = ({
21+
emptyStateMessage,
22+
fieldId,
23+
isDisabled,
24+
showIgnoreNetworkOption,
25+
targetNetworks,
26+
triggerFieldId,
27+
}) => {
28+
const { control, trigger } = useFormContext();
29+
const { t } = useForkliftTranslation();
30+
31+
// Normalize targetNetworks to an array of [key, MappingValue] tuples
32+
const networksEntries = useMemo(
33+
() =>
34+
Array.isArray(targetNetworks)
35+
? targetNetworks.map((network) => [network.id ?? network.name, network] as const)
36+
: Object.entries(targetNetworks),
37+
[targetNetworks],
38+
);
39+
40+
const hasNetworks = useMemo(() => !isEmpty(networksEntries), [networksEntries]);
41+
42+
return (
43+
<Controller
44+
name={fieldId}
45+
control={control}
46+
render={({ field }) => (
47+
<Select
48+
ref={field.ref}
49+
id={fieldId}
50+
testId="network-map-target-network-select"
51+
value={(field.value as MappingValue)?.name}
52+
onSelect={async (_event, value) => {
53+
field.onChange(value);
54+
if (triggerFieldId) {
55+
await trigger(triggerFieldId);
56+
return;
57+
}
58+
59+
await trigger();
60+
}}
61+
placeholder={t('Select target network')}
62+
isDisabled={isDisabled}
63+
>
64+
<SelectList>
65+
{!hasNetworks && emptyStateMessage ? (
66+
<SelectOption key="empty" isDisabled>
67+
{emptyStateMessage}
68+
</SelectOption>
69+
) : (
70+
<>
71+
{networksEntries.map(([key, network]) => (
72+
<SelectOption key={key} value={network}>
73+
{network.name}
74+
</SelectOption>
75+
))}
76+
{showIgnoreNetworkOption && (
77+
<>
78+
<Divider />
79+
<SelectOption
80+
key={IgnoreNetwork.Type}
81+
value={{ id: IgnoreNetwork.Type, name: IgnoreNetwork.Label }}
82+
>
83+
{IgnoreNetwork.Label}
84+
</SelectOption>
85+
</>
86+
)}
87+
</>
88+
)}
89+
</SelectList>
90+
</Select>
91+
)}
92+
/>
93+
);
94+
};
95+
96+
export default TargetNetworkField;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {
2+
NetworkMapFieldId,
3+
type NetworkMapping,
4+
} from 'src/plans/create/steps/network-map/constants';
5+
import type { MappingValue } from 'src/plans/create/types';
6+
7+
export const isNetworkMappingDisabled = (
8+
networkMappings: NetworkMapping[],
9+
usedNetwork: MappingValue,
10+
) => {
11+
return networkMappings?.some(
12+
(mapping: NetworkMapping) => mapping[NetworkMapFieldId.SourceNetwork].id === usedNetwork.id,
13+
);
14+
};

src/networkMaps/create/fields/CreateNetworkMapFieldTable.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getNetworkMapFieldId } from 'src/networkMaps/utils/getNetworkMapFieldId
55
import useTargetNetworks from 'src/utils/hooks/useTargetNetworks';
66

77
import FieldBuilderTable from '@components/FieldBuilderTable/FieldBuilderTable';
8+
import TargetNetworkField from '@components/mappings/network-mappings/TargetNetworkField';
89
import { isEmpty } from '@utils/helpers';
910
import { useForkliftTranslation } from '@utils/i18n';
1011

@@ -13,7 +14,6 @@ import { NetworkMapFieldId } from '../../utils/types';
1314
import type { CreateNetworkMapFormData } from '../types';
1415

1516
import InventorySourceNetworkField from './InventorySourceNetworkField';
16-
import TargetNetworkField from './TargetNetworkField';
1717
import { validateNetworkMaps } from './utils';
1818

1919
const CreateNetworkMapFieldTable: FC = () => {
@@ -76,6 +76,10 @@ const CreateNetworkMapFieldTable: FC = () => {
7676
<TargetNetworkField
7777
fieldId={getNetworkMapFieldId(NetworkMapFieldId.TargetNetwork, index)}
7878
targetNetworks={targetNetworks}
79+
emptyStateMessage={t(
80+
'Select a target provider and project to list available target networks',
81+
)}
82+
isDisabled={isSubmitting}
7983
/>,
8084
],
8185
}))}

0 commit comments

Comments
 (0)