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:
parent
f37802bd01
commit
dd1e1f511e
3 changed files with 77 additions and 34 deletions
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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")}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue