Realm settings(client policies): add/edit client-updater-source-groups condition (#1577)

This commit is contained in:
Jenny 2021-11-24 11:19:28 -05:00 committed by GitHub
parent 17d9872c04
commit d08b55ae9a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 211 additions and 14 deletions

View file

@ -298,6 +298,7 @@ export const AddScopeDialog = ({
{
name: "name",
},
{ name: "protocol", displayKey: "Protocol" },
{
name: "protocol",
displayKey: "clients:protocol",

View file

@ -13,7 +13,7 @@ export const DynamicComponents = ({ properties }: DynamicComponentProps) => (
<>
{properties.map((property) => {
const componentType = property.type!;
if (isValidComponentType(componentType)) {
if (isValidComponentType(componentType) && property.name !== "scopes") {
const Component = COMPONENTS[componentType];
return <Component key={property.name} {...property} />;
} else {

View file

@ -0,0 +1,172 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Controller, useFormContext } from "react-hook-form";
import {
Button,
Chip,
ChipGroup,
FormGroup,
TextInput,
} from "@patternfly/react-core";
import { HelpItem } from "../help-enabler/HelpItem";
import type { ComponentProps } from "./components";
import { AddScopeDialog } from "../../clients/scopes/AddScopeDialog";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
import { useParams } from "react-router";
import type { EditClientPolicyConditionParams } from "../../realm-settings/routes/EditCondition";
import { GroupPickerDialog } from "../group/GroupPickerDialog";
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
export const MultivaluedChipsComponent = ({
defaultValue,
name,
}: ComponentProps) => {
const { t } = useTranslation("dynamic");
const { control } = useFormContext();
const { conditionName } = useParams<EditClientPolicyConditionParams>();
const adminClient = useAdminClient();
const [open, setOpen] = useState(false);
const [clientScopes, setClientScopes] = useState<ClientScopeRepresentation[]>(
[]
);
const [selectedGroups, setSelectedGroups] = useState<GroupRepresentation[]>(
[]
);
useFetch(
() => adminClient.clientScopes.find(),
(clientScopes) => {
setClientScopes(clientScopes);
},
[]
);
const toggleModal = () => {
setOpen(!open);
};
return (
<FormGroup
label={
name === "scopes"
? t("realm-settings:clientScopesCondition")
: t("common:groups")
}
id={name === "scopes" ? "expected-scopes" : "expected-groups"}
labelIcon={
<HelpItem
helpText={
name === "scopes"
? t("realm-settings-help:clientScopesConditionTooltip")
: t("realm-settings-help:clientUpdaterSourceGroupsTooltip")
}
forLabel={
name === "scopes"
? t("clientScopes")
: t("clientUpdaterSourceGroups")
}
forID={name!}
/>
}
fieldId={name!}
>
<Controller
name={`config.${name}`}
control={control}
defaultValue={[defaultValue]}
rules={{ required: true }}
render={({ onChange, value }) => {
return (
<>
{open && name === "scopes" && (
<AddScopeDialog
clientScopes={clientScopes.filter(
(scope) => !value.includes(scope.name!)
)}
isClientScopesConditionType
open={open}
toggleDialog={() => setOpen(!open)}
onAdd={(scopes) => {
onChange([
...value,
...scopes
.map((scope) => scope.scope)
.map((item) => item.name!),
]);
}}
/>
)}
{open && name === "groups" && (
<GroupPickerDialog
type="selectMany"
text={{
title: "users:selectGroups",
ok: "users:join",
}}
onConfirm={(groups) => {
onChange([...value, ...groups.map((group) => group.name)]);
setSelectedGroups([...selectedGroups!, ...groups]);
setOpen(false);
}}
onClose={() => {
setOpen(false);
}}
filterGroups={value}
/>
)}
{value.length === 0 && !conditionName && (
<TextInput
type="text"
id="kc-scopes"
value={value}
data-testid="client-scope-input"
name="config.client-scopes"
isDisabled
/>
)}
<ChipGroup
className="kc-client-scopes-chip-group"
isClosable
onClick={() => {
onChange([]);
if (name === "groups") {
setSelectedGroups([]);
}
}}
>
{value.map((currentChip: string) => (
<Chip
key={currentChip}
onClick={() => {
onChange(
value.filter((item: string) => item !== currentChip)
);
if (name === "groups") {
setSelectedGroups(
value.filter((item: string) => item !== currentChip)
);
}
}}
>
{currentChip}
</Chip>
))}
</ChipGroup>
<Button
data-testid="select-scope-button"
variant="secondary"
onClick={() => {
toggleModal();
}}
>
{t("common:select")}
</Button>
</>
);
}}
/>
</FormGroup>
);
};

View file

@ -25,7 +25,7 @@ import { GroupPath } from "./GroupPath";
export type GroupPickerDialogProps = {
id?: string;
type: "selectOne" | "selectMany";
filterGroups?: GroupRepresentation[];
filterGroups?: string[];
text: { title: string; ok: string };
onConfirm: (groups: GroupRepresentation[]) => void;
onClose: () => void;
@ -100,9 +100,10 @@ export const GroupPickerDialog = ({
);
const isRowDisabled = (row?: GroupRepresentation) => {
return !![...joinedGroups, ...(filterGroups || [])].find(
(group) => group.id === row?.id
);
return [
...joinedGroups.map((item) => item.name),
...(filterGroups || []),
].some((group) => group === row?.name);
};
const hasSubgroups = (group: GroupRepresentation) => {
@ -129,7 +130,7 @@ export const GroupPickerDialog = ({
<Modal
variant={ModalVariant.small}
title={t(text.title, {
group1: filterGroups?.[0]?.name,
group1: filterGroups?.[0],
group2: currentGroup() ? currentGroup().name : t("root"),
})}
isOpen
@ -230,7 +231,7 @@ export const GroupPickerDialog = ({
>
{type === "selectMany" && (
<DataListCheck
className="join-group-modal-check"
className="kc-join-group-modal-check"
data-testid={`${group.name}-check`}
checked={group.checked}
isDisabled={isRowDisabled(group)}

View file

@ -203,7 +203,7 @@ export const GroupTable = () => {
{move && (
<GroupPickerDialog
type="selectOne"
filterGroups={[move]}
filterGroups={[move.name!]}
text={{
title: "groups:moveToGroup",
ok: "groups:moveHere",

View file

@ -38,7 +38,7 @@ import {
COMPONENTS,
isValidComponentType,
} from "../components/dynamic/components";
import { MultivaluedScopesComponent } from "../components/dynamic/MultivaluedScopesComponent";
import { MultivaluedChipsComponent } from "../components/dynamic/MultivaluedChipsComponent";
import { MultivaluedRoleComponent } from "../components/dynamic/MultivaluedRoleComponent";
export type ItemType = { value: string };
@ -92,7 +92,8 @@ export default function NewClientPolicyCondition() {
const property = properties.find((p) => p.name === key);
if (
property?.type === "MultivaluedString" &&
property.name !== "scopes"
property.name !== "scopes" &&
property.name !== "groups"
) {
form.setValue(formKey, convertToMultiline(value));
} else if (property?.name === "client-scopes") {
@ -135,7 +136,9 @@ export default function NewClientPolicyCondition() {
const writeConfig = () => {
return conditionProperties.reduce((r: any, p) => {
p.type === "MultivaluedString" && p.name !== "scopes"
p.type === "MultivaluedString" &&
p.name !== "scopes" &&
p.name !== "groups"
? (r[p.name!] = toValue(configValues[p.name!]))
: (r[p.name!] = configValues[p.name!]);
return r;
@ -300,11 +303,22 @@ export default function NewClientPolicyCondition() {
conditionName === "client-scopes")
) {
return (
<MultivaluedScopesComponent
<MultivaluedChipsComponent
defaultValue="offline_access"
{...property}
/>
);
} else if (
property.name === "groups" &&
(conditionType === "client-updater-source-groups" ||
conditionName === "client-updater-source-groups")
) {
return (
<MultivaluedChipsComponent
defaultValue="topgroup"
{...property}
/>
);
} else if (isValidComponentType(componentType)) {
const Component = COMPONENTS[componentType];
return <Component key={property.name} {...property} />;

View file

@ -265,3 +265,7 @@ input#kc-scopes {
min-width: 585px;
padding-left: none;
}
.kc-join-group-modal-check {
margin-right: var(--pf-global--spacer--sm);
}

View file

@ -145,13 +145,13 @@ export const UserForm = ({
setOpen(false);
}}
onClose={() => setOpen(false)}
filterGroups={selectedGroups}
filterGroups={selectedGroups.map((group) => group.name!)}
/>
)}
{user?.id ? (
<>
<FormGroup label={t("common:id")} fieldId="kc-id" isRequired>
<TextInput id={user?.id} value={user?.id} type="text" isReadOnly />
<TextInput id={user.id} value={user.id} type="text" isReadOnly />
</FormGroup>
<FormGroup label={t("createdAt")} fieldId="kc-created-at" isRequired>
<TextInput

View file

@ -15,6 +15,11 @@ kc-consents-chip-group .pf-c-chip-group__list {
button#kc-join-groups-button {
height: min-content;
}
.kc-join-group-modal-check {
margin-right: var(--pf-global--spacer--sm);
}
.join-group-dialog-row-disabled {
cursor: not-allowed;
}