The role picker no longer queries all clients (#3197)

This commit is contained in:
Erik Jan de Wit 2022-09-06 15:34:11 +02:00 committed by GitHub
parent 0bca44e733
commit 29cf962f19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 56 additions and 202 deletions

View file

@ -87,17 +87,9 @@ export default class AddMapperPage {
} }
addRoleToMapperForm() { addRoleToMapperForm() {
cy.get("#group-role-select-typeahead") cy.findByTestId("add-roles").click();
.click() cy.get("[aria-label='Select row 1']").click();
.get(".pf-c-select__menu-item") cy.findByTestId("assign").click();
.first()
.click();
cy.get("#role-role-select-typeahead")
.click()
.get(".pf-c-select__menu-item")
.first()
.click();
return this; return this;
} }

View file

@ -371,16 +371,9 @@ export default class ProviderPage {
break; break;
case this.hcLdapRoleMapper: case this.hcLdapRoleMapper:
cy.get("#group-role-select-typeahead") cy.findByTestId("add-roles").click();
.click() cy.get("[aria-label='Select row 1']").click();
.get(".pf-c-select__menu-item") cy.findByTestId("assign").click();
.first()
.click();
cy.get("#role-role-select-typeahead")
.click()
.get(".pf-c-select__menu-item")
.first()
.click();
break; break;
default: default:
console.log("Invalid mapper type."); console.log("Invalid mapper type.");

View file

@ -1,138 +1,42 @@
import { useEffect, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useFormContext } from "react-hook-form";
import { import {
Divider, Button,
Chip,
FormGroup, FormGroup,
Select,
SelectGroup,
SelectOption,
SelectVariant,
Split, Split,
SplitItem, SplitItem,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext";
import { HelpItem } from "../help-enabler/HelpItem";
import type { ComponentProps } from "./components"; import type { ComponentProps } from "./components";
import { HelpItem } from "../help-enabler/HelpItem";
import useToggle from "../../utils/useToggle";
import { AddRoleMappingModal } from "../role-mapping/AddRoleMappingModal";
import { ServiceRole, Row } from "../role-mapping/RoleMapping";
import { convertToName } from "./DynamicComponents"; import { convertToName } from "./DynamicComponents";
const RealmClient = (realm: string): ClientRepresentation => ({ const parseValue = (value: any) =>
name: "realmRoles", value?.includes(".") ? value.split(".") : ["", value || ""];
clientId: realm,
}); const parseRow = (value: Row) =>
value.client?.clientId
? `${value.client.clientId}.${value.role.name}`
: value.role.name;
export const RoleComponent = ({ export const RoleComponent = ({
name, name,
label, label,
helpText, helpText,
defaultValue,
isDisabled = false, isDisabled = false,
}: ComponentProps) => { }: ComponentProps) => {
const { t } = useTranslation("dynamic"); const { t } = useTranslation("dynamic");
const { adminClient } = useAdminClient(); const [openModal, toggleModal] = useToggle();
const { realm } = useRealm(); const { control, errors } = useFormContext();
const {
control,
getValues,
formState: { errors },
} = useFormContext();
const [roleOpen, setRoleOpen] = useState(false);
const [clientsOpen, setClientsOpen] = useState(false);
const [clients, setClients] = useState<ClientRepresentation[]>();
const [selectedClient, setSelectedClient] = useState<ClientRepresentation>();
const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]);
const [selectedRole, setSelectedRole] = useState<RoleRepresentation>();
const fieldName = convertToName(name!); const fieldName = convertToName(name!);
useFetch(
async () => {
const clients = await adminClient.clients.find();
const asyncFilter = async (
predicate: (client: ClientRepresentation) => Promise<boolean>
) => {
const results = await Promise.all(clients.map(predicate));
return clients.filter((_, index) => results[index]);
};
const filteredClients = await asyncFilter(
async (client) =>
(await adminClient.clients.listRoles({ id: client.id! })).length > 0
);
return filteredClients;
},
(filteredClients) => setClients(filteredClients),
[]
);
useEffect(() => {
const value = getValues(fieldName);
const [client, role] = value?.includes(".")
? value.split(".")
: ["", value || ""];
if (client) {
setSelectedClient(clients?.find((c) => c.clientId === client));
} else {
setSelectedClient(RealmClient(realm));
}
setSelectedRole({ name: role });
}, [clients, getValues]);
const createSelectGroup = (clients: ClientRepresentation[]) => {
return [
<SelectGroup key="role" label={t("roleGroup")}>
<SelectOption key="realmRoles" value={RealmClient(realm)}>
{realm}
</SelectOption>
</SelectGroup>,
<Divider key="divider" />,
<SelectGroup key="group" label={t("clientGroup")}>
{clients.map((client) => (
<SelectOption key={client.id} value={client}>
{client.clientId}
</SelectOption>
))}
</SelectGroup>,
];
};
const roleSelectOptions = () => {
const createItem = (role: RoleRepresentation) => (
<SelectOption key={role.id} value={role}>
{role.name}
</SelectOption>
);
return clientRoles.map((role) => createItem(role));
};
useFetch(
async () => {
if (selectedClient && selectedClient.name !== "realmRoles") {
const clientRoles = await adminClient.clients.listRoles({
id: selectedClient.id!,
});
return clientRoles;
} else {
return await adminClient.roles.find();
}
},
(clientRoles) => setClientRoles(clientRoles),
[selectedClient]
);
const onClear = (onChange: (value: string) => void) => {
setSelectedClient(undefined);
setSelectedRole(undefined);
onChange("");
};
return ( return (
<FormGroup <FormGroup
label={t(label!)} label={t(label!)}
@ -145,76 +49,41 @@ export const RoleComponent = ({
> >
<Controller <Controller
name={fieldName} name={fieldName}
defaultValue="" defaultValue={defaultValue || ""}
typeAheadAriaLabel="Select an action"
control={control} control={control}
render={({ onChange }) => ( render={({ onChange, value }) => (
<Split hasGutter> <Split>
<SplitItem> {openModal && (
{clients && ( <AddRoleMappingModal
<Select id="id"
toggleId={`group-${name}`} type="roles"
isDisabled={isDisabled} name={name}
onToggle={() => setClientsOpen(!clientsOpen)} onAssign={(rows) => onChange(parseRow(rows[0]))}
isOpen={clientsOpen} onClose={toggleModal}
variant={SelectVariant.typeahead} isRadio
typeAheadAriaLabel={t("selectASourceOfRoles")} />
placeholderText={t("selectASourceOfRoles")}
isGrouped
onFilter={(evt) => {
const textInput = evt?.target.value || "";
if (textInput === "") {
return createSelectGroup(clients);
} else {
return createSelectGroup(
clients.filter((client) =>
client
.name!.toLowerCase()
.includes(textInput.toLowerCase())
)
);
}
}}
selections={selectedClient?.clientId}
onClear={() => onClear(onChange)}
onSelect={(_, value) => {
onClear(onChange);
setSelectedClient(value as ClientRepresentation);
setClientsOpen(false);
}}
>
{createSelectGroup(clients)}
</Select>
)} )}
</SplitItem>
{value !== "" && (
<SplitItem> <SplitItem>
<Select <Chip textMaxWidth="500px" onClick={() => onChange("")}>
toggleId={`role-${name}`} <ServiceRole
onToggle={(isExpanded) => setRoleOpen(isExpanded)} role={{ name: parseValue(value)[1] }}
isOpen={roleOpen} client={{ clientId: parseValue(value)[0] }}
variant={SelectVariant.typeahead} />
placeholderText={ </Chip>
selectedClient?.name !== "realmRoles" </SplitItem>
? t("clientRoles") )}
: t("selectARole") <SplitItem>
} <Button
isDisabled={!selectedClient} onClick={toggleModal}
typeAheadAriaLabel={t("selectARole")} variant="secondary"
selections={selectedRole?.name} data-testid="add-roles"
onSelect={(_, value) => { disabled={isDisabled}
const role = value as RoleRepresentation;
setSelectedRole(role);
onChange(
selectedClient?.name === "realmRoles"
? role.name
: `${selectedClient?.clientId}.${role.name}`
);
setRoleOpen(false);
}}
maxHeight={200}
onClear={() => onClear(onChange)}
> >
{roleSelectOptions()} {t("selectRole.label")}
</Select> </Button>
</SplitItem> </SplitItem>
</Split> </Split>
)} )}

View file

@ -60,7 +60,7 @@ export const mapRoles = (
export const ServiceRole = ({ role, client }: Row) => ( export const ServiceRole = ({ role, client }: Row) => (
<> <>
{client && ( {client?.clientId && (
<Badge isRead className="keycloak-admin--role-mapping__client-name"> <Badge isRead className="keycloak-admin--role-mapping__client-name">
{client.clientId} {client.clientId}
</Badge> </Badge>