issue 19582 (#19749)

* fixed paginate subgroups + nav

* render matched sub groups and fix pagination when searching
This commit is contained in:
Erik Jan de Wit 2023-04-19 21:35:36 +02:00 committed by GitHub
parent 0ddc71d987
commit 3b65f1163d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -61,6 +61,8 @@ export const GroupPickerDialog = ({
const [max, setMax] = useState(10); const [max, setMax] = useState(10);
const [first, setFirst] = useState(0); const [first, setFirst] = useState(0);
const [count, setCount] = useState(0);
const currentGroup = () => navigation[navigation.length - 1]; const currentGroup = () => navigation[navigation.length - 1];
useFetch( useFetch(
@ -68,13 +70,14 @@ export const GroupPickerDialog = ({
let group; let group;
let groups; let groups;
let existingUserGroups; let existingUserGroups;
let count = 0;
if (!groupId) { if (!groupId) {
groups = await adminClient.groups.find({ groups = await adminClient.groups.find({
first, first,
max: max + 1, max: max + (isSearching ? 0 : 1),
search: isSearching ? filter : "", search: isSearching ? filter : "",
}); });
} else { } else if (!navigation.map(({ id }) => id).includes(groupId)) {
group = await adminClient.groups.findOne({ id: groupId }); group = await adminClient.groups.findOne({ id: groupId });
if (!group) { if (!group) {
throw new Error(t("common:notFound")); throw new Error(t("common:notFound"));
@ -82,24 +85,32 @@ export const GroupPickerDialog = ({
groups = group.subGroups!; groups = group.subGroups!;
} }
if (isSearching) {
count = (await adminClient.groups.count({ search: filter, top: true }))
.count;
}
if (id) { if (id) {
existingUserGroups = await adminClient.users.listGroups({ existingUserGroups = await adminClient.users.listGroups({
id, id,
}); });
} }
return { group, groups, existingUserGroups }; return { group, groups, existingUserGroups, count };
}, },
async ({ group: selectedGroup, groups, existingUserGroups }) => { async ({ group: selectedGroup, groups, existingUserGroups, count }) => {
setJoinedGroups(existingUserGroups || []); setJoinedGroups(existingUserGroups || []);
if (selectedGroup) { if (selectedGroup) {
setNavigation([...navigation, selectedGroup]); setNavigation([...navigation, selectedGroup]);
} }
groups.forEach((group: SelectableGroup) => { if (groups) {
group.checked = !!selectedRows.find((r) => r.id === group.id); groups.forEach((group: SelectableGroup) => {
}); group.checked = !!selectedRows.find((r) => r.id === group.id);
setGroups(groups); });
setGroups(groups);
}
setCount(count);
}, },
[groupId, filter, first, max] [groupId, filter, first, max]
); );
@ -111,25 +122,6 @@ export const GroupPickerDialog = ({
].some((group) => group === row?.id); ].some((group) => group === row?.id);
}; };
const hasSubgroups = (group: GroupRepresentation) =>
group.subGroups?.length !== 0;
const expandGroup = (
group: GroupRepresentation,
name: string
): GroupRepresentation => {
if (group.name?.includes(name)) {
return group;
}
if (group.subGroups) {
for (const g of group.subGroups) {
const found = expandGroup(g, name);
return found;
}
}
return group;
};
return ( return (
<Modal <Modal
variant={isSearching ? ModalVariant.medium : ModalVariant.small} variant={isSearching ? ModalVariant.medium : ModalVariant.small}
@ -161,7 +153,10 @@ export const GroupPickerDialog = ({
]} ]}
> >
<PaginatingTableToolbar <PaginatingTableToolbar
count={groups.length} count={
(isSearching ? count : groups.length) -
(groupId || isSearching ? first : 0)
}
first={first} first={first}
max={max} max={max}
onNextClick={setFirst} onNextClick={setFirst}
@ -189,6 +184,8 @@ export const GroupPickerDialog = ({
onClick={() => { onClick={() => {
setGroupId(undefined); setGroupId(undefined);
setNavigation([]); setNavigation([]);
setFirst(0);
setMax(10);
}} }}
> >
{t("groups")} {t("groups")}
@ -203,6 +200,8 @@ export const GroupPickerDialog = ({
onClick={() => { onClick={() => {
setGroupId(group.id); setGroupId(group.id);
setNavigation([...navigation].slice(0, i)); setNavigation([...navigation].slice(0, i));
setFirst(0);
setMax(10);
}} }}
> >
{group.name} {group.name}
@ -213,100 +212,40 @@ export const GroupPickerDialog = ({
))} ))}
</Breadcrumb> </Breadcrumb>
<DataList aria-label={t("groups")} isCompact> <DataList aria-label={t("groups")} isCompact>
{groups.slice(0, max).map((group: SelectableGroup) => ( {groups
<DataListItem .slice(groupId ? first : 0, max + (groupId ? first : 0))
className={`join-group-dialog-row-${ .map((group: SelectableGroup) => (
isRowDisabled(group) ? "disabled" : "" <>
}`} <GroupRow
aria-labelledby={group.name} key={group.id}
key={group.id} group={group}
id={group.id} isRowDisabled={isRowDisabled}
onClick={(e) => { onSelect={setGroupId}
const g = isSearching ? expandGroup(group, filter) : group; type={type}
if (isRowDisabled(g)) return; isSearching={isSearching}
if (type === "selectOne") { setIsSearching={setIsSearching}
setGroupId(g.id); selectedRows={selectedRows}
} else if ( setSelectedRows={setSelectedRows}
hasSubgroups(group) && canBrowse={canBrowse}
(e.target as HTMLInputElement).type !== "checkbox"
) {
setGroupId(
isSearching ? expandGroup(group, filter).id : group.id
);
setIsSearching(false);
}
}}
>
<DataListItemRow
className={`join-group-dialog-row-${
isRowDisabled(group) ? "m-disabled" : ""
}`}
data-testid={group.name}
>
{type === "selectMany" && (
<DataListCheck
className="kc-join-group-modal-check"
data-testid={`${group.name}-check`}
aria-label={group.name}
checked={group.checked}
isDisabled={isRowDisabled(group)}
onChange={(checked) => {
group.checked = checked;
let newSelectedRows: SelectableGroup[] = [];
if (!group.checked) {
newSelectedRows = selectedRows.filter(
(r) => r.id !== group.id
);
} else {
newSelectedRows = [
...selectedRows,
isSearching ? expandGroup(group, filter) : group,
];
}
setSelectedRows(newSelectedRows);
}}
aria-labelledby={`select-${group.name}`}
/>
)}
<DataListItemCells
dataListCells={[
<DataListCell
key={`name-${group.id}`}
className="keycloak-groups-group-path"
>
{isSearching ? (
<GroupPath
id={`select-${group.name}`}
group={expandGroup(group, filter)}
/>
) : (
<span id={`select-${group.name}`}>{group.name}</span>
)}
</DataListCell>,
]}
/> />
<DataListAction {isSearching &&
id="actions" group.subGroups?.length !== 0 &&
aria-labelledby={`select-${group.name}`} group.subGroups!.map((g) => (
aria-label={t("groupName")} <GroupRow
isPlainButtonAction key={g.id}
> group={g}
{((hasSubgroups(group) && canBrowse) || isRowDisabled={isRowDisabled}
type === "selectOne") && ( onSelect={setGroupId}
<Button type={type}
isDisabled isSearching={isSearching}
variant="link" setIsSearching={setIsSearching}
aria-label={t("common:select")} selectedRows={selectedRows}
> setSelectedRows={setSelectedRows}
<AngleRightIcon /> canBrowse={canBrowse}
</Button> />
)} ))}
</DataListAction> </>
</DataListItemRow> ))}
</DataListItem>
))}
</DataList> </DataList>
{groups.length === 0 && !isSearching && ( {groups.length === 0 && !isSearching && (
<ListEmptyState <ListEmptyState
@ -325,3 +264,111 @@ export const GroupPickerDialog = ({
</Modal> </Modal>
); );
}; };
type GroupRowProps = {
group: SelectableGroup;
type: "selectOne" | "selectMany";
isRowDisabled: (row?: GroupRepresentation) => boolean;
isSearching: boolean;
setIsSearching: (value: boolean) => void;
onSelect: (groupId: string) => void;
selectedRows: SelectableGroup[];
setSelectedRows: (groups: SelectableGroup[]) => void;
canBrowse: boolean;
};
const GroupRow = ({
group,
type,
isRowDisabled,
isSearching,
setIsSearching,
onSelect,
selectedRows,
setSelectedRows,
canBrowse,
}: GroupRowProps) => {
const { t } = useTranslation();
const hasSubgroups = (group: GroupRepresentation) =>
group.subGroups?.length !== 0;
return (
<DataListItem
className={`join-group-dialog-row-${
isRowDisabled(group) ? "disabled" : ""
}`}
aria-labelledby={group.name}
key={group.id}
id={group.id}
onClick={(e) => {
if (isRowDisabled(group)) return;
if (type === "selectOne") {
onSelect(group.id!);
} else if (
hasSubgroups(group) &&
(e.target as HTMLInputElement).type !== "checkbox"
) {
onSelect(group.id!);
setIsSearching(false);
}
}}
>
<DataListItemRow
className={`join-group-dialog-row-${
isRowDisabled(group) ? "m-disabled" : ""
}`}
data-testid={group.name}
>
{type === "selectMany" && (
<DataListCheck
className="kc-join-group-modal-check"
data-testid={`${group.name}-check`}
aria-label={group.name}
checked={group.checked}
isDisabled={isRowDisabled(group)}
onChange={(checked) => {
group.checked = checked;
let newSelectedRows: SelectableGroup[] = [];
if (!group.checked) {
newSelectedRows = selectedRows.filter((r) => r.id !== group.id);
} else {
newSelectedRows = [...selectedRows, group];
}
setSelectedRows(newSelectedRows);
}}
aria-labelledby={`select-${group.name}`}
/>
)}
<DataListItemCells
dataListCells={[
<DataListCell
key={`name-${group.id}`}
className="keycloak-groups-group-path"
>
{isSearching ? (
<GroupPath id={`select-${group.name}`} group={group} />
) : (
<span id={`select-${group.name}`}>{group.name}</span>
)}
</DataListCell>,
]}
/>
<DataListAction
id="actions"
aria-labelledby={`select-${group.name}`}
aria-label={t("groupName")}
isPlainButtonAction
>
{((hasSubgroups(group) && canBrowse) || type === "selectOne") && (
<Button isDisabled variant="link" aria-label={t("common:select")}>
<AngleRightIcon />
</Button>
)}
</DataListAction>
</DataListItemRow>
</DataListItem>
);
};