Realm roles(associated roles): fix search filtering bug (#497)

* realm roles UX review progress wip

* filter realm roles on Enter key press, add filter functionality

* clean up

* filterChips logic now in table toolbar

* fix lint and format

* save with erik

* remove filter chips functionality

* fix check-types

* fix realm roles cypress test

* format

* wip pagination

* rebase

* fix roles pagination

* format

* add back save

* remove duplicates in associated roles table, can now paginate modal

* remove logs

* rebase and fix pagination/search

* remove slice

* pagination in modal and associated roles tab

* show client roles

* lint and format

* fix ts error in AliasRenderer

* fix lint

* add filterType

* wip search in component

* fix associated roles tab pagination

* revert KDT changes

* fix text

* add promise resolve type

* format

* remove comment

* add alphabetize function

* fix search

* remove log stmt

* clean up

* address PR feedback from Erik and render clientId badge in associated roles

* remove comment

* fix type

* format

* make checkboxes selectable

* address PR feedbak from Erik

* changes from rebase
This commit is contained in:
Eugenia 2021-04-09 11:08:40 -04:00 committed by GitHub
parent f37802bd01
commit dd1e1f511e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 34 deletions

View file

@ -268,17 +268,23 @@ export function KeycloakDataTable<T>({
);
} else {
data![rowIndex].selected = isSelected;
setRows([...rows!]);
}
// Keeps selected items when paginating
const difference = _.differenceBy(
selected,
data!.map((row) => row.data),
"id"
);
// Selected rows are any rows previously selected from a different page, plus current page selections
const selectedRows = [
...difference,
...data!.filter((row) => row.selected).map((row) => row.data),
];
setSelected(selectedRows);
onSelect!(selectedRows);
};

View file

