make group select required when specific group is selected (#38768)

* make group select required when specific group is selected

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

* small refactor to make labels not dependant on route

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

* fixed tests

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-11 13:53:06 +02:00
committed by GitHub
parent f798db885e
commit 514b1b452b
8 changed files with 38 additions and 44 deletions

View File

@@ -66,6 +66,7 @@ export const Role = () => {
<AddRoleMappingModal
id="role"
type="roles"
title={t("assignRole")}
onAssign={(rows) => {
field.onChange([
...(field.value || []),
@@ -77,7 +78,6 @@ export const Role = () => {
onClose={() => {
setOpen(false);
}}
isLDAPmapper
/>
)}
<Button

View File

@@ -24,8 +24,6 @@ import useLocaleSort from "../../utils/useLocaleSort";
import { ResourcesKey, Row, ServiceRole } from "./RoleMapping";
import { getAvailableRoles } from "./queries";
import { getAvailableClientRoles } from "./resource";
import { PermissionsConfigurationTabsParams } from "../../permissions-configuration/routes/PermissionsConfigurationTabs";
import { useParams } from "react-router-dom";
type AddRoleMappingModalProps = {
id: string;
@@ -34,7 +32,8 @@ type AddRoleMappingModalProps = {
isRadio?: boolean;
onAssign: (rows: Row[]) => void;
onClose: () => void;
isLDAPmapper?: boolean;
title?: string;
actionLabel?: string;
};
type FilterType = "roles" | "clients";
@@ -52,9 +51,11 @@ export const AddRoleMappingModal = ({
id,
name,
type,
isLDAPmapper,
isRadio,
onAssign,
onClose,
title,
actionLabel,
}: AddRoleMappingModalProps) => {
const { adminClient } = useAdminClient();
@@ -71,7 +72,6 @@ export const AddRoleMappingModal = ({
const localeSort = useLocaleSort();
const compareRow = ({ role: { name } }: Row) => name?.toUpperCase();
const { tab } = useParams<PermissionsConfigurationTabsParams>();
const loader = async (
first?: number,
@@ -123,13 +123,7 @@ export const AddRoleMappingModal = ({
return (
<Modal
variant={ModalVariant.large}
title={
tab !== "evaluation"
? isLDAPmapper
? t("assignRole")
: t("assignRolesTo", { client: name })
: t("selectRole")
}
title={title || t("assignRolesTo", { client: name })}
isOpen
onClose={onClose}
actions={[
@@ -143,7 +137,7 @@ export const AddRoleMappingModal = ({
onClose();
}}
>
{tab !== "evaluation" ? t("assign") : t("select")}
{actionLabel || t("assign")}
</Button>,
<Button
data-testid="cancel"
@@ -157,13 +151,7 @@ export const AddRoleMappingModal = ({
>
<KeycloakDataTable
key={key}
onSelect={(rows) => {
if (tab === "evaluation") {
setSelectedRows(rows.length > 0 ? [rows[0]] : []);
} else {
setSelectedRows([...rows]);
}
}}
onSelect={(rows) => setSelectedRows([...rows])}
searchPlaceholderKey="searchByRoleName"
isPaginated={!(filterType === "roles" && type !== "roles")}
searchTypeComponent={
@@ -202,7 +190,7 @@ export const AddRoleMappingModal = ({
)
}
canSelectAll
isRadio={tab === "evaluation"}
isRadio={isRadio}
loader={filterType === "roles" ? loader : clientRolesLoader}
ariaLabelKey="associatedRolesText"
columns={[

View File

@@ -196,6 +196,7 @@ const PermissionEvaluateContent = ({ client }: Props) => {
defaultValue={[]}
variant="typeahead"
isRequired
isRadio
/>
)}
<SelectControl

View File

@@ -69,14 +69,11 @@ export const GroupSelect = ({
name={name!}
control={control}
defaultValue={defaultValue}
rules={
isRequired
? {
validate: (value?: GroupRepresentation[]) =>
value && value.length > 0,
}
: undefined
}
rules={{
validate: (value?: string[]) => {
return isRequired && value && value.length > 0;
},
}}
render={({ field }) => (
<>
{open && (
@@ -147,7 +144,7 @@ export const GroupSelect = ({
</Tbody>
</Table>
)}
{errors.groups && <FormErrorText message={t("requiredGroups")} />}
{errors[name!] && <FormErrorText message={t("requiredGroups")} />}
</FormGroup>
);
};

View File

@@ -96,6 +96,7 @@ export const ResourceType = ({
})}
defaultValue={[]}
variant="typeaheadMulti"
isRequired={withEnforceAccessTo}
/>
)}
</>

View File

@@ -12,14 +12,13 @@ import {
} from "@keycloak/keycloak-ui-shared";
import { AddRoleMappingModal } from "../../components/role-mapping/AddRoleMappingModal";
import { Row, ServiceRole } from "../../components/role-mapping/RoleMapping";
import { PermissionsConfigurationTabsParams } from "../routes/PermissionsConfigurationTabs";
import { useParams } from "react-router-dom";
type RoleSelectorProps = {
name: string;
isRadio?: boolean;
};
export const RoleSelect = ({ name }: RoleSelectorProps) => {
export const RoleSelect = ({ name, isRadio = false }: RoleSelectorProps) => {
const { adminClient } = useAdminClient();
const { t } = useTranslation();
const {
@@ -30,7 +29,6 @@ export const RoleSelect = ({ name }: RoleSelectorProps) => {
const values = getValues(name) || [];
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedRoles, setSelectedRoles] = useState<Row[]>([]);
const { tab } = useParams<PermissionsConfigurationTabsParams>();
useFetch(
async () => {
@@ -55,12 +53,10 @@ export const RoleSelect = ({ name }: RoleSelectorProps) => {
return (
<FormGroup
label={tab !== "evaluation" ? t("roles") : t("role")}
label={isRadio ? t("role") : t("roles")}
labelIcon={
<HelpItem
helpText={
tab !== "evaluation" ? t("policyRolesHelp") : t("selectRole")
}
helpText={isRadio ? t("selectRole") : t("policyRolesHelp")}
fieldLabelId="roles"
/>
}
@@ -71,19 +67,21 @@ export const RoleSelect = ({ name }: RoleSelectorProps) => {
<AddRoleMappingModal
id="role"
type="roles"
title={t("selectRole")}
actionLabel={t("select")}
isRadio={isRadio}
onAssign={(rows) => {
setValue(name, [
...values,
...(!isRadio ? values : []),
...rows
.filter((row) => row.role.id !== undefined)
.map((row) => row.role.id!),
]);
setSelectedRoles([...selectedRoles, ...rows]);
setSelectedRoles(isRadio ? rows : [...selectedRoles, ...rows]);
setIsModalOpen(false);
}}
onClose={() => setIsModalOpen(false)}
isLDAPmapper
/>
)}
<Button
@@ -91,7 +89,7 @@ export const RoleSelect = ({ name }: RoleSelectorProps) => {
variant="secondary"
onClick={() => setIsModalOpen(true)}
>
{tab !== "evaluation" ? t("addRoles") : t("selectRole")}
{isRadio ? t("selectRole") : t("addRoles")}
</Button>
{selectedRoles.length > 0 && (
<Table variant="compact">

View File

@@ -11,6 +11,7 @@ import {
clickCreatePermission,
clickCreatePolicySaveButton,
clickSearchButton,
deletePermission,
fillPermissionForm,
goToEvaluation,
goToPermissions,
@@ -76,6 +77,7 @@ test.describe("Permissions section tests", () => {
await goToPermissions(page);
await assertRowExists(page, "test-permission");
await deletePermission(page, "test-permission");
await goToPolicies(page);
await assertRowExists(page, "test-policy");
});
@@ -95,7 +97,7 @@ test.describe("Permissions section tests", () => {
await fillPolicyForm(
page,
{
name: "test-policy",
name: "test-policy2",
description: "test-description",
type: "User",
user: "test-user",

View File

@@ -1,6 +1,8 @@
import PolicyRepresentation from "@keycloak/keycloak-admin-client/lib/defs/policyRepresentation";
import { Page } from "@playwright/test";
import { selectItem } from "../utils/form";
import { confirmModal } from "../utils/modal";
import { clickRowKebabItem } from "../utils/table";
type PermissionForm = PolicyRepresentation & {
enforcementMode?: "allResources" | "specificResources";
@@ -68,3 +70,8 @@ export async function openSearchPanel(page: Page) {
export async function clickSearchButton(page: Page) {
await page.getByTestId("search-btn").click();
}
export async function deletePermission(page: Page, name: string) {
await clickRowKebabItem(page, name, "Delete");
await confirmModal(page);
}