Skip to content

Commit

Permalink
Added required field check in wizard flow
Browse files Browse the repository at this point in the history
Signed-off-by: Gowtham Shanmugasundaram <[email protected]>
  • Loading branch information
GowthamShanmugam committed Sep 12, 2023
1 parent 77210bd commit 0090f7d
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 111 deletions.
18 changes: 9 additions & 9 deletions locales/en/plugin__odf-console.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,16 +204,14 @@
"Secure your application by assigning a policy from the available policy templates.": "Secure your application by assigning a policy from the available policy templates.",
"Manage data policy": "Manage data policy",
"Assign a policy to protect the application and to ensure a quick recovery.": "Assign a policy to protect the application and to ensure a quick recovery.",
"Policy": "Policy",
"PersistentVolumeClaim": "PersistentVolumeClaim",
"Review and assign": "Review and assign",
"Assign": "Assign",
"New policy assigned to application.": "New policy assigned to application.",
"Unable to assign policy to application.": "Unable to assign policy to application.",
"Next": "Next",
"Back": "Back",
"Assign policy nav": "Assign policy nav",
"Assign policy content": "Assign policy content",
"1 or more mandatory fields are empty. To proceed, fill in the required information.": "1 or more mandatory fields are empty. To proceed, fill in the required information.",
"Assign": "Assign",
"Next": "Next",
"Back": "Back",
"Manage list view alert": "Manage list view alert",
"Confirm unassign": "Confirm unassign",
"All placements": "All placements",
Expand All @@ -233,12 +231,12 @@
"No activity": "No activity",
"No assigned data policy found": "No assigned data policy found",
"Delete": "Delete",
"Required": "Required",
"Select a placement": "Select a placement",
"{{count}} selected_one": "{{count}} selected",
"{{count}} selected_other": "{{count}} selected",
"Select labels": "Select labels",
"Use PVC label selectors to effortlessly specify the application resources that need protection.": "Use PVC label selectors to effortlessly specify the application resources that need protection.",
"If no label is provided, all PVCs will be protected. Define your preferences to protect specific resources.": "If no label is provided, all PVCs will be protected. Define your preferences to protect specific resources.",
"Application resource": "Application resource",
"Add application resource": "Add application resource",
"Select a policy": "Select a policy",
Expand Down Expand Up @@ -288,6 +286,9 @@
"minutes": "minutes",
"hours": "hours",
"days": "days",
"Policy": "Policy",
"PersistentVolumeClaim": "PersistentVolumeClaim",
"Review and assign": "Review and assign",
"{{async}}, interval: {{interval}}": "{{async}}, interval: {{interval}}",
"In use: {{targetClusters}}": "In use: {{targetClusters}}",
"Used: {{targetClusters}}": "Used: {{targetClusters}}",
Expand Down Expand Up @@ -1155,6 +1156,5 @@
"Cannot change resource name (original: \"{{name}}\", updated: \"{{newName}}\").": "Cannot change resource name (original: \"{{name}}\", updated: \"{{newName}}\").",
"Cannot change resource namespace (original: \"{{namespace}}\", updated: \"{{newNamespace}}\").": "Cannot change resource namespace (original: \"{{namespace}}\", updated: \"{{newNamespace}}\").",
"Cannot change resource kind (original: \"{{original}}\", updated: \"{{updated}}\").": "Cannot change resource kind (original: \"{{original}}\", updated: \"{{updated}}\").",
"Cannot change API group (original: \"{{apiGroup}}\", updated: \"{{newAPIGroup}}\").": "Cannot change API group (original: \"{{apiGroup}}\", updated: \"{{newAPIGroup}}\").",
"Required": "Required"
"Cannot change API group (original: \"{{apiGroup}}\", updated: \"{{newAPIGroup}}\").": "Cannot change API group (original: \"{{apiGroup}}\", updated: \"{{newAPIGroup}}\")."
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as React from 'react';
import { AssignPolicySteps, AssignPolicyStepsNames } from '@odf/mco/constants';
import { createRefFromK8Resource } from '@odf/mco/utils';
import { ModalBody } from '@odf/shared/modals/Modal';
import { getName } from '@odf/shared/selectors';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import { getErrorMessage } from '@odf/shared/utils';
import { TFunction } from 'i18next';
import { Wizard, WizardStep, AlertVariant } from '@patternfly/react-core';
import { AssignPolicyViewFooter } from './helper/assign-policy-view-footer';
import { PolicyConfigViewer } from './helper/policy-config-viewer';
import { PVCDetailsWizardContent } from './helper/pvc-details-wizard-content';
import { SelectPolicyWizardContent } from './helper/select-policy-wizard-content';
Expand All @@ -32,7 +33,7 @@ export const createSteps = (
matchingPolicies: DRPolicyType[],
state: AssignPolicyViewState,
stepIdReached: number,
isAssignDisabled: boolean,
isValidationEnabled: boolean,
t: TFunction,
setPolicy: (policy?: DataPolicyType) => void,
setDRPlacementControls: (
Expand All @@ -41,40 +42,36 @@ export const createSteps = (
): WizardStep[] => [
{
id: 1,
name: t('Policy'),
name: AssignPolicyStepsNames(t)[AssignPolicySteps.Policy],
component: (
<SelectPolicyWizardContent
matchingPolicies={matchingPolicies}
policy={state.policy}
isValidationEnabled={isValidationEnabled}
setPolicy={setPolicy}
/>
),
enableNext: !!getName(state.policy),
},
{
id: 2,
name: t('PersistentVolumeClaim'),
name: AssignPolicyStepsNames(t)[AssignPolicySteps.PersistentVolumeClaim],
component: (
<PVCDetailsWizardContent
placementControInfo={state.policy?.placementControlInfo}
unProtectedPlacements={unProtectedPlacements}
policyRef={createRefFromK8Resource(state.policy)}
workloadNamespace={workloadNamespace}
isValidationEnabled={isValidationEnabled}
policyRef={createRefFromK8Resource(state.policy)}
setDRPlacementControls={setDRPlacementControls}
/>
),
canJumpTo: stepIdReached >= 2,
enableNext: !!state.policy?.placementControlInfo?.length,
},
{
id: 3,
name: t('Review and assign'),
name: AssignPolicyStepsNames(t)[AssignPolicySteps.ReviewAndAssign],
component: <PolicyConfigViewer policy={state.policy} hideSelector={true} />,
nextButtonText: t('Assign'),
canJumpTo: stepIdReached >= 3,
enableNext: !isAssignDisabled,
hideBackButton: isAssignDisabled,
hideCancelButton: isAssignDisabled,
},
];

Expand All @@ -89,16 +86,7 @@ export const AssignPolicyView: React.FC<AssignPolicyViewProps> = ({
}) => {
const { t } = useCustomTranslation();
const [stepIdReached, setStepIdReached] = React.useState(1);
const [isAssignDisabled, setAssignDisabled] = React.useState(false);

const onNext = ({ id }: WizardStep) => {
if (id) {
if (typeof id === 'string') {
id = parseInt(id, 10);
}
setStepIdReached(stepIdReached < id ? id : stepIdReached);
}
};
const [isValidationEnabled, setIsValidationEnabled] = React.useState(false);

const setPolicy = (policy: DataPolicyType = null) =>
dispatch({
Expand All @@ -116,8 +104,7 @@ export const AssignPolicyView: React.FC<AssignPolicyViewProps> = ({
payload: drPlacementControls,
});

const assignPolicy = () => {
setAssignDisabled(true);
const onSubmit = async () => {
const updateContext = (
title: string,
description: string,
Expand All @@ -141,7 +128,7 @@ export const AssignPolicyView: React.FC<AssignPolicyViewProps> = ({
};
// assign DRPolicy
const promises = assignPromises(state.policy);
Promise.all(promises)
await Promise.all(promises)
.then(() => {
updateContext(
t('New policy assigned to application.'),
Expand All @@ -160,32 +147,39 @@ export const AssignPolicyView: React.FC<AssignPolicyViewProps> = ({
});
};

const onClose = () => {
setModalContext(ModalViewContext.POLICY_LIST_VIEW);
// reset policy info
setPolicy();
};

return (
<ModalBody>
<Wizard
cancelButtonText={t('Cancel')}
nextButtonText={t('Next')}
backButtonText={t('Back')}
navAriaLabel={t('Assign policy nav')}
mainAriaLabel={t('Assign policy content')}
onSave={assignPolicy}
onClose={() => {
setModalContext(ModalViewContext.POLICY_LIST_VIEW);
// reset policy info
setPolicy();
}}
steps={createSteps(
applicaitonInfo.workloadNamespace,
applicaitonInfo.placements,
matchingPolicies,
state,
stepIdReached,
isAssignDisabled,
isValidationEnabled,
t,
setPolicy,
setDRPlacementControls
)}
onNext={onNext}
footer={
<AssignPolicyViewFooter
dataPolicy={state.policy}
stepIdReached={stepIdReached}
isValidationEnabled={isValidationEnabled}
setStepIdReached={setStepIdReached}
onSubmit={onSubmit}
onCancel={onClose}
setIsValidationEnabled={setIsValidationEnabled}
/>
}
height={450}
/>
</ModalBody>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as React from 'react';
import { AssignPolicySteps, AssignPolicyStepsNames } from '@odf/mco/constants';
import { getName } from '@odf/shared/selectors';
import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook';
import { TFunction } from 'i18next';
import {
Button,
WizardContextType,
WizardContext,
WizardFooter,
Alert,
AlertVariant,
} from '@patternfly/react-core';
import { DRPolicyType, DataPolicyType } from '../utils/types';
import '../../../../style.scss';
import '../style.scss';

const isPVCSelectorFound = (dataPolicy: DRPolicyType) =>
!!dataPolicy?.placementControlInfo?.length &&
!!dataPolicy.placementControlInfo.every((drpc) => !!drpc.pvcSelector?.length);

const isDRPolicySelected = (dataPolicy: DRPolicyType) => !!getName(dataPolicy);

const canJumpToNextStep = (
stepName: string,
dataPolicy: DataPolicyType,
t: TFunction
) => {
switch (stepName) {
case AssignPolicyStepsNames(t)[AssignPolicySteps.Policy]:
return isDRPolicySelected(dataPolicy);
case AssignPolicyStepsNames(t)[AssignPolicySteps.PersistentVolumeClaim]:
return isPVCSelectorFound(dataPolicy);
default:
return false;
}
};

export const AssignPolicyViewFooter: React.FC<AssignPolicyViewFooterProps> = ({
dataPolicy,
stepIdReached,
isValidationEnabled,
setStepIdReached,
onSubmit,
onCancel,
setIsValidationEnabled,
}) => {
const { t } = useCustomTranslation();
const [requestInProgress, setRequestInProgress] = React.useState(false);
const { activeStep, onNext, onBack } =
React.useContext<WizardContextType>(WizardContext);

const stepId = activeStep.id as number;
const stepName = activeStep.name as string;

const canJumpToNext = canJumpToNextStep(stepName, dataPolicy, t);
const validationError = isValidationEnabled && !canJumpToNext;

const moveToNextStep = () => {
if (canJumpToNext) {
setStepIdReached(stepIdReached <= stepId ? stepId + 1 : stepIdReached);
onNext();
setIsValidationEnabled(false);
} else {
setIsValidationEnabled(true);
}
};

const handleNext = async () => {
switch (stepName) {
case AssignPolicyStepsNames(t)[AssignPolicySteps.ReviewAndAssign]:
setRequestInProgress(true);
await onSubmit();
setRequestInProgress(false);
break;
default:
moveToNextStep();
}
};

return (
<>
{validationError && (
<Alert
title={t(
'1 or more mandatory fields are empty. To proceed, fill in the required information.'
)}
variant={AlertVariant.danger}
isInline
className="odf-alert mco-manage-policies__alert--margin-left"
/>
)}
<WizardFooter>
<Button
isLoading={requestInProgress}
isDisabled={requestInProgress || validationError}
variant="primary"
type="submit"
onClick={handleNext}
>
{stepName ===
AssignPolicyStepsNames(t)[AssignPolicySteps.ReviewAndAssign]
? t('Assign')
: t('Next')}
</Button>
{/* Disabling the back button for the first step (Policy) in wizard */}
<Button
variant="secondary"
onClick={onBack}
isDisabled={
stepName === AssignPolicyStepsNames(t)[AssignPolicySteps.Policy] ||
requestInProgress
}
>
{t('Back')}
</Button>
<Button
variant="link"
onClick={onCancel}
isDisabled={requestInProgress}
>
{t('Cancel')}
</Button>
</WizardFooter>
</>
);
};

type AssignPolicyViewFooterProps = {
dataPolicy: DataPolicyType;
stepIdReached: number;
isValidationEnabled: boolean;
setStepIdReached: React.Dispatch<React.SetStateAction<number>>;
onSubmit: () => Promise<void>;
onCancel: () => void;
setIsValidationEnabled: React.Dispatch<React.SetStateAction<boolean>>;
};
Loading

0 comments on commit 0090f7d

Please sign in to comment.