diff --git a/locales/en/plugin__odf-console.json b/locales/en/plugin__odf-console.json
index 7bcf99435..717bce59b 100644
--- a/locales/en/plugin__odf-console.json
+++ b/locales/en/plugin__odf-console.json
@@ -85,6 +85,11 @@
"There are no namespaces to display. Select a cluster first to view namespaces.": "There are no namespaces to display. Select a cluster first to view namespaces.",
"There are no namespaces to display.": "There are no namespaces to display.",
"This list does not include namespaces where applications are enrolled separately under disaster recovery protection.": "This list does not include namespaces where applications are enrolled separately under disaster recovery protection.",
+ "Name requirements": "Name requirements",
+ "Example": "Example",
+ "Enter a unique name": "Enter a unique name",
+ "Name input": "Name input",
+ "A unique identifier for ACM discovered applications from selected namespaces.": "A unique identifier for ACM discovered applications from selected namespaces.",
"Select a DRCluster to choose your namespace.": "Select a DRCluster to choose your namespace.",
"No DR cluster found": "No DR cluster found",
"Namespace selection": "Namespace selection",
@@ -422,8 +427,6 @@
"Create BlockPool": "Create BlockPool",
"Data loss may occur, only recommended for small clusters or when backups are available or data loss is acceptable": "Data loss may occur, only recommended for small clusters or when backups are available or data loss is acceptable",
"{{replica}} Replication": "{{replica}} Replication",
- "Name requirements": "Name requirements",
- "Example": "Example",
"pool-name-help": "pool-name-help",
"my-block-pool": "my-block-pool",
"Data protection policy": "Data protection policy",
@@ -980,8 +983,8 @@
"Access Key Field": "Access Key Field",
"Secret Key Field": "Secret Key Field",
"ObjectBucketClaim Name": "ObjectBucketClaim Name",
- "If not provided a generic name will be generated.": "If not provided a generic name will be generated.",
"my-object-bucket": "my-object-bucket",
+ "If not provided a generic name will be generated.": "If not provided a generic name will be generated.",
"StorageClass": "StorageClass",
"Defines the object-store service and the bucket provisioner.": "Defines the object-store service and the bucket provisioner.",
"BucketClass": "BucketClass",
diff --git a/packages/mco/components/discovered-application-wizard/enroll-discovered-application.spec.tsx b/packages/mco/components/discovered-application-wizard/enroll-discovered-application.spec.tsx
index 0b1fb3d4b..f56e509fa 100644
--- a/packages/mco/components/discovered-application-wizard/enroll-discovered-application.spec.tsx
+++ b/packages/mco/components/discovered-application-wizard/enroll-discovered-application.spec.tsx
@@ -265,18 +265,59 @@ jest.mock(
})
);
-// Mocking as "fireEvent" is throwing warning for FieldLevelHelp
-jest.mock('@odf/shared/generic', () => ({
- ...jest.requireActual('@odf/shared/generic'),
- FieldLevelHelp: () => <>>,
+// Mocking as "Popover" is throwing warning for FieldLevelHelp & TextInputWithFieldRequirements
+jest.mock('@patternfly/react-core', () => ({
+ ...jest.requireActual('@patternfly/react-core'),
+ Popover: () => <>>,
}));
+const moveToStep = async (step: number) => {
+ if (step > 1) {
+ // Select cluster
+ fireEvent.click(screen.getByText('Select cluster'));
+ fireEvent.click(screen.getByText('east-1'));
+
+ // Select namespaces
+ fireEvent.click(screen.getByLabelText('Select row 0'));
+ fireEvent.click(screen.getByLabelText('Select row 1'));
+
+ // Name input
+ fireEvent.change(screen.getByLabelText('Name input'), {
+ target: { value: 'my-name' },
+ });
+ await waitFor(() => {
+ expect(screen.getByDisplayValue('my-name')).toBeInTheDocument();
+ });
+
+ // Next wizard step
+ fireEvent.click(screen.getByText('Next'));
+ }
+
+ if (step > 2) {
+ // Select recipe
+ fireEvent.click(screen.getByText('Select a recipe'));
+ fireEvent.click(screen.getByText('mock-recipe-1'));
+
+ // Next wizard step
+ fireEvent.click(screen.getByText('Next'));
+ }
+
+ if (step > 3) {
+ // Select policy
+ fireEvent.click(screen.getByText('Select a policy'));
+ fireEvent.click(screen.getByText('mock-policy-1'));
+
+ // Next wizard step
+ fireEvent.click(screen.getByText('Next'));
+ }
+};
+
describe('Test namespace step', () => {
beforeEach(() => {
- testCase += 1;
render();
});
test('Namespace selection form test', async () => {
+ testCase = 1;
// Step1 title
expect(screen.getByText('Namespace selection')).toBeInTheDocument();
// Step1 title description
@@ -311,6 +352,13 @@ describe('Test namespace step', () => {
'This list does not include namespaces where applications are enrolled separately under disaster recovery protection.'
)
).toBeInTheDocument();
+ // Name input
+ expect(screen.getByText('Name')).toBeInTheDocument();
+ expect(
+ screen.getByText(
+ 'A unique identifier for ACM discovered applications from selected namespaces.'
+ )
+ ).toBeInTheDocument();
// Footer
expect(screen.getByText('Next')).toBeInTheDocument();
@@ -331,6 +379,7 @@ describe('Test namespace step', () => {
});
test('No namespace found test', async () => {
+ testCase = 2;
// Cluster selection
fireEvent.click(screen.getByText('Select cluster'));
fireEvent.click(screen.getByText('east-1'));
@@ -347,6 +396,7 @@ describe('Test namespace step', () => {
});
test('Namespace selection test', async () => {
+ testCase = 3;
// Cluster east-1 selection
fireEvent.click(screen.getByText('Select cluster'));
fireEvent.click(screen.getByText('east-1'));
@@ -384,28 +434,27 @@ describe('Test namespace step', () => {
expect(() => screen.getByText('openshift')).toThrow(
'Unable to find an element'
);
+ // Name input
+ fireEvent.change(screen.getByLabelText('Name input'), {
+ target: { value: 'my-name' },
+ });
+ await waitFor(() => {
+ expect(screen.getByDisplayValue('my-name')).toBeInTheDocument();
+ });
});
});
describe('Test configure step', () => {
beforeEach(() => {
- testCase += 1;
render();
- // Select cluster
- fireEvent.click(screen.getByText('Select cluster'));
- fireEvent.click(screen.getByText('east-1'));
-
- // Select namespaces
- fireEvent.click(screen.getByLabelText('Select row 0'));
- fireEvent.click(screen.getByLabelText('Select row 1'));
-
- // Next wizard step
- fireEvent.click(screen.getByText('Next'));
});
+
test('Configure form test', async () => {
- // Step1 title
+ testCase = 4;
+ await moveToStep(2);
+ // Step2 title
expect(screen.getByText('Configure definition')).toBeInTheDocument();
- // Step1 title description
+ // Step2 title description
expect(
screen.getByText(
'Choose your configuration preference to protect resources (application volumes/PVCs, or Kubernetes objects).'
@@ -462,32 +511,16 @@ describe('Test configure step', () => {
describe('Test replication step', () => {
beforeEach(() => {
- testCase += 1;
render();
- // Select cluster
- fireEvent.click(screen.getByText('Select cluster'));
- fireEvent.click(screen.getByText('east-1'));
-
- // Select namespaces
- fireEvent.click(screen.getByLabelText('Select row 0'));
- fireEvent.click(screen.getByLabelText('Select row 1'));
-
- // Next wizard step
- fireEvent.click(screen.getByText('Next'));
-
- // Select recipe
- fireEvent.click(screen.getByText('Select a recipe'));
- fireEvent.click(screen.getByText('mock-recipe-1'));
-
- // Next wizard step
- fireEvent.click(screen.getByText('Next'));
});
test('Replication form test', async () => {
- // Step1 title
+ testCase = 5;
+ await moveToStep(3);
+ // Step3 title
expect(
screen.getByText('Volume and Kubernetes object replication')
).toBeInTheDocument();
- // Step1 title description
+ // Step3 title description
expect(
screen.getByText(
'Define where to sync or replicate your application volumes and Kubernetes object using a disaster recovery policy.'
@@ -544,38 +577,11 @@ describe('Test replication step', () => {
});
describe('Test review step', () => {
beforeEach(() => {
- testCase += 1;
render();
- // Select cluster
- fireEvent.click(screen.getByText('Select cluster'));
- fireEvent.click(screen.getByText('east-1'));
-
- // Select namespaces
- fireEvent.click(screen.getByLabelText('Select row 0'));
- fireEvent.click(screen.getByLabelText('Select row 1'));
-
- // Next wizard step
- fireEvent.click(screen.getByText('Next'));
-
- if (testCase === 6) {
- // Select recipe
- fireEvent.click(screen.getByText('Select a recipe'));
- fireEvent.click(screen.getByText('mock-recipe-1'));
-
- // Next wizard step
- fireEvent.click(screen.getByText('Next'));
- } else {
- // ToDo: Resource label selection reivew
- }
-
- // Select policy
- fireEvent.click(screen.getByText('Select a policy'));
- fireEvent.click(screen.getByText('mock-policy-1'));
-
- // Next wizard step
- fireEvent.click(screen.getByText('Next'));
});
test('Review form test', async () => {
+ testCase = 6;
+ await moveToStep(4);
// Namespace selection test
expect(screen.getAllByText('Namespace').length === 2).toBeTruthy();
expect(screen.getByText('Cluster:')).toBeInTheDocument();
diff --git a/packages/mco/components/discovered-application-wizard/footer.tsx b/packages/mco/components/discovered-application-wizard/footer.tsx
index c51ee4c84..664824418 100644
--- a/packages/mco/components/discovered-application-wizard/footer.tsx
+++ b/packages/mco/components/discovered-application-wizard/footer.tsx
@@ -25,7 +25,9 @@ import {
} from './utils/reducer';
const validateNamespaceStep = (state: EnrollDiscoveredApplicationState) =>
- !!state.namespace.clusterName && !!state.namespace.namespaces.length;
+ !!state.namespace.clusterName &&
+ !!state.namespace.namespaces.length &&
+ !!state.namespace.name;
const validateConfigurationStep = (state: EnrollDiscoveredApplicationState) => {
const { recipe, resourceLabels, protectionMethod } = state.configuration;
diff --git a/packages/mco/components/discovered-application-wizard/utils/reducer.ts b/packages/mco/components/discovered-application-wizard/utils/reducer.ts
index 903bea1ee..bceb2f867 100644
--- a/packages/mco/components/discovered-application-wizard/utils/reducer.ts
+++ b/packages/mco/components/discovered-application-wizard/utils/reducer.ts
@@ -21,6 +21,7 @@ export enum EnrollDiscoveredApplicationStateType {
SET_PVC_LABEL_EXPRESSIONS = 'CONFIGURATION/RESOURCE_LABEL/SET_PVC_LABEL_EXPRESSIONS',
SET_POLICY = 'REPLICATION/SET_POLICY',
SET_K8S_RESOURCE_REPLICATION_INTERVAL = 'REPLICATION/SET_K8S_RESOURCE_REPLICATION_INTERVAL',
+ SET_NAME = 'NAMESPACE/SET_NAME',
}
export type EnrollDiscoveredApplicationState = {
@@ -29,6 +30,8 @@ export type EnrollDiscoveredApplicationState = {
namespaces: K8sResourceCommon[];
// Cluster name of the discovered application
clusterName: string;
+ // DRPC name to protect the discovered application
+ name: string;
};
configuration: {
// recipe CRD (or) normal K8s CR label based protection
@@ -60,6 +63,7 @@ export const initialState: EnrollDiscoveredApplicationState = {
namespace: {
clusterName: '',
namespaces: [],
+ name: '',
},
configuration: {
protectionMethod: ProtectionMethodType.RECIPE,
@@ -111,6 +115,10 @@ export type EnrollDiscoveredApplicationAction =
| {
type: EnrollDiscoveredApplicationStateType.SET_K8S_RESOURCE_REPLICATION_INTERVAL;
payload: string;
+ }
+ | {
+ type: EnrollDiscoveredApplicationStateType.SET_NAME;
+ payload: string;
};
export const reducer: EnrollReducer = (state, action) => {
@@ -127,6 +135,7 @@ export const reducer: EnrollReducer = (state, action) => {
namespace: {
...newState.namespace,
clusterName: action.payload,
+ name: state.namespace.name,
},
};
}
@@ -207,6 +216,15 @@ export const reducer: EnrollReducer = (state, action) => {
},
};
}
+ case EnrollDiscoveredApplicationStateType.SET_NAME: {
+ return {
+ ...state,
+ namespace: {
+ ...state.namespace,
+ name: action.payload,
+ },
+ };
+ }
default:
throw new TypeError(`${action} is not a valid reducer action`);
}
diff --git a/packages/mco/components/discovered-application-wizard/wizard-steps/namespace-step/namesapce-table.tsx b/packages/mco/components/discovered-application-wizard/wizard-steps/namespace-step/namesapce-table.tsx
index 191242f6c..ee6ded0a0 100644
--- a/packages/mco/components/discovered-application-wizard/wizard-steps/namespace-step/namesapce-table.tsx
+++ b/packages/mco/components/discovered-application-wizard/wizard-steps/namespace-step/namesapce-table.tsx
@@ -1,8 +1,5 @@
import * as React from 'react';
-import {
- getDRPlacementControlResourceObj,
- useACMSafeFetch,
-} from '@odf/mco/hooks';
+import { useACMSafeFetch } from '@odf/mco/hooks';
import { DRPlacementControlKind, DRPolicyKind } from '@odf/mco/types';
import {
queryNamespacesUsingCluster,
@@ -21,7 +18,6 @@ import { isSystemNamespace, sortRows } from '@odf/shared/utils';
import {
K8sResourceCommon,
ListPageFilter,
- useK8sWatchResource,
useListPageFilter,
} from '@openshift-console/dynamic-plugin-sdk';
import { TFunction } from 'i18next';
@@ -79,13 +75,16 @@ const TableRow: React.FC> = ({
};
export const NamespaceSelectionTable: React.FC =
- ({ namespaces, clusterName, policies, isValidationEnabled, dispatch }) => {
+ ({
+ namespaces,
+ clusterName,
+ policies,
+ drPlacements,
+ isValidationEnabled,
+ dispatch,
+ }) => {
const { t } = useCustomTranslation();
- const [drPlacements, drpcLoaded, drpcLoadError] = useK8sWatchResource<
- DRPlacementControlKind[]
- >(getDRPlacementControlResourceObj());
-
const protectedNamespaces = React.useMemo(() => {
const eligiblePolicies = findAllEligiblePolicies(clusterName, policies);
return getProtectedNamespaces(drPlacements, eligiblePolicies);
@@ -100,11 +99,8 @@ export const NamespaceSelectionTable: React.FC =
const [searchResult, searchError, searchLoaded] =
useACMSafeFetch(searchQuery);
- const loaded = searchLoaded && drpcLoaded;
- const loadError = searchError || drpcLoadError;
-
const userNamespaces: K8sResourceCommon[] = React.useMemo(() => {
- if (loaded && !loadError) {
+ if (searchLoaded && !searchError) {
// Converting from search result type to K8sResourceCommon for shared components compatibility.
const allNamespaces = convertSearchResultToK8sResourceCommon(
searchResult?.data.searchResult?.[0]?.items || []
@@ -119,7 +115,7 @@ export const NamespaceSelectionTable: React.FC =
});
}
return [];
- }, [searchResult, protectedNamespaces, loaded, loadError]);
+ }, [searchResult, protectedNamespaces, searchLoaded, searchError]);
const [data, filteredData, onFilterChange] =
useListPageFilter(userNamespaces);
@@ -147,7 +143,7 @@ export const NamespaceSelectionTable: React.FC =
{namespaceValidated && (
@@ -190,6 +186,7 @@ type NamespaceSelectionTableProps = {
namespaces: K8sResourceCommon[];
clusterName: string;
policies: DRPolicyKind[];
+ drPlacements: DRPlacementControlKind[];
isValidationEnabled: boolean;
dispatch: React.Dispatch;
};
diff --git a/packages/mco/components/discovered-application-wizard/wizard-steps/namespace-step/namespace-step.tsx b/packages/mco/components/discovered-application-wizard/wizard-steps/namespace-step/namespace-step.tsx
index 923881864..25f5b68ce 100644
--- a/packages/mco/components/discovered-application-wizard/wizard-steps/namespace-step/namespace-step.tsx
+++ b/packages/mco/components/discovered-application-wizard/wizard-steps/namespace-step/namespace-step.tsx
@@ -1,12 +1,26 @@
import * as React from 'react';
+import { RAMEN_PROTECTED_APPS_NAMESPACE } from '@odf/mco/constants';
+import { getDRPlacementControlResourceObj } from '@odf/mco/hooks';
import { DRPolicyModel } from '@odf/mco/models';
-import { DRPolicyKind } from '@odf/mco/types';
+import { DRPlacementControlKind, DRPolicyKind } from '@odf/mco/types';
+import {
+ fieldRequirementsTranslations,
+ formSettings,
+} from '@odf/shared/constants';
import { SingleSelectDropdown } from '@odf/shared/dropdown';
import { StatusBox } from '@odf/shared/generic/status-box';
import { useK8sList } from '@odf/shared/hooks';
+import { TextInputWithFieldRequirements } from '@odf/shared/input-with-requirements';
+import { getName, getNamespace } from '@odf/shared/selectors';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import { getValidatedProp } from '@odf/shared/utils';
+import validationRegEx from '@odf/shared/utils/validation';
+import { useYupValidationResolver } from '@odf/shared/yup-validation-resolver';
+import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk';
import { SelectOption } from '@patternfly/react-core/deprecated';
+import { TFunction } from 'i18next';
+import { useForm } from 'react-hook-form';
+import * as Yup from 'yup';
import {
Alert,
AlertVariant,
@@ -44,6 +58,103 @@ const clusterOptions = (clusters: string[]): JSX.Element[] =>
/>
));
+const getInputValidationSchema = (t: TFunction, existingNames: string[]) => {
+ const fieldRequirements = [
+ fieldRequirementsTranslations.maxChars(t, 63),
+ fieldRequirementsTranslations.startAndEndName(t),
+ fieldRequirementsTranslations.alphaNumericPeriodAdnHyphen(t),
+ fieldRequirementsTranslations.cannotBeUsedBefore(t),
+ ];
+
+ const schema = Yup.object({
+ 'name-input': Yup.string()
+ .required()
+ .max(63, fieldRequirements[0])
+ .matches(
+ validationRegEx.startAndEndsWithAlphanumerics,
+ fieldRequirements[1]
+ )
+ .matches(
+ validationRegEx.alphaNumericsPeriodsHyphensNonConsecutive,
+ fieldRequirements[2]
+ )
+ .test(
+ 'unique-name',
+ fieldRequirements[3],
+ (value: string) => !existingNames.includes(value)
+ ),
+ });
+
+ return { schema, fieldRequirements };
+};
+
+export const NameInput: React.FC = ({
+ name,
+ drPlacements,
+ dispatch,
+}) => {
+ const { t } = useCustomTranslation();
+
+ const { schema, fieldRequirements } = React.useMemo(() => {
+ const existingNames =
+ drPlacements
+ ?.filter(
+ (drpc) => getNamespace(drpc) === RAMEN_PROTECTED_APPS_NAMESPACE
+ )
+ .map(getName) || [];
+ return getInputValidationSchema(t, existingNames);
+ }, [drPlacements, t]);
+
+ const resolver = useYupValidationResolver(schema);
+ const {
+ control,
+ watch,
+ formState: { isValid },
+ } = useForm({
+ ...formSettings,
+ resolver,
+ });
+
+ const newName: string = watch('name-input');
+
+ React.useEffect(
+ () =>
+ dispatch({
+ type: EnrollDiscoveredApplicationStateType.SET_NAME,
+ payload: isValid ? newName : '',
+ }),
+ [newName, isValid, dispatch]
+ );
+
+ return (
+
+ );
+};
+
export const NamespaceSelection: React.FC = ({
state,
isValidationEnabled,
@@ -51,10 +162,17 @@ export const NamespaceSelection: React.FC = ({
}) => {
const { t } = useCustomTranslation();
- const [drPolicies, loaded, loadError] =
+ const [drPolicies, policyloaded, policyLoadError] =
useK8sList(DRPolicyModel);
- const { clusterName, namespaces } = state.namespace;
+ const [drPlacements, drpcLoaded, drpcLoadError] = useK8sWatchResource<
+ DRPlacementControlKind[]
+ >(getDRPlacementControlResourceObj());
+
+ const loaded = policyloaded && drpcLoaded;
+ const loadError = policyLoadError || drpcLoadError;
+
+ const { clusterName, namespaces, name } = state.namespace;
const clusterNameHelperText = t(
'Select a DRCluster to choose your namespace.'
);
@@ -135,10 +253,16 @@ export const NamespaceSelection: React.FC = ({
namespaces={namespaces}
clusterName={clusterName}
policies={drPolicies}
+ drPlacements={drPlacements}
isValidationEnabled={isValidationEnabled}
dispatch={dispatch}
/>
+
) : (
@@ -153,3 +277,9 @@ type NamespaceSelectionProps = {
isValidationEnabled: boolean;
dispatch: React.Dispatch;
};
+
+type NameInputProps = {
+ name: string;
+ drPlacements: DRPlacementControlKind[];
+ dispatch: React.Dispatch;
+};
diff --git a/packages/ocs/block-pool/body.tsx b/packages/ocs/block-pool/body.tsx
index 2cb873145..eb3139b6a 100644
--- a/packages/ocs/block-pool/body.tsx
+++ b/packages/ocs/block-pool/body.tsx
@@ -13,6 +13,7 @@ import {
CephClusterKind,
} from '@odf/shared/types';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
+import validationRegEx from '@odf/shared/utils/validation';
import { useYupValidationResolver } from '@odf/shared/yup-validation-resolver';
import {
Dropdown,
@@ -30,7 +31,6 @@ import {
EmptyStateHeader,
} from '@patternfly/react-core';
import { CaretDownIcon } from '@patternfly/react-icons';
-import validationRegEx from '../../odf/utils/validation';
import { OCS_DEVICE_REPLICA, POOL_PROGRESS, POOL_STATE } from '../constants';
import { StorageClusterModel, CephBlockPoolModel } from '../models';
import {
diff --git a/packages/odf/components/bucket-class/wizard-pages/general-page.tsx b/packages/odf/components/bucket-class/wizard-pages/general-page.tsx
index 459ddcf67..0a9dbeb5a 100644
--- a/packages/odf/components/bucket-class/wizard-pages/general-page.tsx
+++ b/packages/odf/components/bucket-class/wizard-pages/general-page.tsx
@@ -8,6 +8,7 @@ import { TextInputWithFieldRequirements } from '@odf/shared/input-with-requireme
import { getName } from '@odf/shared/selectors';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import { isValidIP } from '@odf/shared/utils';
+import validationRegEx from '@odf/shared/utils/validation';
import { useYupValidationResolver } from '@odf/shared/yup-validation-resolver';
import { TFunction } from 'i18next';
import { useForm } from 'react-hook-form';
@@ -22,7 +23,6 @@ import {
} from '@patternfly/react-core';
import { NooBaaBucketClassModel } from '../../../models';
import { BucketClassKind, BucketClassType } from '../../../types';
-import validationRegEx from '../../../utils/validation';
import { ExternalLink } from '../../mcg-endpoints/gcp-endpoint-type';
import { Action, State } from '../state';
import '../create-bc.scss';
diff --git a/packages/odf/components/create-bs/create-bs.tsx b/packages/odf/components/create-bs/create-bs.tsx
index 7d393d2db..f289a2fe9 100644
--- a/packages/odf/components/create-bs/create-bs.tsx
+++ b/packages/odf/components/create-bs/create-bs.tsx
@@ -14,6 +14,7 @@ import { SecretModel } from '@odf/shared/models';
import { getName } from '@odf/shared/selectors';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import { referenceForModel } from '@odf/shared/utils';
+import validationRegEx from '@odf/shared/utils/validation';
import { useYupValidationResolver } from '@odf/shared/yup-validation-resolver';
import {
getAPIVersionForModel,
@@ -41,7 +42,6 @@ import {
getProviders,
secretPayloadCreator,
} from '../../utils';
-import validationRegEx from '../../utils/validation';
import { GCPEndpointType } from '../mcg-endpoints/gcp-endpoint-type';
import { PVCType } from '../mcg-endpoints/pvc-endpoint-type';
import { S3EndPointType } from '../mcg-endpoints/s3-endpoint-type';
diff --git a/packages/odf/components/create-storage-system/create-storage-system-steps/create-local-volume-set-step/body.tsx b/packages/odf/components/create-storage-system/create-storage-system-steps/create-local-volume-set-step/body.tsx
index 0e2f55642..03745553d 100644
--- a/packages/odf/components/create-storage-system/create-storage-system-steps/create-local-volume-set-step/body.tsx
+++ b/packages/odf/components/create-storage-system/create-storage-system-steps/create-local-volume-set-step/body.tsx
@@ -20,6 +20,7 @@ import { getName } from '@odf/shared/selectors';
import { NodeKind } from '@odf/shared/types';
import { StorageSystemKind } from '@odf/shared/types';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
+import validationRegEx from '@odf/shared/utils/validation';
import { useYupValidationResolver } from '@odf/shared/yup-validation-resolver';
import { SelectOption } from '@patternfly/react-core/deprecated';
import { TFunction } from 'i18next';
@@ -40,7 +41,6 @@ import {
HelperTextItem,
} from '@patternfly/react-core';
import { getValidatedDeviceTypes } from '../../../../utils';
-import validationRegEx from '../../../../utils/validation';
import { LocalVolumeSet, WizardDispatch, WizardState } from '../../reducer';
import { SelectNodesTable } from '../../select-nodes-table/select-nodes-table';
import '../../../../style.scss';
diff --git a/packages/odf/components/create-storage-system/create-storage-system-steps/create-storage-class-step/create-storage-class-step.tsx b/packages/odf/components/create-storage-system/create-storage-system-steps/create-storage-class-step/create-storage-class-step.tsx
index 40bf6b83d..6e2075c4c 100644
--- a/packages/odf/components/create-storage-system/create-storage-system-steps/create-storage-class-step/create-storage-class-step.tsx
+++ b/packages/odf/components/create-storage-system/create-storage-system-steps/create-storage-class-step/create-storage-class-step.tsx
@@ -24,11 +24,11 @@ import { getName } from '@odf/shared/selectors';
import { SecretKind, StorageClassResourceKind } from '@odf/shared/types';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import { isValidIP } from '@odf/shared/utils';
+import validationRegEx from '@odf/shared/utils/validation';
import { useYupValidationResolver } from '@odf/shared/yup-validation-resolver';
import { useForm } from 'react-hook-form';
import * as Yup from 'yup';
import { Form, TextContent, TextVariants, Text } from '@patternfly/react-core';
-import validationRegEx from '../../../../utils/validation';
import { WizardDispatch, WizardState } from '../../reducer';
import './create-storage-class-step.scss';
diff --git a/packages/odf/components/mcg/CreateObjectBucketClaim.tsx b/packages/odf/components/mcg/CreateObjectBucketClaim.tsx
index 7526331ae..9c3b89e4f 100644
--- a/packages/odf/components/mcg/CreateObjectBucketClaim.tsx
+++ b/packages/odf/components/mcg/CreateObjectBucketClaim.tsx
@@ -259,7 +259,6 @@ export const CreateOBCForm: React.FC = (props) => {
label: t('ObjectBucketClaim Name'),
fieldId: 'obc-name',
className: 'control-label',
- helperText: t('If not provided a generic name will be generated.'),
}}
textInputProps={{
id: 'obc-name',
@@ -270,6 +269,7 @@ export const CreateOBCForm: React.FC = (props) => {
'aria-describedby': 'obc-name-help',
'data-test': 'obc-name',
}}
+ helperText={t('If not provided a generic name will be generated.')}
/>
;
diff --git a/packages/odf/components/namespace-store/namespace-store-form.tsx b/packages/odf/components/namespace-store/namespace-store-form.tsx
index 96fbeb5b3..95b542857 100644
--- a/packages/odf/components/namespace-store/namespace-store-form.tsx
+++ b/packages/odf/components/namespace-store/namespace-store-form.tsx
@@ -12,6 +12,7 @@ import { PersistentVolumeClaimModel, SecretModel } from '@odf/shared/models';
import { getName } from '@odf/shared/selectors';
import { PersistentVolumeClaimKind, SecretKind } from '@odf/shared/types';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
+import validationRegEx from '@odf/shared/utils/validation';
import { useYupValidationResolver } from '@odf/shared/yup-validation-resolver';
import {
getAPIVersionForModel,
@@ -43,7 +44,6 @@ import {
getProviders,
secretPayloadCreator,
} from '../../utils';
-import validationRegEx from '../../utils/validation';
import { S3EndPointType } from '../mcg-endpoints/s3-endpoint-type';
import {
initialState,
diff --git a/packages/shared/src/input-with-requirements/TextInputWithFieldRequirements.tsx b/packages/shared/src/input-with-requirements/TextInputWithFieldRequirements.tsx
index 7f9de4813..ea26f7ed2 100644
--- a/packages/shared/src/input-with-requirements/TextInputWithFieldRequirements.tsx
+++ b/packages/shared/src/input-with-requirements/TextInputWithFieldRequirements.tsx
@@ -28,7 +28,9 @@ export type TextInputWithFieldRequirementsProps = {
fieldRequirements: string[];
control: Control;
defaultValue?: any;
- formGroupProps: FormGroupProps & { helperText?: string };
+ formGroupProps: FormGroupProps;
+ // In PF5 FormGroupProps don't have helperText
+ helperText?: string;
textInputProps: TextInputProps & {
'data-test': string;
disabled?: boolean;
@@ -71,6 +73,7 @@ const TextInputWithFieldRequirements: React.FC {
const {
field: { name, value, onChange, onBlur },
@@ -176,9 +179,7 @@ const TextInputWithFieldRequirements: React.FC
-
- {formGroupProps?.helperText}
-
+ {helperText}
diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts
index 8c37b9fce..22e667e27 100644
--- a/packages/shared/src/utils/index.ts
+++ b/packages/shared/src/utils/index.ts
@@ -13,3 +13,4 @@ export * from './link';
export * from './valid-k8s-resources';
export * from './table';
export * from './namespace';
+export * from './validation';
diff --git a/packages/odf/utils/validation.ts b/packages/shared/src/utils/validation.ts
similarity index 100%
rename from packages/odf/utils/validation.ts
rename to packages/shared/src/utils/validation.ts