@ -5,6 +5,7 @@ import {
Dropdown,
DropdownItem,
DropdownToggle,
Label,
Modal,
ModalVariant,
} from "@patternfly/react-core";
@ -14,10 +15,13 @@ import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { CaretDownIcon, FilterIcon } from "@patternfly/react-icons";
import { AliasRendererComponent } from "./AliasRendererComponent";
import _ from "lodash";
import { useErrorHandler } from "react-error-boundary";
type Role = RoleRepresentation & {
clientId?: string;
};
export type AssociatedRolesModalProps = {
open: boolean;
toggleDialog: () => void;
@ -50,11 +54,21 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
});
const allRoles = [...roles, ...existingAdditionalRoles];
const filterDupes = allRoles.filter(
const filterDupes: Role[] = allRoles.filter(
(thing, index, self) =>
index === self.findIndex((t) => t.name === thing.name)
);
const clients = await adminClient.clients.find();
filterDupes
.filter((role) => role.clientRole)
.map(
(role) =>
(role.clientId = clients.find(
(client) => client.id === role.containerId
)!.clientId!)
);
return alphabetize(filterDupes).filter((role: RoleRepresentation) => {
return (
props.existingCompositeRoles.find(
@ -64,16 +78,15 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
});
};
const AliasRenderer = (role: RoleRepresentation) => {
const AliasRenderer = ({ id, name, clientId }: Role) => {
return (
<>
<AliasRendererComponent
id={id}
name={role.name}
adminClient={adminClient}
filterType={filterType}
containerId={role.containerId}
/>
{clientId && (
<Label color="blue" key={`label-${id}`}>
{clientId}
</Label>
)}{" "}
{name}
</>
);
};
@ -83,7 +96,7 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
const clientIdArray = clients.map((client) => client.id);
let rolesList: RoleRepresentation[] = [];
let rolesList: Role[] = [];
for (const id of clientIdArray) {
const clientRolesList = await adminClient.clients.listRoles({
id: id as string,
@ -94,6 +107,15 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
id,
});
rolesList
.filter((role) => role.clientRole)
.map(
(role) =>
(role.clientId = clients.find(
(client) => client.id === role.containerId
)!.clientId!)
);
return alphabetize(rolesList).filter((role: RoleRepresentation) => {
return (
existingAdditionalRoles.find(
@ -213,8 +235,8 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
emptyState={
<ListEmptyState
hasIcon={true}
message={t("noRolesInThisRealm")}
instructions={t("noRolesInThisRealmInstructions")}
message={t("noRoles")}
instructions={t("noRolesInstructions")}
primaryActionText={t("createRole")}
/>
}

View file

@ -6,6 +6,7 @@ import {
Button,
ButtonVariant,
Checkbox,
Label,
PageSection,
ToolbarItem,
} from "@patternfly/react-core";
@ -20,17 +21,20 @@ import { AssociatedRolesModal } from "./AssociatedRolesModal";
import { useAdminClient } from "../context/auth/AdminClient";
import { RoleFormType } from "./RealmRoleTabs";
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
import { AliasRendererComponent } from "./AliasRendererComponent";
import _ from "lodash";
type AssociatedRolesTabProps = {
additionalRoles: RoleRepresentation[];
additionalRoles: Role[];
addComposites: (newReps: RoleRepresentation[]) => void;
parentRole: RoleFormType;
onRemove: (newReps: RoleRepresentation[]) => void;
client?: ClientRepresentation;
};
type Role = RoleRepresentation & {
clientId?: string;
};
export const AssociatedRolesTab = ({
additionalRoles,
addComposites,
@ -54,10 +58,7 @@ export const AssociatedRolesTab = ({
const { id } = useParams<{ id: string }>();
const inheritanceMap = React.useRef<{ [key: string]: string }>({});
const getSubRoles = async (
role: RoleRepresentation,
allRoles: RoleRepresentation[]
): Promise<RoleRepresentation[]> => {
const getSubRoles = async (role: Role, allRoles: Role[]): Promise<Role[]> => {
// Fetch all composite roles
const allCompositeRoles = await adminClient.roles.getCompositeRoles({
id: role.id!,
@ -86,33 +87,47 @@ export const AssociatedRolesTab = ({
};
const loader = async () => {
const alphabetize = (rolesList: RoleRepresentation[]) => {
const alphabetize = (rolesList: Role[]) => {
return _.sortBy(rolesList, (role) => role.name?.toUpperCase());
};
const clients = await adminClient.clients.find();
if (isInheritedHidden) {
setAllRoles(additionalRoles);
return alphabetize(additionalRoles);
return alphabetize(
additionalRoles.filter(
(role) =>
role.containerId === "master" && !inheritanceMap.current[role.id!]
)
);
}
const fetchedRoles: Promise<RoleRepresentation[]> = additionalRoles.reduce(
async (acc: Promise<RoleRepresentation[]>, role) => {
const fetchedRoles: Promise<Role[]> = additionalRoles.reduce(
async (acc: Promise<Role[]>, role) => {
const resolvedRoles = await acc;
resolvedRoles.push(role);
const subRoles = await getSubRoles(role, resolvedRoles);
resolvedRoles.push(...subRoles);
return acc;
},
Promise.resolve([] as RoleRepresentation[])
Promise.resolve([] as Role[])
);
return fetchedRoles.then((results: RoleRepresentation[]) => {
return fetchedRoles.then((results: Role[]) => {
const filterDupes = results.filter(
(thing, index, self) =>
index === self.findIndex((t) => t.name === thing.name)
);
setAllRoles(filterDupes);
filterDupes
.filter((role) => role.clientRole)
.map(
(role) =>
(role.clientId = clients.find(
(client) => client.id === role.containerId
)!.clientId!)
);
return alphabetize(filterDupes);
return alphabetize(additionalRoles);
});
};
@ -124,15 +139,15 @@ export const AssociatedRolesTab = ({
return <>{inheritanceMap.current[role.id!]}</>;
};
const AliasRenderer = (role: RoleRepresentation) => {
const AliasRenderer = ({ id, name, clientId }: Role) => {
return (
<>
<AliasRendererComponent
id={id}
name={role.name}
adminClient={adminClient}
containerId={role.containerId}
/>
{clientId && (
<Label color="blue" key={`label-${id}`}>
{clientId}
</Label>
)}{" "}
{name}
</>
);
};