From 4291f4b01bd568f98531dc5754fcc0195c56f222 Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Wed, 18 Oct 2023 18:24:30 +0200 Subject: [PATCH] Enhance user profile attribute form UX (#24083) Enhances the user experience for the create and edit form for User Profile attribute by making the following changes: - Prevents the scopes from being persisted if an attribute can always be shown. - Hides the scopes if 'Always' is selected, as the control is not interactive. - Sets the attribute to be editable by admins by default. Closes #24081 Closes #23790 Co-authored-by: Pedro Igor --- .../realm-settings/NewAttributeSettings.tsx | 72 +++-- .../attribute/AttributeGeneralSettings.tsx | 260 ++++++++---------- 2 files changed, 159 insertions(+), 173 deletions(-) diff --git a/js/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx b/js/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx index 7d1bfef6fa..4c5937125c 100644 --- a/js/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx +++ b/js/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx @@ -42,7 +42,7 @@ export type IndexedValidations = { value?: Record; }; -type UserProfileAttributeType = Omit< +type UserProfileAttributeFormFields = Omit< UserProfileAttribute, "validations" | "annotations" > & @@ -50,6 +50,8 @@ type UserProfileAttributeType = Omit< Permission & { validations: IndexedValidations[]; annotations: IndexedAnnotations[]; + hasSelector: boolean; + hasRequiredScopes: boolean; }; type Attribute = { @@ -123,7 +125,7 @@ const CreateAttributeFormContent = ({ export default function NewAttributeSettings() { const { realm, attributeName } = useParams(); - const form = useForm(); + const form = useForm(); const { t } = useTranslation(); const navigate = useNavigate(); const { addAlert, addError } = useAlerts(); @@ -141,11 +143,17 @@ export default function NewAttributeSettings() { selector, required, ...values - } = - config.attributes!.find( - (attribute) => attribute.name === attributeName, - ) || {}; - convertToFormValues(values, form.setValue); + } = config.attributes!.find( + (attribute) => attribute.name === attributeName, + ) || { permissions: { edit: ["admin"] } }; + convertToFormValues( + { + ...values, + hasSelector: typeof selector !== "undefined", + hasRequiredScopes: typeof required?.scopes !== "undefined", + }, + form.setValue, + ); Object.entries( flatten({ permissions, selector, required }, { safe: true }), ).map(([key, value]) => form.setValue(key as any, value)); @@ -168,8 +176,20 @@ export default function NewAttributeSettings() { [], ); - const save = async (profileConfig: UserProfileAttributeType) => { - const validations = profileConfig.validations.reduce( + const save = async ({ + hasSelector, + hasRequiredScopes, + ...formFields + }: UserProfileAttributeFormFields) => { + if (!hasSelector) { + delete formFields.selector; + } + + if (!hasRequiredScopes) { + delete formFields.required?.scopes; + } + + const validations = formFields.validations.reduce( (prevValidations, currentValidations) => { prevValidations[currentValidations.key] = currentValidations.value || {}; @@ -178,7 +198,7 @@ export default function NewAttributeSettings() { {} as Record, ); - const annotations = profileConfig.annotations.reduce( + const annotations = formFields.annotations.reduce( (obj, item) => Object.assign(obj, { [item.key]: item.value }), {}, ); @@ -194,18 +214,14 @@ export default function NewAttributeSettings() { { ...attribute, name: attributeName, - displayName: profileConfig.displayName!, - selector: profileConfig.selector, - permissions: profileConfig.permissions!, + displayName: formFields.displayName!, + selector: formFields.selector, + permissions: formFields.permissions!, annotations, validations, }, - profileConfig.isRequired - ? { required: profileConfig.required } - : undefined, - profileConfig.group - ? { group: profileConfig.group } - : { group: null }, + formFields.isRequired ? { required: formFields.required } : undefined, + formFields.group ? { group: formFields.group } : { group: null }, ); }); @@ -213,20 +229,16 @@ export default function NewAttributeSettings() { config?.attributes!.concat([ Object.assign( { - name: profileConfig.name, - displayName: profileConfig.displayName!, - required: profileConfig.isRequired - ? profileConfig.required - : undefined, - selector: profileConfig.selector, - permissions: profileConfig.permissions!, + name: formFields.name, + displayName: formFields.displayName!, + required: formFields.isRequired ? formFields.required : undefined, + selector: formFields.selector, + permissions: formFields.permissions!, annotations, validations, }, - profileConfig.isRequired - ? { required: profileConfig.required } - : undefined, - profileConfig.group ? { group: profileConfig.group } : undefined, + formFields.isRequired ? { required: formFields.required } : undefined, + formFields.group ? { group: formFields.group } : undefined, ), ] as UserProfileAttribute); diff --git a/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeGeneralSettings.tsx b/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeGeneralSettings.tsx index 3df5c78765..d324e55601 100644 --- a/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeGeneralSettings.tsx +++ b/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeGeneralSettings.tsx @@ -45,16 +45,14 @@ export const AttributeGeneralSettings = () => { const { attributeName } = useParams(); const editMode = attributeName ? true : false; - const selectedScopes = useWatch({ + const hasSelector = useWatch({ control: form.control, - name: "selector.scopes", - defaultValue: [], + name: "hasSelector", }); - const requiredScopes = useWatch({ + const hasRequiredScopes = useWatch({ control: form.control, - name: "required.scopes", - defaultValue: [], + name: "hasRequiredScopes", }); const required = useWatch({ @@ -69,6 +67,15 @@ export const AttributeGeneralSettings = () => { if (!clientScopes) { return ; } + + function setHasSelector(hasSelector: boolean) { + form.setValue("hasSelector", hasSelector); + } + + function setHasRequiredScopes(hasRequiredScopes: boolean) { + form.setValue("hasRequiredScopes", hasRequiredScopes); + } + return ( { { - if (value) { - form.setValue( - "selector.scopes", - clientScopes.map((s) => s.name), - ); - } else { - form.setValue("selector.scopes", []); - } - }} + onChange={() => setHasSelector(false)} className="pf-u-mb-md" /> { - if (value) { - form.setValue("selector.scopes", []); - } else { - form.setValue( - "selector.scopes", - clientScopes.map((s) => s.name), - ); - } - }} + onChange={() => setHasSelector(true)} className="pf-u-mb-md" /> - - s.name)} - render={({ field }) => ( - - )} - /> - + {hasSelector && ( + + ( + + )} + /> + + )} { { - if (value) { - form.setValue( - "required.scopes", - clientScopes.map((s) => s.name), - ); - } else { - form.setValue("required.scopes", []); - } - }} + onChange={() => setHasRequiredScopes(false)} className="pf-u-mb-md" /> { - if (value) { - form.setValue("required.scopes", []); - } else { - form.setValue( - "required.scopes", - clientScopes.map((s) => s.name), - ); - } - }} + onChange={() => setHasRequiredScopes(true)} className="pf-u-mb-md" /> - - ( - - )} - /> - + {hasRequiredScopes && ( + + ( + + )} + /> + + )} )}