Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug 2236436: [release-4.14] Added required field check in wizard flow #1044

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions locales/en/plugin__odf-console.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,16 +210,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 @@ -239,12 +237,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 @@ -294,6 +292,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 @@ -1161,6 +1162,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