Made form readonly and fixed initialisation (#2070)

* add the ablity for dynamic component to become
disabeled when used in a <FormAccess component

* add ability to manually make form readonly

* added readonly + fixed form initialize

fixes: #1898

* Update src/components/form-access/FormAccess.tsx

Co-authored-by: Jon Koops <jonkoops@gmail.com>

Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
Erik Jan de Wit 2022-02-16 15:53:45 +01:00 committed by GitHub
parent a6904be9ff
commit c0a9b5cebc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 63 additions and 22 deletions

View file

@ -11,6 +11,7 @@ export const BooleanComponent = ({
label, label,
helpText, helpText,
defaultValue, defaultValue,
isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const { control } = useFormContext(); const { control } = useFormContext();
@ -32,6 +33,7 @@ export const BooleanComponent = ({
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Switch <Switch
id={name!} id={name!}
isDisabled={isDisabled}
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value === "true" || value === true} isChecked={value === "true" || value === true}

View file

@ -18,6 +18,7 @@ export const ClientSelectComponent = ({
label, label,
helpText, helpText,
defaultValue, defaultValue,
isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const { control } = useFormContext(); const { control } = useFormContext();
@ -66,6 +67,7 @@ export const ClientSelectComponent = ({
variant={SelectVariant.typeahead} variant={SelectVariant.typeahead}
onToggle={(open) => setOpen(open)} onToggle={(open) => setOpen(open)}
isOpen={open} isOpen={open}
isDisabled={isDisabled}
selections={value} selections={value}
onFilter={(_, value) => { onFilter={(_, value) => {
setSearch(value); setSearch(value);

View file

@ -9,13 +9,16 @@ type DynamicComponentProps = {
parentCallback?: (data: string[]) => void; parentCallback?: (data: string[]) => void;
}; };
export const DynamicComponents = ({ properties }: DynamicComponentProps) => ( export const DynamicComponents = ({
properties,
...rest
}: DynamicComponentProps) => (
<> <>
{properties.map((property) => { {properties.map((property) => {
const componentType = property.type!; const componentType = property.type!;
if (isValidComponentType(componentType) && property.name !== "scopes") { if (isValidComponentType(componentType) && property.name !== "scopes") {
const Component = COMPONENTS[componentType]; const Component = COMPONENTS[componentType];
return <Component key={property.name} {...property} />; return <Component key={property.name} {...property} {...rest} />;
} else { } else {
console.warn(`There is no editor registered for ${componentType}`); console.warn(`There is no editor registered for ${componentType}`);
} }

View file

@ -17,6 +17,7 @@ export const ListComponent = ({
helpText, helpText,
defaultValue, defaultValue,
options, options,
isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const { control } = useFormContext(); const { control } = useFormContext();
@ -38,6 +39,7 @@ export const ListComponent = ({
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId={name} toggleId={name}
isDisabled={isDisabled}
onToggle={(toggle) => setOpen(toggle)} onToggle={(toggle) => setOpen(toggle)}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value as string); onChange(value as string);

View file

@ -23,6 +23,7 @@ export const MultivaluedChipsComponent = ({
name, name,
label, label,
helpText, helpText,
isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const { control } = useFormContext(); const { control } = useFormContext();
@ -126,6 +127,7 @@ export const MultivaluedChipsComponent = ({
))} ))}
</ChipGroup> </ChipGroup>
<Button <Button
isDisabled={isDisabled}
data-testid="select-scope-button" data-testid="select-scope-button"
variant="secondary" variant="secondary"
onClick={() => { onClick={() => {

View file

@ -17,6 +17,7 @@ export const MultiValuedListComponent = ({
helpText, helpText,
defaultValue, defaultValue,
options, options,
isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const { control } = useFormContext(); const { control } = useFormContext();
@ -38,6 +39,7 @@ export const MultiValuedListComponent = ({
<Select <Select
toggleId={name} toggleId={name}
data-testid={name} data-testid={name}
isDisabled={isDisabled}
chipGroupProps={{ chipGroupProps={{
numChips: 3, numChips: 3,
expandedText: t("common:hide"), expandedText: t("common:hide"),

View file

@ -20,6 +20,7 @@ export const MultivaluedRoleComponent = ({
name, name,
label, label,
helpText, helpText,
isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const { whoAmI } = useWhoAmI(); const { whoAmI } = useWhoAmI();
@ -73,6 +74,7 @@ export const MultivaluedRoleComponent = ({
rules={{ required: true }} rules={{ required: true }}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
isDisabled={isDisabled}
onToggle={(isExpanded) => setOpen(isExpanded)} onToggle={(isExpanded) => setOpen(isExpanded)}
isOpen={open} isOpen={open}
className="kc-role-select" className="kc-role-select"

View file

@ -20,6 +20,7 @@ import type { EditClientPolicyConditionParams } from "../../realm-settings/route
export const MultivaluedScopesComponent = ({ export const MultivaluedScopesComponent = ({
defaultValue, defaultValue,
name, name,
isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const { control } = useFormContext(); const { control } = useFormContext();
@ -111,6 +112,7 @@ export const MultivaluedScopesComponent = ({
))} ))}
</ChipGroup> </ChipGroup>
<Button <Button
isDisabled={isDisabled}
data-testid="select-scope-button" data-testid="select-scope-button"
variant="secondary" variant="secondary"
onClick={() => { onClick={() => {

View file

@ -10,6 +10,7 @@ export const MultiValuedStringComponent = ({
name, name,
label, label,
helpText, helpText,
isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
@ -24,6 +25,7 @@ export const MultiValuedStringComponent = ({
<MultiLineInput <MultiLineInput
name={`config.${name}`} name={`config.${name}`}
aria-label={name} aria-label={name}
isDisabled={isDisabled}
addButtonLabel={t("addMultivaluedLabel", { addButtonLabel={t("addMultivaluedLabel", {
fieldLabel: t(label!).toLowerCase(), fieldLabel: t(label!).toLowerCase(),
})} })}

View file

@ -24,7 +24,12 @@ const RealmClient = (realm: string): ClientRepresentation => ({
clientId: realm, clientId: realm,
}); });
export const RoleComponent = ({ name, label, helpText }: ComponentProps) => { export const RoleComponent = ({
name,
label,
helpText,
isDisabled = false,
}: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
@ -143,6 +148,7 @@ export const RoleComponent = ({ name, label, helpText }: ComponentProps) => {
{clients && ( {clients && (
<Select <Select
toggleId={`group-${name}`} toggleId={`group-${name}`}
isDisabled={isDisabled}
onToggle={() => setClientsOpen(!clientsOpen)} onToggle={() => setClientsOpen(!clientsOpen)}
isOpen={clientsOpen} isOpen={clientsOpen}
variant={SelectVariant.typeahead} variant={SelectVariant.typeahead}

View file

@ -12,6 +12,7 @@ export const ScriptComponent = ({
label, label,
helpText, helpText,
defaultValue, defaultValue,
isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const { control } = useFormContext(); const { control } = useFormContext();
@ -35,6 +36,7 @@ export const ScriptComponent = ({
<CodeEditor <CodeEditor
id={name!} id={name!}
data-testid={name} data-testid={name}
isReadOnly={isDisabled}
type="text" type="text"
onChange={onChange} onChange={onChange}
code={value} code={value}

View file

@ -11,6 +11,7 @@ export const StringComponent = ({
label, label,
helpText, helpText,
defaultValue, defaultValue,
isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const { register } = useFormContext(); const { register } = useFormContext();
@ -26,6 +27,7 @@ export const StringComponent = ({
<TextInput <TextInput
id={name!} id={name!}
data-testid={name} data-testid={name}
isDisabled={isDisabled}
ref={register()} ref={register()}
type="text" type="text"
name={`config.${name}`} name={`config.${name}`}

View file

@ -12,7 +12,10 @@ import { MultiValuedStringComponent } from "./MultivaluedStringComponent";
import { MultiValuedListComponent } from "./MultivaluedListComponent"; import { MultiValuedListComponent } from "./MultivaluedListComponent";
import { GroupComponent } from "./GroupComponent"; import { GroupComponent } from "./GroupComponent";
export type ComponentProps = Omit<ConfigPropertyRepresentation, "type">; export type ComponentProps = Omit<ConfigPropertyRepresentation, "type"> & {
isDisabled?: boolean;
};
const ComponentTypes = [ const ComponentTypes = [
"String", "String",
"boolean", "boolean",

View file

@ -41,6 +41,11 @@ export type FormAccessProps = FormProps & {
* @type {boolean} * @type {boolean}
*/ */
unWrap?: boolean; unWrap?: boolean;
/**
* Overwrite the fineGrainedAccess and make form regardless of access rights.
*/
isReadOnly?: boolean;
}; };
/** /**
@ -51,6 +56,7 @@ export const FormAccess: FunctionComponent<FormAccessProps> = ({
children, children,
role, role,
fineGrainedAccess = false, fineGrainedAccess = false,
isReadOnly = false,
unWrap = false, unWrap = false,
...rest ...rest
}) => { }) => {
@ -107,7 +113,7 @@ export const FormAccess: FunctionComponent<FormAccessProps> = ({
}); });
}; };
const isDisabled = !hasAccess(role) && !fineGrainedAccess; const isDisabled = isReadOnly || (!hasAccess(role) && !fineGrainedAccess);
return ( return (
<> <>

View file

@ -24,7 +24,6 @@ import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/li
import { ClientProfileParams, toClientProfile } from "./routes/ClientProfile"; import { ClientProfileParams, toClientProfile } from "./routes/ClientProfile";
import { DynamicComponents } from "../components/dynamic/DynamicComponents"; import { DynamicComponents } from "../components/dynamic/DynamicComponents";
import type { ExecutorParams } from "./routes/Executor"; import type { ExecutorParams } from "./routes/Executor";
import { convertToFormValues } from "../util";
type ExecutorForm = { type ExecutorForm = {
config: object; config: object;
@ -57,28 +56,27 @@ export default function ExecutorForm() {
ClientProfileRepresentation[] ClientProfileRepresentation[]
>([]); >([]);
const [profiles, setProfiles] = useState<ClientProfileRepresentation[]>([]); const [profiles, setProfiles] = useState<ClientProfileRepresentation[]>([]);
const form = useForm({ defaultValues }); const form = useForm({ defaultValues, shouldUnregister: false });
const { control, setValue, handleSubmit } = form; const { control, reset, handleSubmit } = form;
const editMode = !!executorName; const editMode = !!executorName;
const setupForm = (profiles: ClientProfileRepresentation[]) => {
const profile = profiles.find((profile) => profile.name === profileName);
const executor = profile?.executors?.find(
(executor) => executor.executor === executorName
);
if (executor) reset({ config: executor.configuration });
};
useFetch( useFetch(
() => () =>
adminClient.clientPolicies.listProfiles({ includeGlobalProfiles: true }), adminClient.clientPolicies.listProfiles({ includeGlobalProfiles: true }),
(profiles) => { (profiles) => {
setGlobalProfiles(profiles.globalProfiles ?? []); setGlobalProfiles(profiles.globalProfiles!);
setProfiles(profiles.profiles ?? []); setProfiles(profiles.profiles!);
const profile = profiles.profiles!.find( setupForm(profiles.profiles!);
(profile) => profile.name === profileName setupForm(profiles.globalProfiles!);
);
const profileExecutor = profile?.executors!.find(
(executor) => executor.executor === executorName
);
if (profileExecutor) {
convertToFormValues(profileExecutor, setValue);
}
}, },
[] []
); );
@ -163,7 +161,12 @@ export default function ExecutorForm() {
divider divider
/> />
<PageSection variant="light"> <PageSection variant="light">
<FormAccess isHorizontal role="manage-realm" className="pf-u-mt-lg"> <FormAccess
isHorizontal
role="manage-realm"
className="pf-u-mt-lg"
isReadOnly={!!globalProfile}
>
<FormGroup <FormGroup
label={t("executorType")} label={t("executorType")}
fieldId="kc-executorType" fieldId="kc-executorType"