moved filter to dropdown (#36711)

* moved filter to dropdown

fixes: #36635
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* fixing tests

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* fixed more tests

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* fixed tests

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* PR review comments

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

---------

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
Erik Jan de Wit 2025-04-16 19:25:27 +02:00 committed by GitHub
parent f475646b87
commit bbbd0ff986
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 228 additions and 213 deletions

View file

@ -505,7 +505,7 @@ eventTypes.CLIENT_LOGIN.name=Client login
mapper.nameid.format.tooltip=This mapper is applied only if the NameID format of the incoming AuthnRequest is equal to this value.
hideOnLoginPageHelp=If hidden, login with this provider is possible only if requested explicitly, for example using the 'kc_idp_hint' parameter.
eventTypes.UPDATE_PROFILE.description=Update profile
assignRolesTo=Assign roles to {{client}}
assignRolesTo=Assign {{type}} roles to {{client}}
orderChangeError=Could not change display order of identity providers {{error}}
policyProvider.client-scope=Define conditions for your permissions where a set of one or more client scopes is permitted to access an object.
secretExpiresOn=Secret expires on {{time}}

View file

@ -7,7 +7,11 @@ import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../../../admin-client";
import { DefaultSwitchControl } from "../../../components/SwitchControl";
import { AddRoleMappingModal } from "../../../components/role-mapping/AddRoleMappingModal";
import {
AddRoleButton,
AddRoleMappingModal,
FilterType,
} from "../../../components/role-mapping/AddRoleMappingModal";
import { Row, ServiceRole } from "../../../components/role-mapping/RoleMapping";
import type { RequiredIdValue } from "./ClientScope";
@ -22,6 +26,8 @@ export const Role = () => {
const values = getValues("roles");
const [open, setOpen] = useState(false);
const [filterType, setFilterType] = useState<FilterType>("clients");
const [selectedRoles, setSelectedRoles] = useState<Row[]>([]);
useFetch(
@ -67,6 +73,7 @@ export const Role = () => {
id="role"
type="roles"
title={t("assignRole")}
filterType={filterType}
onAssign={(rows) => {
field.onChange([
...(field.value || []),
@ -80,15 +87,14 @@ export const Role = () => {
}}
/>
)}
<Button
<AddRoleButton
data-testid="select-role-button"
variant="secondary"
onClick={() => {
onFilerTypeChange={(type) => {
setFilterType(type);
setOpen(true);
}}
>
{t("addRoles")}
</Button>
/>
</>
)}
/>

View file

@ -1,15 +1,14 @@
import { FormErrorText, HelpItem } from "@keycloak/keycloak-ui-shared";
import {
Button,
Chip,
FormGroup,
Split,
SplitItem,
} from "@patternfly/react-core";
import { Chip, FormGroup, Split, SplitItem } from "@patternfly/react-core";
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import useToggle from "../../utils/useToggle";
import { AddRoleMappingModal } from "../role-mapping/AddRoleMappingModal";
import {
AddRoleButton,
AddRoleMappingModal,
FilterType,
} from "../role-mapping/AddRoleMappingModal";
import { Row, ServiceRole } from "../role-mapping/RoleMapping";
import type { ComponentProps } from "./components";
@ -33,6 +32,8 @@ export const RoleComponent = ({
const { t } = useTranslation();
const [openModal, toggleModal] = useToggle();
const [filterType, setFilterType] = useState<FilterType>("clients");
const {
control,
formState: { errors },
@ -57,6 +58,7 @@ export const RoleComponent = ({
<AddRoleMappingModal
id="id"
type="roles"
filterType={filterType}
name={name}
onAssign={(rows) => field.onChange(parseRow(rows[0]))}
onClose={toggleModal}
@ -75,14 +77,16 @@ export const RoleComponent = ({
</SplitItem>
)}
<SplitItem>
<Button
onClick={toggleModal}
<AddRoleButton
label="selectRole.label"
onFilerTypeChange={(type) => {
setFilterType(type);
toggleModal();
}}
variant="secondary"
data-testid="add-roles"
disabled={isDisabled}
>
{t("selectRole.label")}
</Button>
isDisabled={isDisabled}
/>
</SplitItem>
</Split>
)}

View file

@ -8,12 +8,11 @@ import {
Dropdown,
DropdownItem,
DropdownList,
DropdownProps,
MenuToggle,
Modal,
ModalVariant,
ToolbarItem,
} from "@patternfly/react-core";
import { FilterIcon } from "@patternfly/react-icons";
import { cellWidth, TableText } from "@patternfly/react-table";
import { useState } from "react";
import { useTranslation } from "react-i18next";
@ -21,13 +20,15 @@ import { useAdminClient } from "../../admin-client";
import { useAccess } from "../../context/access/Access";
import { translationFormatter } from "../../utils/translationFormatter";
import useLocaleSort from "../../utils/useLocaleSort";
import { ResourcesKey, Row, ServiceRole } from "./RoleMapping";
import useToggle from "../../utils/useToggle";
import { ResourcesKey, Row } from "./RoleMapping";
import { getAvailableRoles } from "./queries";
import { getAvailableClientRoles } from "./resource";
type AddRoleMappingModalProps = {
id: string;
type: ResourcesKey;
filterType: FilterType;
name?: string;
isRadio?: boolean;
onAssign: (rows: Row[]) => void;
@ -36,7 +37,7 @@ type AddRoleMappingModalProps = {
actionLabel?: string;
};
type FilterType = "roles" | "clients";
export type FilterType = "roles" | "clients";
const RoleDescription = ({ role }: { role: RoleRepresentation }) => {
const { t } = useTranslation();
@ -47,11 +48,78 @@ const RoleDescription = ({ role }: { role: RoleRepresentation }) => {
);
};
type AddRoleButtonProps = Omit<
DropdownProps,
"children" | "toggle" | "isOpen" | "onOpenChange"
> & {
label?: string;
variant?: "default" | "plain" | "primary" | "plainText" | "secondary";
isDisabled?: boolean;
onFilerTypeChange: (type: FilterType) => void;
};
export const AddRoleButton = ({
label,
variant,
isDisabled,
onFilerTypeChange,
...rest
}: AddRoleButtonProps) => {
const { t } = useTranslation();
const [open, toggle] = useToggle();
const { hasAccess } = useAccess();
const canViewRealmRoles = hasAccess("view-realm") || hasAccess("query-users");
return (
<Dropdown
onOpenChange={toggle}
toggle={(ref) => (
<MenuToggle
ref={ref}
onClick={toggle}
variant={variant || "primary"}
isDisabled={isDisabled}
data-testid="add-role-mapping-button"
>
{t(label || "assignRole")}
</MenuToggle>
)}
isOpen={open}
{...rest}
>
<DropdownList>
<DropdownItem
data-testid="client-role"
component="button"
onClick={() => {
onFilerTypeChange("clients");
}}
>
{t("clientRoles")}
</DropdownItem>
{canViewRealmRoles && (
<DropdownItem
data-testid="roles-role"
component="button"
onClick={() => {
onFilerTypeChange("roles");
}}
>
{t("realmRoles")}
</DropdownItem>
)}
</DropdownList>
</Dropdown>
);
};
export const AddRoleMappingModal = ({
id,
name,
type,
isRadio,
filterType,
onAssign,
onClose,
title,
@ -60,15 +128,7 @@ export const AddRoleMappingModal = ({
const { adminClient } = useAdminClient();
const { t } = useTranslation();
const { hasAccess } = useAccess();
const canViewRealmRoles = hasAccess("view-realm") || hasAccess("query-users");
const [searchToggle, setSearchToggle] = useState(false);
const [filterType, setFilterType] = useState<FilterType>("clients");
const [selectedRows, setSelectedRows] = useState<Row[]>([]);
const [key, setKey] = useState(0);
const refresh = () => setKey(key + 1);
const localeSort = useLocaleSort();
const compareRow = ({ role: { name } }: Row) => name?.toUpperCase();
@ -120,10 +180,37 @@ export const AddRoleMappingModal = ({
);
};
const columns = [
{
name: "role.name",
displayKey: "name",
transforms: [cellWidth(30)],
},
{
name: "client.clientId",
displayKey: "clientId",
},
{
name: "role.description",
displayKey: "description",
cellRenderer: RoleDescription,
},
];
if (filterType === "roles") {
columns.splice(1, 1);
}
return (
<Modal
variant={ModalVariant.large}
title={title || t("assignRolesTo", { client: name })}
title={
title ||
t("assignRolesTo", {
type: filterType === "roles" ? t("realm") : t("client"),
client: name,
})
}
isOpen
onClose={onClose}
actions={[
@ -150,74 +237,20 @@ export const AddRoleMappingModal = ({
]}
>
<KeycloakDataTable
key={key}
onSelect={(rows) => setSelectedRows([...rows])}
searchPlaceholderKey="searchByRoleName"
isPaginated={!(filterType === "roles" && type !== "roles")}
searchTypeComponent={
canViewRealmRoles && (
<ToolbarItem>
<Dropdown
onOpenChange={(isOpen) => setSearchToggle(isOpen)}
onSelect={() => {
setFilterType(filterType === "roles" ? "clients" : "roles");
setSearchToggle(false);
refresh();
}}
toggle={(ref) => (
<MenuToggle
data-testid="filter-type-dropdown"
ref={ref}
onClick={() => setSearchToggle(!searchToggle)}
icon={<FilterIcon />}
>
{filterType === "roles"
? t("filterByRoles")
: t("filterByClients")}
</MenuToggle>
)}
isOpen={searchToggle}
>
<DropdownList>
<DropdownItem key="filter-type" data-testid={filterType}>
{filterType === "roles"
? t("filterByClients")
: t("filterByRoles")}
</DropdownItem>
</DropdownList>
</Dropdown>
</ToolbarItem>
)
searchPlaceholderKey={
filterType === "roles" ? "searchByRoleName" : "search"
}
isPaginated={!(filterType === "roles" && type !== "roles")}
canSelectAll
isRadio={isRadio}
loader={filterType === "roles" ? loader : clientRolesLoader}
ariaLabelKey="associatedRolesText"
columns={[
{
name: "name",
cellRenderer: ServiceRole,
transforms: [cellWidth(30)],
},
{
name: "role.description",
displayKey: "description",
cellRenderer: RoleDescription,
},
]}
columns={columns}
emptyState={
<ListEmptyState
message={t("noRoles")}
instructions={t("noRealmRolesToAssign")}
secondaryActions={[
{
text: t("filterByRoles"),
onClick: () => {
setFilterType("roles");
refresh();
},
},
]}
/>
}
/>

View file

@ -19,7 +19,11 @@ import { translationFormatter } from "../../utils/translationFormatter";
import { useConfirmDialog } from "../confirm-dialog/ConfirmDialog";
import { ListEmptyState } from "@keycloak/keycloak-ui-shared";
import { Action, KeycloakDataTable } from "@keycloak/keycloak-ui-shared";
import { AddRoleMappingModal } from "./AddRoleMappingModal";
import {
AddRoleButton,
AddRoleMappingModal,
FilterType,
} from "./AddRoleMappingModal";
import { deleteMapping, getEffectiveRoles, getMapping } from "./queries";
import { getEffectiveClientRoles } from "./resource";
@ -99,6 +103,7 @@ export const RoleMapping = ({
const [hide, setHide] = useState(true);
const [showAssign, setShowAssign] = useState(false);
const [filterType, setFilterType] = useState<FilterType>("clients");
const [selected, setSelected] = useState<Row[]>([]);
const assignRoles = async (rows: Row[]) => {
@ -179,6 +184,7 @@ export const RoleMapping = ({
<AddRoleMappingModal
id={id}
type={type}
filterType={filterType}
name={name}
onAssign={assignRoles}
onClose={() => setShowAssign(false)}
@ -213,12 +219,12 @@ export const RoleMapping = ({
{isManager && (
<>
<ToolbarItem>
<Button
data-testid="assignRole"
onClick={() => setShowAssign(true)}
>
{t("assignRole")}
</Button>
<AddRoleButton
onFilerTypeChange={(type) => {
setFilterType(type);
setShowAssign(true);
}}
/>
</ToolbarItem>
<ToolbarItem>
<Button
@ -270,8 +276,6 @@ export const RoleMapping = ({
<ListEmptyState
message={t(`noRoles-${type}`)}
instructions={t(`noRolesInstructions-${type}`)}
primaryActionText={t("assignRole")}
onPrimaryAction={() => setShowAssign(true)}
secondaryActions={[
{
text: t("showInheritedRoles"),
@ -281,7 +285,14 @@ export const RoleMapping = ({
},
},
]}
/>
>
<AddRoleButton
onFilerTypeChange={(type) => {
setFilterType(type);
setShowAssign(true);
}}
/>
</ListEmptyState>
}
/>
</>

View file

@ -1,16 +1,20 @@
import { useState } from "react";
import { Button, FormGroup } from "@patternfly/react-core";
import { MinusCircleIcon } from "@patternfly/react-icons";
import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../../admin-client";
import {
FormErrorText,
HelpItem,
useFetch,
} from "@keycloak/keycloak-ui-shared";
import { AddRoleMappingModal } from "../../components/role-mapping/AddRoleMappingModal";
import { Button, FormGroup } from "@patternfly/react-core";
import { MinusCircleIcon } from "@patternfly/react-icons";
import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
import { useState } from "react";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../../admin-client";
import {
AddRoleButton,
AddRoleMappingModal,
FilterType,
} from "../../components/role-mapping/AddRoleMappingModal";
import { Row, ServiceRole } from "../../components/role-mapping/RoleMapping";
type RoleSelectorProps = {
@ -29,6 +33,7 @@ export const RoleSelect = ({ name, isRadio = false }: RoleSelectorProps) => {
const values = getValues(name) || [];
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedRoles, setSelectedRoles] = useState<Row[]>([]);
const [filterType, setFilterType] = useState<FilterType>("clients");
useFetch(
async () => {
@ -82,15 +87,18 @@ export const RoleSelect = ({ name, isRadio = false }: RoleSelectorProps) => {
setIsModalOpen(false);
}}
onClose={() => setIsModalOpen(false)}
filterType={filterType}
/>
)}
<Button
<AddRoleButton
label={isRadio ? t("selectRole") : t("addRoles")}
data-testid="select-role-button"
variant="secondary"
onClick={() => setIsModalOpen(true)}
>
{isRadio ? t("selectRole") : t("addRoles")}
</Button>
onFilerTypeChange={(type) => {
setFilterType(type);
setIsModalOpen(true);
}}
/>
{selectedRoles.length > 0 && (
<Table variant="compact">
<Thead>

View file

@ -1,5 +1,9 @@
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { useAlerts, useFetch } from "@keycloak/keycloak-ui-shared";
import {
KeycloakSpinner,
useAlerts,
useFetch,
} from "@keycloak/keycloak-ui-shared";
import {
AlertVariant,
ButtonVariant,
@ -35,10 +39,8 @@ import {
arrayToKeyValue,
keyValueToArray,
} from "../components/key-value-form/key-value-convert";
import { KeycloakSpinner } from "@keycloak/keycloak-ui-shared";
import { PermissionsTab } from "../components/permission-tab/PermissionTab";
import { RoleForm } from "../components/role-form/RoleForm";
import { AddRoleMappingModal } from "../components/role-mapping/AddRoleMappingModal";
import { RoleMapping } from "../components/role-mapping/RoleMapping";
import {
RoutableTabs,
@ -47,12 +49,12 @@ import {
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAccess } from "../context/access/Access";
import { useRealm } from "../context/realm-context/RealmContext";
import { AdminEvents } from "../events/AdminEvents";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { useParams } from "../utils/useParams";
import { UsersInRoleTab } from "./UsersInRoleTab";
import { RealmRoleRoute, RealmRoleTab, toRealmRole } from "./routes/RealmRole";
import { toRealmRoles } from "./routes/RealmRoles";
import { AdminEvents } from "../events/AdminEvents";
export default function RealmRoleTabs() {
const { adminClient } = useAdminClient();
@ -82,7 +84,6 @@ export default function RealmRoleTabs() {
const [canManageClientRole, setCanManageClientRole] = useState(false);
const [open, setOpen] = useState(false);
const convert = (role: RoleRepresentation) => {
const { attributes, ...rest } = role;
return {
@ -246,15 +247,6 @@ export default function RealmRoleTabs() {
return (
<>
<DeleteConfirm />
{open && (
<AddRoleMappingModal
id={id}
type="roles"
name={roleName}
onAssign={(rows) => addComposites(rows.map((r) => r.role))}
onClose={() => setOpen(false)}
/>
)}
<ViewHeader
titleKey={roleName!}
badges={[

View file

@ -5,7 +5,7 @@ import { login } from "../utils/login";
import { assertNotificationMessage } from "../utils/masthead";
import { assertModalTitle, confirmModal } from "../utils/modal";
import {
changeRoleTypeFilter,
pickRoleType,
clickHideInheritedRoles,
clickUnassign,
confirmModalAssign,
@ -20,7 +20,7 @@ import {
getTableData,
searchItem,
} from "../utils/table";
import { assignRole, goToScopeTab } from "./scope";
import { goToScopeTab } from "./scope";
test.describe("Scope tab test", () => {
const scopeName = `client-scope-mapper-${uuidv4()}`;
@ -47,8 +47,7 @@ test.describe("Scope tab test", () => {
test("Assign and unassign role", async ({ page }) => {
const role = "admin";
await assignRole(page);
await changeRoleTypeFilter(page, "roles");
await pickRoleType(page, "roles");
await pickRole(page, role, true);
await confirmModalAssign(page);

View file

@ -3,7 +3,3 @@ import { Page } from "@playwright/test";
export async function goToScopeTab(page: Page) {
await page.getByTestId("scopeTab").click();
}
export async function assignRole(page: Page) {
await page.getByTestId("no-roles-for-this-client-scope-empty-action").click();
}

View file

@ -170,7 +170,6 @@ test.describe("Roles tab test", () => {
await goToAssociatedRolesTab(page);
// Add associated realm role
await page.getByTestId("no-roles-in-this-realm-empty-action").click();
await addAssociatedRoles(page, createRealmRoleName);
await assertNotificationMessage(page, "Associated roles have been added");
@ -188,8 +187,7 @@ test.describe("Roles tab test", () => {
await goToAssociatedRolesTab(page);
// Add associated client roles
await page.getByTestId("no-roles-in-this-realm-empty-action").click();
await addAssociatedRoles(page, "accountmanage-account", "client");
await addAssociatedRoles(page, "manage-account", "client");
await assertNotificationMessage(page, "Associated roles have been added");
});

View file

@ -1,6 +1,6 @@
import { expect, Page } from "@playwright/test";
import {
changeRoleTypeFilter,
pickRoleType,
confirmModalAssign,
pickRole,
RoleType,
@ -45,7 +45,7 @@ export async function addAssociatedRoles(
roleName: string,
roleType: RoleType = "roles",
) {
await changeRoleTypeFilter(page, roleType);
await pickRoleType(page, roleType);
await pickRole(page, roleName, true);
await confirmModalAssign(page);
}

View file

@ -3,8 +3,9 @@ import { v4 as uuid } from "uuid";
import adminClient from "../utils/AdminClient";
import { login } from "../utils/login";
import { assertNotificationMessage } from "../utils/masthead";
import { confirmModal } from "../utils/modal";
import {
changeRoleTypeFilter,
pickRoleType,
clickHideInheritedRoles,
clickUnassign,
confirmModalAssign,
@ -16,8 +17,7 @@ import {
assertRowExists,
clickTableRowItem,
} from "../utils/table";
import { assignRole, goToRoleMappingTab } from "./role";
import { confirmModal } from "../utils/modal";
import { goToRoleMappingTab } from "./role";
test.describe("Role mappings", () => {
const predefinedGroup = "group1";
@ -53,8 +53,7 @@ test.describe("Role mappings", () => {
});
test("Assign roles from empty state", async ({ page }) => {
await assignRole(page);
await changeRoleTypeFilter(page, "roles");
await pickRoleType(page, "roles");
await pickRole(page, "default-roles-master", true);
await confirmModalAssign(page);

View file

@ -3,7 +3,3 @@ import { Page } from "@playwright/test";
export async function goToRoleMappingTab(page: Page) {
await page.getByTestId("role-mapping-tab").click();
}
export async function assignRole(page: Page) {
await page.getByTestId("no-roles-for-this-group-empty-action").click();
}

View file

@ -19,7 +19,7 @@ import { login } from "../utils/login";
import { assertNotificationMessage } from "../utils/masthead";
import { confirmModal } from "../utils/modal";
import {
changeRoleTypeFilter,
pickRoleType,
clickUnassign,
confirmModalAssign,
pickRole,
@ -35,8 +35,6 @@ import {
} from "../utils/table";
import {
assertUnassignDisabled,
assignRole,
clickAddRoleButton,
clickCreateRoleButton,
goToAssociatedRolesTab,
} from "./main";
@ -132,25 +130,22 @@ test.describe("Realm roles test", () => {
await goToAssociatedRolesTab(page);
// Add associated realm role from search bar
await clickAddRoleButton(page, true);
await changeRoleTypeFilter(page, "roles");
await pickRoleType(page, "roles");
await pickRole(page, "offline_access", true);
await confirmModalAssign(page);
await assertNotificationMessage(page, "Associated roles have been added");
// Add associated client role from search bar
await clickAddRoleButton(page);
await pickRole(page, "accountmanage-account", true);
await pickRoleType(page, "client");
await pickRole(page, "manage-account", true);
await confirmModalAssign(page);
await assertNotificationMessage(page, "Associated roles have been added");
// Add associated client role
await clickAddRoleButton(page);
await pickRole(page, "accountmanage-consent", true);
await pickRoleType(page, "client");
await pickRole(page, "manage-consent", true);
await confirmModalAssign(page);
await assertNotificationMessage(page, "Associated roles have been added");
await clickAddRoleButton(page);
});
test("should search existing associated role by name and go to it", async ({
@ -207,8 +202,8 @@ test.describe("Realm roles test", () => {
await clickTableRowItem(page, itemId);
await goToAssociatedRolesTab(page);
await assignRole(page);
await pickRole(page, "accountview-profile", true);
await pickRoleType(page, "client");
await pickRole(page, "view-profile", true);
await confirmModalAssign(page);
await assertUnassignDisabled(page);
@ -222,8 +217,8 @@ test.describe("Realm roles test", () => {
await clickTableRowItem(page, itemId);
await goToAssociatedRolesTab(page);
await assignRole(page);
await pickRole(page, "accountview-profile", true);
await pickRoleType(page, "client");
await pickRole(page, "view-profile", true);
await confirmModalAssign(page);
await clickRowKebabItem(page, "account view-profile", "Unassign");
@ -239,8 +234,8 @@ test.describe("Realm roles test", () => {
await clickTableRowItem(page, itemId);
await goToAssociatedRolesTab(page);
await assignRole(page);
await pickRole(page, "accountview-profile", true);
await pickRoleType(page, "client");
await pickRole(page, "view-profile", true);
await confirmModalAssign(page);
await page.locator('input[name="check-all"]').check();

View file

@ -8,15 +8,6 @@ export async function goToAssociatedRolesTab(page: Page) {
await page.getByTestId("associatedRolesTab").click();
}
export async function clickAddRoleButton(page: Page, empty: boolean = false) {
const id = empty ? "no-roles-in-this-realm-empty-action" : "assignRole";
await page.getByTestId(id).click();
}
export async function assignRole(page: Page) {
await page.getByTestId("no-roles-in-this-realm-empty-action").click();
}
export async function assertUnassignDisabled(page: Page) {
await expect(page.getByTestId("unAssignRole")).toBeDisabled();
}

View file

@ -3,15 +3,16 @@ import { v4 as uuid } from "uuid";
import adminClient from "../utils/AdminClient";
import { login } from "../utils/login";
import { assertAxeViolations } from "../utils/masthead";
import { pickRoleType } from "../utils/roles";
import { goToRealm, goToRealmSettings } from "../utils/sidebar";
import { goToRealmEventsTab } from "./events";
import { goToAddProviders, goToKeys } from "./keys";
import { goToLoginTab } from "./login";
import { goToLocalizationTab, goToRealmOverridesSubTab } from "./localization";
import {
goToClientPoliciesList,
goToClientPoliciesTab,
} from "./client-policies";
import { goToRealmEventsTab } from "./events";
import { goToAddProviders, goToKeys } from "./keys";
import { goToLocalizationTab, goToRealmOverridesSubTab } from "./localization";
import { goToLoginTab } from "./login";
test.describe("Accessibility tests for realm settings", () => {
const realmName = `realm-settings-accessibility-${uuid()}`;
@ -186,7 +187,7 @@ test.describe("Accessibility tests for realm settings", () => {
page,
}) => {
await page.getByTestId("rs-userRegistration-tab").click();
await page.getByTestId("assignRole").click();
await pickRoleType(page, "client");
await assertAxeViolations(page);
});
});

View file

@ -4,15 +4,10 @@ import adminClient from "../utils/AdminClient";
import { login } from "../utils/login";
import { assertNotificationMessage } from "../utils/masthead";
import { confirmModal } from "../utils/modal";
import {
changeRoleTypeFilter,
confirmModalAssign,
pickRole,
} from "../utils/roles";
import { pickRoleType, confirmModalAssign, pickRole } from "../utils/roles";
import { goToRealm, goToRealmSettings } from "../utils/sidebar";
import { assertRowExists, clickRowKebabItem, searchItem } from "../utils/table";
import {
clickAssignRole,
goToDefaultGroupTab,
goToUserRegistrationTab,
} from "./user-registration";
@ -41,8 +36,7 @@ test.describe("Realm settings - User registration tab", () => {
test(`Add / remove ${roleName} role`, async ({ page }) => {
const roleType = "roles";
await clickAssignRole(page);
await changeRoleTypeFilter(page, roleType);
await pickRoleType(page, roleType);
await pickRole(page, roleName, true);
await confirmModalAssign(page);
await assertNotificationMessage(page, "Associated roles have been added");

View file

@ -4,10 +4,6 @@ export async function goToUserRegistrationTab(page: Page) {
await page.getByTestId("rs-userRegistration-tab").click();
}
export async function clickAssignRole(page: Page) {
await page.getByTestId("assignRole").click();
}
export async function goToDefaultGroupTab(page: Page) {
await page.getByTestId("default-groups-tab").click();
}

View file

@ -1,10 +1,6 @@
import ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import { Page } from "@playwright/test";
import {
changeRoleTypeFilter,
confirmModalAssign,
pickRole,
} from "../utils/roles";
import { pickRoleType, confirmModalAssign, pickRole } from "../utils/roles";
export async function goToMapperTab(page: Page) {
await page.getByTestId("ldap-mappers-tab").click();
@ -43,8 +39,7 @@ export async function fillHardwareAttributeMapper(
.fill(data.config?.["ldap.attribute.value"][0] || "");
if (data.config?.role) {
await page.getByTestId("add-roles").click();
await changeRoleTypeFilter(page, "roles");
await pickRoleType(page, "roles");
await pickRole(page, data.config.role[0], true);
await confirmModalAssign(page);
}

View file

@ -4,22 +4,16 @@ import { clickSelectRow } from "./table";
export type RoleType = "client" | "roles";
const rolePickTableName = "Role list";
export async function changeRoleTypeFilter(page: Page, roleType: RoleType) {
const currentFilter = await page
.getByTestId("filter-type-dropdown")
.innerText();
if (currentFilter.includes(roleType)) {
return;
}
export async function pickRoleType(page: Page, roleType: RoleType) {
await page.getByTestId("add-role-mapping-button").click();
let filter;
if (roleType === "client") {
filter = "Filter by clients";
filter = "Client roles";
} else {
filter = "Filter by realm roles";
filter = "Realm roles";
}
await page.getByTestId("filter-type-dropdown").click();
await page.getByRole("menuitem", { name: filter, exact: true }).click();
}

View file

@ -1,4 +1,9 @@
import { ComponentClass, MouseEventHandler, ReactNode } from "react";
import {
ComponentClass,
MouseEventHandler,
PropsWithChildren,
ReactNode,
} from "react";
import {
EmptyState,
EmptyStateIcon,
@ -40,7 +45,8 @@ export const ListEmptyState = ({
secondaryActions,
icon,
isDisabled = false,
}: ListEmptyStateProps) => {
children,
}: PropsWithChildren<ListEmptyStateProps>) => {
return (
<EmptyState data-testid="empty-state" variant="lg">
{hasIcon && isSearchVariant ? (
@ -63,6 +69,7 @@ export const ListEmptyState = ({
{primaryActionText}
</Button>
)}
{children}
{secondaryActions && (
<EmptyStateActions>
{secondaryActions.map((action) => (