Fix deprecated wizards (#29453)

* updated wizards

Signed-off-by: mfrances <mfrances@redhat.com>

* fix broken tests

Signed-off-by: mfrances <mfrances@redhat.com>

---------

Signed-off-by: mfrances <mfrances@redhat.com>
This commit is contained in:
Mark Franceschelli 2024-05-22 08:18:28 -04:00 committed by GitHub
parent e284972d7a
commit bc82e7eb3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 199 additions and 269 deletions

View file

@ -1255,10 +1255,10 @@ describe("Clients test", () => {
createClientPage.fillClientData(clientId); createClientPage.fillClientData(clientId);
cy.checkA11y(); cy.checkA11y();
cy.findByTestId("next").click(); createClientPage.continue();
cy.checkA11y(); cy.checkA11y();
cy.findByTestId("next").click(); createClientPage.continue();
cy.checkA11y(); cy.checkA11y();
}); });

View file

@ -55,10 +55,10 @@ export default class CreateClientPage extends CommonPage {
#actionDrpDwn = "action-dropdown"; #actionDrpDwn = "action-dropdown";
#deleteClientBtn = "delete-client"; #deleteClientBtn = "delete-client";
#saveBtn = "save"; #saveBtn = "Save";
#continueBtn = "next"; #continueBtn = "Next";
#backBtn = "back"; #backBtn = "Back";
#cancelBtn = "cancel"; #cancelBtn = "Cancel";
//#region General Settings //#region General Settings
selectClientType(clientType: string) { selectClientType(clientType: string) {
@ -174,25 +174,25 @@ export default class CreateClientPage extends CommonPage {
//#endregion //#endregion
save() { save() {
cy.findByTestId(this.#saveBtn).click(); cy.contains("button", this.#saveBtn).click();
return this; return this;
} }
continue() { continue() {
cy.findByTestId(this.#continueBtn).click(); cy.contains("button", this.#continueBtn).click();
return this; return this;
} }
back() { back() {
cy.findByTestId(this.#backBtn).click(); cy.contains("button", this.#backBtn).click();
return this; return this;
} }
cancel() { cancel() {
cy.findByTestId(this.#cancelBtn).click(); cy.contains("button", this.#cancelBtn).click();
return this; return this;
} }

View file

@ -1,10 +1,11 @@
import { AlertVariant, Button, PageSection } from "@patternfly/react-core";
import { import {
AlertVariant,
PageSection,
useWizardContext,
Wizard, Wizard,
WizardContextConsumer,
WizardFooter, WizardFooter,
} from "@patternfly/react-core/deprecated"; WizardStep,
import { useState } from "react"; } from "@patternfly/react-core";
import { FormProvider, useForm } from "react-hook-form"; import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
@ -21,6 +22,32 @@ import { CapabilityConfig } from "./CapabilityConfig";
import { GeneralSettings } from "./GeneralSettings"; import { GeneralSettings } from "./GeneralSettings";
import { LoginSettings } from "./LoginSettings"; import { LoginSettings } from "./LoginSettings";
const NewClientFooter = (newClientForm: any) => {
const { t } = useTranslation();
const { trigger } = newClientForm;
const { activeStep, goToNextStep, goToPrevStep, close } = useWizardContext();
const forward = async (onNext: () => void) => {
if (!(await trigger())) {
return;
}
onNext?.();
};
return (
<WizardFooter
activeStep={activeStep}
onNext={() => forward(goToNextStep)}
onBack={goToPrevStep}
onClose={close}
isBackDisabled={activeStep.index === 1}
backButtonText={t("back")}
nextButtonText={t("next")}
cancelButtonText={t("cancel")}
/>
);
};
export default function NewClientForm() { export default function NewClientForm() {
const { adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
@ -28,8 +55,6 @@ export default function NewClientForm() {
const { realm } = useRealm(); const { realm } = useRealm();
const navigate = useNavigate(); const navigate = useNavigate();
const [step, setStep] = useState(0);
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
const form = useForm<FormFields>({ const form = useForm<FormFields>({
defaultValues: { defaultValues: {
@ -49,7 +74,7 @@ export default function NewClientForm() {
}, },
}, },
}); });
const { getValues, watch, trigger } = form; const { getValues, watch } = form;
const protocol = watch("protocol"); const protocol = watch("protocol");
const save = async () => { const save = async () => {
@ -66,33 +91,6 @@ export default function NewClientForm() {
} }
}; };
const forward = async (onNext?: () => void) => {
if (!(await trigger())) {
return;
}
if (!isFinalStep()) {
setStep(step + 1);
}
onNext?.();
};
const isFinalStep = () =>
protocol === "openid-connect" ? step === 2 : step === 1;
const back = () => {
setStep(step - 1);
};
const onGoToStep = (newStep: { id?: string | number }) => {
if (newStep.id === "generalSettings") {
setStep(0);
} else if (newStep.id === "capabilityConfig") {
setStep(1);
} else {
setStep(2);
}
};
const title = t("createClient"); const title = t("createClient");
return ( return (
<> <>
@ -102,75 +100,39 @@ export default function NewClientForm() {
<Wizard <Wizard
onClose={() => navigate(toClients({ realm }))} onClose={() => navigate(toClients({ realm }))}
navAriaLabel={`${title} steps`} navAriaLabel={`${title} steps`}
mainAriaLabel={`${title} content`}
steps={[
{
id: "generalSettings",
name: t("generalSettings"),
component: <GeneralSettings />,
},
...(protocol !== "saml"
? [
{
id: "capabilityConfig",
name: t("capabilityConfig"),
component: <CapabilityConfig protocol={protocol} />,
canJumpTo: step >= 1,
},
]
: []),
{
id: "loginSettings",
name: t("loginSettings"),
component: (
<FormAccess isHorizontal role="manage-clients">
<LoginSettings protocol={protocol} />
</FormAccess>
),
canJumpTo: step >= 1,
},
]}
footer={
<WizardFooter>
<WizardContextConsumer>
{({ activeStep, onNext, onBack, onClose }) => (
<>
<Button
variant="primary"
data-testid={isFinalStep() ? "save" : "next"}
type="submit"
onClick={() => {
forward(onNext);
}}
>
{isFinalStep() ? t("save") : t("next")}
</Button>
<Button
variant="secondary"
data-testid="back"
onClick={() => {
back();
onBack();
}}
isDisabled={activeStep.name === t("generalSettings")}
>
{t("back")}
</Button>
<Button
data-testid="cancel"
variant="link"
onClick={onClose}
>
{t("cancel")}
</Button>
</>
)}
</WizardContextConsumer>
</WizardFooter>
}
onSave={save} onSave={save}
onGoToStep={onGoToStep} footer={<NewClientFooter {...form} />}
/> >
<WizardStep
name={t("generalSettings")}
id="generalSettings"
key="generalSettings"
>
<GeneralSettings />
</WizardStep>
<WizardStep
name={t("capabilityConfig")}
id="capabilityConfig"
key="capabilityConfig"
isHidden={protocol === "saml"}
>
<CapabilityConfig protocol={protocol} />
</WizardStep>
<WizardStep
name={t("loginSettings")}
id="loginSettings"
key="loginSettings"
footer={{
backButtonText: t("back"),
nextButtonText: t("save"),
cancelButtonText: t("cancel"),
}}
>
<FormAccess isHorizontal role="manage-clients">
<LoginSettings protocol={protocol} />
</FormAccess>
</WizardStep>
</Wizard>
</FormProvider> </FormProvider>
</PageSection> </PageSection>
</> </>

View file

@ -1,4 +1,9 @@
import { Wizard } from "@patternfly/react-core/deprecated"; import {
useWizardContext,
Wizard,
WizardFooter,
WizardStep,
} from "@patternfly/react-core/";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { KerberosSettingsRequired } from "./kerberos/KerberosSettingsRequired"; import { KerberosSettingsRequired } from "./kerberos/KerberosSettingsRequired";
@ -6,36 +11,50 @@ import { SettingsCache } from "./shared/SettingsCache";
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation"; import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
const UserFedKerberosFooter = () => {
const { t } = useTranslation();
const { activeStep, goToNextStep, goToPrevStep, close } = useWizardContext();
return (
<WizardFooter
activeStep={activeStep}
onNext={goToNextStep}
onBack={goToPrevStep}
onClose={close}
isBackDisabled={activeStep.index === 1}
backButtonText={t("back")}
nextButtonText={t("next")}
cancelButtonText={t("cancel")}
/>
);
};
export const UserFederationKerberosWizard = () => { export const UserFederationKerberosWizard = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const form = useForm<ComponentRepresentation>({ mode: "onChange" }); const form = useForm<ComponentRepresentation>({ mode: "onChange" });
const steps = [ return (
{ <Wizard height="100%" footer={<UserFedKerberosFooter />}>
name: t("requiredSettings"), <WizardStep
component: ( name={t("requiredSettings")}
id="kerberosRequiredSettingsStep"
>
<KerberosSettingsRequired <KerberosSettingsRequired
form={form} form={form}
showSectionHeading showSectionHeading
showSectionDescription showSectionDescription
/> />
), </WizardStep>
}, <WizardStep
{ name={t("cacheSettings")}
name: t("cacheSettings"), id="cacheSettingsStep"
component: ( footer={{
backButtonText: t("back"),
nextButtonText: t("finish"),
cancelButtonText: t("cancel"),
}}
>
<SettingsCache form={form} showSectionHeading showSectionDescription /> <SettingsCache form={form} showSectionHeading showSectionDescription />
), </WizardStep>
nextButtonText: t("finish"), // TODO: needs to disable until cache policy is valid </Wizard>
},
];
return (
<Wizard
// Because this is an inline wizard, this title and description should be put into the page. Specifying them here causes the wizard component to make a header that would be used on a modal.
// title={t("addKerberosWizardTitle")}
// description={helpText("addKerberosWizardDescription")}
steps={steps}
/>
); );
}; };

View file

@ -1,10 +1,12 @@
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation"; import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import { Button } from "@patternfly/react-core";
import { import {
Button,
useWizardContext,
Wizard, Wizard,
WizardContextConsumer,
WizardFooter, WizardFooter,
} from "@patternfly/react-core/deprecated"; WizardFooterWrapper,
WizardStep,
} from "@patternfly/react-core";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -17,173 +19,120 @@ import { LdapSettingsSearching } from "./ldap/LdapSettingsSearching";
import { LdapSettingsSynchronization } from "./ldap/LdapSettingsSynchronization"; import { LdapSettingsSynchronization } from "./ldap/LdapSettingsSynchronization";
import { SettingsCache } from "./shared/SettingsCache"; import { SettingsCache } from "./shared/SettingsCache";
const UserFedLdapFooter = () => {
const { t } = useTranslation();
const { activeStep, goToNextStep, goToPrevStep, close } = useWizardContext();
return (
<WizardFooter
activeStep={activeStep}
onNext={goToNextStep}
onBack={goToPrevStep}
onClose={close}
isBackDisabled={activeStep.index === 1}
backButtonText={t("back")}
nextButtonText={t("next")}
cancelButtonText={t("cancel")}
/>
);
};
const SkipCustomizationFooter = () => {
const { goToNextStep, goToPrevStep, close } = useWizardContext();
const { t } = useTranslation();
return (
<WizardFooterWrapper>
<Button variant="secondary" onClick={goToPrevStep}>
{t("back")}
</Button>
<Button variant="primary" type="submit" onClick={goToNextStep}>
{t("next")}
</Button>
{/* TODO: validate last step and finish */}
<Button variant="link">{t("skipCustomizationAndFinish")}</Button>
<Button variant="link" onClick={close}>
{t("cancel")}
</Button>
</WizardFooterWrapper>
);
};
export const UserFederationLdapWizard = () => { export const UserFederationLdapWizard = () => {
const form = useForm<ComponentRepresentation>(); const form = useForm<ComponentRepresentation>();
const { t } = useTranslation(); const { t } = useTranslation();
const isFeatureEnabled = useIsFeatureEnabled(); const isFeatureEnabled = useIsFeatureEnabled();
const steps = [ return (
{ <Wizard height="100%" footer={<UserFedLdapFooter />}>
name: t("requiredSettings"), <WizardStep name={t("requiredSettings")} id="ldapRequiredSettingsStep">
id: "ldapRequiredSettingsStep",
component: (
<LdapSettingsGeneral <LdapSettingsGeneral
form={form} form={form}
showSectionHeading showSectionHeading
showSectionDescription showSectionDescription
/> />
), </WizardStep>
}, <WizardStep
{ name={t("connectionAndAuthenticationSettings")}
name: t("connectionAndAuthenticationSettings"), id="ldapConnectionSettingsStep"
id: "ldapConnectionSettingsStep", >
component: (
<LdapSettingsConnection <LdapSettingsConnection
form={form} form={form}
showSectionHeading showSectionHeading
showSectionDescription showSectionDescription
/> />
), </WizardStep>
}, <WizardStep
{ name={t("ldapSearchingAndUpdatingSettings")}
name: t("ldapSearchingAndUpdatingSettings"), id="ldapSearchingSettingsStep"
id: "ldapSearchingSettingsStep", >
component: (
<LdapSettingsSearching <LdapSettingsSearching
form={form} form={form}
showSectionHeading showSectionHeading
showSectionDescription showSectionDescription
/> />
), </WizardStep>
}, <WizardStep
{ name={t("synchronizationSettings")}
name: t("synchronizationSettings"), id="ldapSynchronizationSettingsStep"
id: "ldapSynchronizationSettingsStep", footer={<SkipCustomizationFooter />}
component: ( >
<LdapSettingsSynchronization <LdapSettingsSynchronization
form={form} form={form}
showSectionHeading showSectionHeading
showSectionDescription showSectionDescription
/> />
), </WizardStep>
}, <WizardStep
{ name={t("kerberosIntegration")}
name: t("kerberosIntegration"), id="ldapKerberosIntegrationSettingsStep"
id: "ldapKerberosIntegrationSettingsStep", isDisabled={!isFeatureEnabled(Feature.Kerberos)}
component: ( footer={<SkipCustomizationFooter />}
>
<LdapSettingsKerberosIntegration <LdapSettingsKerberosIntegration
form={form} form={form}
showSectionHeading showSectionHeading
showSectionDescription showSectionDescription
/> />
), </WizardStep>
isDisabled: !isFeatureEnabled(Feature.Kerberos), <WizardStep
}, name={t("cacheSettings")}
{ id="ldapCacheSettingsStep"
name: t("cacheSettings"), footer={<SkipCustomizationFooter />}
id: "ldapCacheSettingsStep", >
component: (
<SettingsCache form={form} showSectionHeading showSectionDescription /> <SettingsCache form={form} showSectionHeading showSectionDescription />
), </WizardStep>
}, <WizardStep
{ name={t("advancedSettings")}
name: t("advancedSettings"), id="ldapAdvancedSettingsStep"
id: "ldapAdvancedSettingsStep", footer={{
component: ( backButtonText: t("back"),
nextButtonText: t("finish"),
cancelButtonText: t("cancel"),
}}
>
<LdapSettingsAdvanced <LdapSettingsAdvanced
form={form} form={form}
showSectionHeading showSectionHeading
showSectionDescription showSectionDescription
/> />
), </WizardStep>
}, </Wizard>
];
const footer = (
<WizardFooter>
<WizardContextConsumer>
{({ activeStep, onNext, onBack, onClose }) => {
// First step buttons
if (activeStep.id === "ldapRequiredSettingsStep") {
return (
<>
<Button variant="primary" type="submit" onClick={onNext}>
{t("next")}
</Button>
<Button
variant="secondary"
onClick={onBack}
className="pf-m-disabled"
>
{t("back")}
</Button>
<Button variant="link" onClick={onClose}>
{t("cancel")}
</Button>
</>
);
}
// Other required step buttons
else if (
activeStep.id === "ldapConnectionSettingsStep" ||
activeStep.id === "ldapSearchingSettingsStep"
) {
return (
<>
<Button variant="primary" type="submit" onClick={onNext}>
{t("next")}
</Button>
<Button variant="secondary" onClick={onBack}>
{t("back")}
</Button>
<Button variant="link" onClick={onClose}>
{t("cancel")}
</Button>
</>
);
}
// Last step buttons
else if (activeStep.id === "ldapAdvancedSettingsStep") {
return (
<>
{/* TODO: close the wizard and finish */}
<Button>{t("finish")}</Button>
<Button variant="secondary" onClick={onBack}>
{t("back")}
</Button>
<Button variant="link" onClick={onClose}>
{t("cancel")}
</Button>
</>
);
}
// All the other steps buttons
return (
<>
<Button onClick={onNext}>Next</Button>
<Button variant="secondary" onClick={onBack}>
Back
</Button>
{/* TODO: validate last step and finish */}
<Button variant="link">{t("skipCustomizationAndFinish")}</Button>
<Button variant="link" onClick={onClose}>
{t("cancel")}
</Button>
</>
);
}}
</WizardContextConsumer>
</WizardFooter>
);
return (
<Wizard
// Because this is an inline wizard, this title and description should be put into the page. Specifying them here causes the wizard component to make a header that would be used on a modal.
// title={t("addLdapWizardTitle")}
// description={helpText("addLdapWizardDescription")}
height="100%"
steps={steps}
footer={footer}
/>
); );
}; };