Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -3561,10 +3561,9 @@ bruteForceMode.PermanentAfterTemporaryLockout=Lockout permanently after temporar
bruteForceMode=Brute Force Mode
error-invalid-multivalued-size=Attribute {{0}} must have at least {{1}} and at most {{2}} value(s).
multivalued=Multivalued
multivaluedHelp=If this attribute supports multiple values. This setting is an indicator and does not enable any validation.
multivaluedHelp=If enabled, this attribute can store multiple values. Rendering depends on inputType annotations (for example, multiselect and multiselect-checkboxes use their own controls). Use validators to enforce allowed value counts.
defaultValue=Default value
defaultValueHelp=Default value when attribute value is not specified.
to the attribute. For that, make sure to use any of the built-in validators to properly validate the size and the values.
sendIdTokenOnLogout=Send 'id_token_hint' in logout requests
sendIdTokenOnLogoutHelp=If the 'id_token_hint' parameter should be sent in logout requests.
sendClientIdOnLogout=Send 'client_id' in logout requests
Expand Down
104 changes: 103 additions & 1 deletion js/apps/admin-ui/test/realm-settings/userprofile.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { expect, test } from "@playwright/test";
import { v4 as uuid } from "uuid";
import adminClient from "../utils/AdminClient.ts";
import { assertFieldError, selectItem, switchOn } from "../utils/form.ts";
import {
assertFieldError,
selectItem,
switchOff,
switchOn,
} from "../utils/form.ts";
import { login } from "../utils/login.ts";
import { assertNotificationMessage } from "../utils/masthead.ts";
import { confirmModal } from "../utils/modal.ts";
Expand Down Expand Up @@ -32,6 +37,29 @@ test.describe.serial("User profile tabs", () => {
const group = `realm-settings-group-${uuid()}`;

const realmName = `sessions-realm-user-profile-${uuid()}`;
const userProfilePermissions = {
view: ["admin", "user"],
edit: ["admin", "user"],
};

const addMultiselectCheckboxAttribute = async (
attributeName: string,
optionValues: string[],
multivalued = true,
) => {
await adminClient.addUserProfile(realmName, {
attributes: [
{
name: attributeName,
displayName: attributeName,
permissions: userProfilePermissions,
multivalued,
annotations: { inputType: "multiselect-checkboxes" },
validations: { options: { options: optionValues } },
},
],
});
};

test.beforeAll(async () => {
await adminClient.createRealm(realmName);
Expand Down Expand Up @@ -259,4 +287,78 @@ test.describe.serial("User profile tabs", () => {

await expect(page.getByRole("heading", { name: group })).toBeVisible();
});

test("Persists multivalued switch state when editing a configured attribute", async ({
page,
}) => {
const suffix = uuid().slice(0, 8);
const attributeName = `multivalued-switch-${suffix}`;

await addMultiselectCheckboxAttribute(attributeName, [
`option-a-${suffix}`,
`option-b-${suffix}`,
]);
await goToRealmSettings(page);
await goToUserProfileTab(page);
await goToAttributesTab(page);
await assertRowExists(page, attributeName, true);

await clickTableRowItem(page, attributeName);
await expect(page.locator("#multivalued")).toBeChecked();

await switchOff(page, "#multivalued");
await clickSaveAttribute(page);
await assertNotificationMessage(
page,
"Success! User Profile configuration has been saved.",
);

await clickTableRowItem(page, attributeName);
await expect(page.locator("#multivalued")).not.toBeChecked();
});

test("Uses inputType precedence over multivalued switch for multiselect-checkboxes rendering", async ({
page,
}) => {
const suffix = uuid().slice(0, 8);
const attributeName = `multivalued-render-${suffix}`;
const optionOne = `option-one-${suffix}`;
const optionTwo = `option-two-${suffix}`;

await addMultiselectCheckboxAttribute(attributeName, [
optionOne,
optionTwo,
]);
await goToRealmSettings(page);
await goToUserProfileTab(page);
await goToAttributesTab(page);
await assertRowExists(page, attributeName, true);

await goToUsers(page);
await page.getByTestId("no-users-found-empty-action").click();
await expect(page.getByTestId(optionOne)).toBeVisible();
await expect(page.getByTestId(optionTwo)).toBeVisible();
await expect(page.getByTestId(`attributes.${attributeName}0`)).toHaveCount(
0,
);

await goToRealmSettings(page);
await goToUserProfileTab(page);
await goToAttributesTab(page);
await clickTableRowItem(page, attributeName);
await switchOff(page, "#multivalued");
await clickSaveAttribute(page);
await assertNotificationMessage(
page,
"Success! User Profile configuration has been saved.",
);

await goToUsers(page);
await page.getByTestId("no-users-found-empty-action").click();
await expect(page.getByTestId(optionOne)).toBeVisible();
await expect(page.getByTestId(optionTwo)).toBeVisible();
await expect(page.getByTestId(`attributes.${attributeName}0`)).toHaveCount(
0,
);
});
});
20 changes: 14 additions & 6 deletions js/libs/ui-shared/src/user-profile/UserProfileFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,20 @@ const FormField = ({
fieldName(attribute.name) as FieldPath<UserFormFields>,
);
const inputType = useMemo(() => determineInputType(attribute), [attribute]);

const Component =
attribute.multivalued ||
(isMultiValue(value) && attribute.annotations?.inputType === undefined)
? FIELDS["multi-input"]
: FIELDS[inputType];
const hasDedicatedInputComponent =
inputType === "textarea" ||
inputType === "select" ||
inputType === "multiselect" ||
inputType === "select-radiobuttons" ||
inputType === "multiselect-checkboxes";
const shouldRenderMultiInput =
!hasDedicatedInputComponent &&
(attribute.multivalued ||
(isMultiValue(value) && attribute.annotations?.inputType === undefined));

const Component = shouldRenderMultiInput
? FIELDS["multi-input"]
: FIELDS[inputType];

if (attribute.name === "locale")
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@
<@inputTagSelects attribute=attribute/>
<#break>
<#default>
<#if attribute.multivalued && attribute.values?has_content>
<#assign inferredMultivalued = !attribute.annotations.inputType?? && (attribute.values?size > 1)>
<#if (attribute.multivalued || inferredMultivalued) && attribute.values?has_content>
Comment on lines +86 to +87
<#list attribute.values as value>
<@inputTag attribute=attribute value=value!''/>
</#list>
Expand Down
Loading