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);
cy.checkA11y();
cy.findByTestId("next").click();
createClientPage.continue();
cy.checkA11y();
cy.findByTestId("next").click();
createClientPage.continue();
cy.checkA11y();
});

View file

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

View file

@ -1,10 +1,11 @@
import { AlertVariant, Button, PageSection } from "@patternfly/react-core";
import {
AlertVariant,
PageSection,
useWizardContext,
Wizard,
WizardContextConsumer,
WizardFooter,
} from "@patternfly/react-core/deprecated";
import { useState } from "react";
WizardStep,
} from "@patternfly/react-core";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
@ -21,6 +22,32 @@ import { CapabilityConfig } from "./CapabilityConfig";
import { GeneralSettings } from "./GeneralSettings";
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() {
const { adminClient } = useAdminClient();
@ -28,8 +55,6 @@ export default function NewClientForm() {
const { realm } = useRealm();
const navigate = useNavigate();
const [step, setStep] = useState(0);
const { addAlert, addError } = useAlerts();
const form = useForm<FormFields>({
defaultValues: {
@ -49,7 +74,7 @@ export default function NewClientForm() {
},
},
});
const { getValues, watch, trigger } = form;
const { getValues, watch } = form;
const protocol = watch("protocol");
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");
return (
<>
@ -102,75 +100,39 @@ export default function NewClientForm() {
<Wizard
onClose={() => navigate(toClients({ realm }))}
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}
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>
</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 { 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 { 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 = () => {
const { t } = useTranslation();
const form = useForm<ComponentRepresentation>({ mode: "onChange" });
const steps = [
{
name: t("requiredSettings"),
component: (
return (
<Wizard height="100%" footer={<UserFedKerberosFooter />}>
<WizardStep
name={t("requiredSettings")}
id="kerberosRequiredSettingsStep"
>
<KerberosSettingsRequired
form={form}
showSectionHeading
showSectionDescription
/>
),
},
{
name: t("cacheSettings"),
component: (
</WizardStep>
<WizardStep
name={t("cacheSettings")}
id="cacheSettingsStep"
footer={{
backButtonText: t("back"),
nextButtonText: t("finish"),
cancelButtonText: t("cancel"),
}}
>
<SettingsCache form={form} showSectionHeading showSectionDescription />
),
nextButtonText: t("finish"), // TODO: needs to disable until cache policy is valid
},
];
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}
/>
</WizardStep>
</Wizard>
);
};

View file

@ -1,10 +1,12 @@
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import { Button } from "@patternfly/react-core";
import {
Button,
useWizardContext,
Wizard,
WizardContextConsumer,
WizardFooter,
} from "@patternfly/react-core/deprecated";
WizardFooterWrapper,
WizardStep,
} from "@patternfly/react-core";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
@ -17,173 +19,120 @@ import { LdapSettingsSearching } from "./ldap/LdapSettingsSearching";
import { LdapSettingsSynchronization } from "./ldap/LdapSettingsSynchronization";
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 = () => {
const form = useForm<ComponentRepresentation>();
const { t } = useTranslation();
const isFeatureEnabled = useIsFeatureEnabled();
const steps = [
{
name: t("requiredSettings"),
id: "ldapRequiredSettingsStep",
component: (
return (
<Wizard height="100%" footer={<UserFedLdapFooter />}>
<WizardStep name={t("requiredSettings")} id="ldapRequiredSettingsStep">
<LdapSettingsGeneral
form={form}
showSectionHeading
showSectionDescription
/>
),
},
{
name: t("connectionAndAuthenticationSettings"),
id: "ldapConnectionSettingsStep",
component: (
</WizardStep>
<WizardStep
name={t("connectionAndAuthenticationSettings")}
id="ldapConnectionSettingsStep"
>
<LdapSettingsConnection
form={form}
showSectionHeading
showSectionDescription
/>
),
},
{
name: t("ldapSearchingAndUpdatingSettings"),
id: "ldapSearchingSettingsStep",
component: (
</WizardStep>
<WizardStep
name={t("ldapSearchingAndUpdatingSettings")}
id="ldapSearchingSettingsStep"
>
<LdapSettingsSearching
form={form}
showSectionHeading
showSectionDescription
/>
),
},
{
name: t("synchronizationSettings"),
id: "ldapSynchronizationSettingsStep",
component: (
</WizardStep>
<WizardStep
name={t("synchronizationSettings")}
id="ldapSynchronizationSettingsStep"
footer={<SkipCustomizationFooter />}
>
<LdapSettingsSynchronization
form={form}
showSectionHeading
showSectionDescription
/>
),
},
{
name: t("kerberosIntegration"),
id: "ldapKerberosIntegrationSettingsStep",
component: (
</WizardStep>
<WizardStep
name={t("kerberosIntegration")}
id="ldapKerberosIntegrationSettingsStep"
isDisabled={!isFeatureEnabled(Feature.Kerberos)}
footer={<SkipCustomizationFooter />}
>
<LdapSettingsKerberosIntegration
form={form}
showSectionHeading
showSectionDescription
/>
),
isDisabled: !isFeatureEnabled(Feature.Kerberos),
},
{
name: t("cacheSettings"),
id: "ldapCacheSettingsStep",
component: (
</WizardStep>
<WizardStep
name={t("cacheSettings")}
id="ldapCacheSettingsStep"
footer={<SkipCustomizationFooter />}
>
<SettingsCache form={form} showSectionHeading showSectionDescription />
),
},
{
name: t("advancedSettings"),
id: "ldapAdvancedSettingsStep",
component: (
</WizardStep>
<WizardStep
name={t("advancedSettings")}
id="ldapAdvancedSettingsStep"
footer={{
backButtonText: t("back"),
nextButtonText: t("finish"),
cancelButtonText: t("cancel"),
}}
>
<LdapSettingsAdvanced
form={form}
showSectionHeading
showSectionDescription
/>
),
},
];
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}
/>
</WizardStep>
</Wizard>
);
};