Refactor roles list to React Router v6 (#4187)

This commit is contained in:
Jon Koops 2023-01-12 16:51:08 +01:00 committed by GitHub
parent 2b9744f6bf
commit d11ed1ccef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 103 additions and 118 deletions

View file

@ -31,6 +31,7 @@ import { DownloadDialog } from "../components/download-dialog/DownloadDialog";
import type { KeyValueType } from "../components/key-value-form/key-value-convert"; import type { KeyValueType } from "../components/key-value-form/key-value-convert";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { PermissionsTab } from "../components/permission-tab/PermissionTab"; import { PermissionsTab } from "../components/permission-tab/PermissionTab";
import { RolesList } from "../components/roles-list/RolesList";
import { import {
RoutableTabs, RoutableTabs,
useRoutableTab, useRoutableTab,
@ -43,7 +44,6 @@ import { useAccess } from "../context/access/Access";
import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { RolesList } from "../realm-roles/RolesList";
import { import {
convertAttributeNameToForm, convertAttributeNameToForm,
convertFormValuesToObject, convertFormValuesToObject,
@ -70,8 +70,10 @@ import {
toAuthorizationTab, toAuthorizationTab,
} from "./routes/AuthenticationTab"; } from "./routes/AuthenticationTab";
import { ClientParams, ClientTab, toClient } from "./routes/Client"; import { ClientParams, ClientTab, toClient } from "./routes/Client";
import { toClientRole } from "./routes/ClientRole";
import { toClients } from "./routes/Clients"; import { toClients } from "./routes/Clients";
import { ClientScopesTab, toClientScopesTab } from "./routes/ClientScopeTab"; import { ClientScopesTab, toClientScopesTab } from "./routes/ClientScopeTab";
import { toCreateRole } from "./routes/NewRole";
import { ClientScopes } from "./scopes/ClientScopes"; import { ClientScopes } from "./scopes/ClientScopes";
import { EvaluateScopes } from "./scopes/EvaluateScopes"; import { EvaluateScopes } from "./scopes/EvaluateScopes";
import { ServiceAccount } from "./service-account/ServiceAccount"; import { ServiceAccount } from "./service-account/ServiceAccount";
@ -474,6 +476,15 @@ export default function ClientDetails() {
loader={loader} loader={loader}
paginated={false} paginated={false}
messageBundle="clients" messageBundle="clients"
toCreate={toCreateRole({ realm, clientId: client.id! })}
toDetail={(roleId) =>
toClientRole({
realm,
clientId: client.id!,
id: roleId,
tab: "details",
})
}
isReadOnly={!(hasManageClients || client.access?.configure)} isReadOnly={!(hasManageClients || client.access?.configure)}
/> />
</Tab> </Tab>

View file

@ -9,8 +9,8 @@ import { AttributeForm } from "../../components/key-value-form/AttributeForm";
import { RoleForm } from "../../components/role-form/RoleForm"; import { RoleForm } from "../../components/role-form/RoleForm";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext"; import { useRealm } from "../../context/realm-context/RealmContext";
import { toClientRole } from "../../realm-roles/routes/ClientRole";
import { toClient } from "../routes/Client"; import { toClient } from "../routes/Client";
import { toClientRole } from "../routes/ClientRole";
import { NewRoleParams } from "../routes/NewRole"; import { NewRoleParams } from "../routes/NewRole";
export default function CreateClientRole() { export default function CreateClientRole() {

View file

@ -1,34 +1,35 @@
import type { RouteDef } from "../route-config"; import type { RouteDef } from "../route-config";
import { AddClientRoute } from "./routes/AddClient"; import { AddClientRoute } from "./routes/AddClient";
import { ClientRoute } from "./routes/Client";
import { ClientsRoute, ClientsRouteWithTab } from "./routes/Clients";
import { CreateInitialAccessTokenRoute } from "./routes/CreateInitialAccessToken";
import { ImportClientRoute } from "./routes/ImportClient";
import { MapperRoute } from "./routes/Mapper";
import { ClientScopesRoute } from "./routes/ClientScopeTab";
import { AuthorizationRoute } from "./routes/AuthenticationTab"; import { AuthorizationRoute } from "./routes/AuthenticationTab";
import { NewResourceRoute } from "./routes/NewResource"; import { ClientRoute } from "./routes/Client";
import { import { ClientRoleRoute } from "./routes/ClientRole";
ResourceDetailsRoute, import { ClientsRoute, ClientsRouteWithTab } from "./routes/Clients";
ResourceDetailsWithResourceIdRoute, import { ClientScopesRoute } from "./routes/ClientScopeTab";
} from "./routes/Resource"; import { CreateInitialAccessTokenRoute } from "./routes/CreateInitialAccessToken";
import { NewRoleRoute } from "./routes/NewRole";
import { NewScopeRoute } from "./routes/NewScope";
import {
ScopeDetailsRoute,
ScopeDetailsWithScopeIdRoute,
} from "./routes/Scope";
import { NewPolicyRoute } from "./routes/NewPolicy";
import { PolicyDetailsRoute } from "./routes/PolicyDetails";
import {
NewPermissionRoute,
NewPermissionWithSelectedIdRoute,
} from "./routes/NewPermission";
import { PermissionDetailsRoute } from "./routes/PermissionDetails";
import { import {
DedicatedScopeDetailsRoute, DedicatedScopeDetailsRoute,
DedicatedScopeDetailsWithTabRoute, DedicatedScopeDetailsWithTabRoute,
} from "./routes/DedicatedScopeDetails"; } from "./routes/DedicatedScopeDetails";
import { ImportClientRoute } from "./routes/ImportClient";
import { MapperRoute } from "./routes/Mapper";
import {
NewPermissionRoute,
NewPermissionWithSelectedIdRoute,
} from "./routes/NewPermission";
import { NewPolicyRoute } from "./routes/NewPolicy";
import { NewResourceRoute } from "./routes/NewResource";
import { NewRoleRoute } from "./routes/NewRole";
import { NewScopeRoute } from "./routes/NewScope";
import { PermissionDetailsRoute } from "./routes/PermissionDetails";
import { PolicyDetailsRoute } from "./routes/PolicyDetails";
import {
ResourceDetailsRoute,
ResourceDetailsWithResourceIdRoute,
} from "./routes/Resource";
import {
ScopeDetailsRoute,
ScopeDetailsWithScopeIdRoute,
} from "./routes/Scope";
const routes: RouteDef[] = [ const routes: RouteDef[] = [
AddClientRoute, AddClientRoute,
@ -41,6 +42,7 @@ const routes: RouteDef[] = [
DedicatedScopeDetailsRoute, DedicatedScopeDetailsRoute,
DedicatedScopeDetailsWithTabRoute, DedicatedScopeDetailsWithTabRoute,
ClientScopesRoute, ClientScopesRoute,
ClientRoleRoute,
AuthorizationRoute, AuthorizationRoute,
NewResourceRoute, NewResourceRoute,
ResourceDetailsRoute, ResourceDetailsRoute,

View file

@ -13,25 +13,16 @@ export type ClientRoleParams = {
realm: string; realm: string;
clientId: string; clientId: string;
id: string; id: string;
tab?: ClientRoleTab; tab: ClientRoleTab;
}; };
export const ClientRoleRoute: RouteDef = { export const ClientRoleRoute: RouteDef = {
path: "/:realm/clients/:clientId/roles/:id", path: "/:realm/clients/:clientId/roles/:id/:tab",
component: lazy(() => import("../RealmRoleTabs")), component: lazy(() => import("../../realm-roles/RealmRoleTabs")),
breadcrumb: (t) => t("roles:roleDetails"), breadcrumb: (t) => t("roles:roleDetails"),
access: "view-realm", access: "view-realm",
}; };
export const ClientRoleRouteWithTab: RouteDef = { export const toClientRole = (params: ClientRoleParams): Partial<Path> => ({
...ClientRoleRoute, pathname: generatePath(ClientRoleRoute.path, params),
path: "/:realm/clients/:clientId/roles/:id/:tab", });
};
export const toClientRole = (params: ClientRoleParams): Partial<Path> => {
const path = params.tab ? ClientRoleRouteWithTab.path : ClientRoleRoute.path;
return {
pathname: generatePath(path, params),
};
};

View file

@ -1,32 +1,30 @@
import { FunctionComponent, useState } from "react";
import { useRouteMatch } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom-v5-compat";
import { useTranslation } from "react-i18next";
import { AlertVariant, Button, ButtonVariant } from "@patternfly/react-core";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { emptyFormatter, upperCaseFormatter } from "../util";
import { useRealm } from "../context/realm-context/RealmContext";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { HelpItem } from "../components/help-enabler/HelpItem"; import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { ClientParams, ClientRoute } from "../clients/routes/Client"; import { AlertVariant, Button, ButtonVariant } from "@patternfly/react-core";
import { toClientRole } from "./routes/ClientRole"; import { useState } from "react";
import { toRealmRole } from "./routes/RealmRole"; import { useTranslation } from "react-i18next";
import { toRealmSettings } from "../realm-settings/routes/RealmSettings"; import { Link, To, useNavigate } from "react-router-dom-v5-compat";
import "./RealmRolesSection.css"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext";
import { toRealmSettings } from "../../realm-settings/routes/RealmSettings";
import { emptyFormatter, upperCaseFormatter } from "../../util";
import { useAlerts } from "../alert/Alerts";
import { useConfirmDialog } from "../confirm-dialog/ConfirmDialog";
import { HelpItem } from "../help-enabler/HelpItem";
import { KeycloakSpinner } from "../keycloak-spinner/KeycloakSpinner";
import { ListEmptyState } from "../list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../table-toolbar/KeycloakDataTable";
import "./RolesList.css";
type RolesListProps = { type RolesListProps = {
paginated?: boolean; paginated?: boolean;
parentRoleId?: string; parentRoleId?: string;
messageBundle?: string; messageBundle?: string;
isReadOnly: boolean; isReadOnly: boolean;
toCreate: To;
toDetail: (roleId: string) => To;
loader?: ( loader?: (
first?: number, first?: number,
max?: number, max?: number,
@ -34,32 +32,19 @@ type RolesListProps = {
) => Promise<RoleRepresentation[]>; ) => Promise<RoleRepresentation[]>;
}; };
type RoleLinkProps = {
role: RoleRepresentation;
};
const RoleLink: FunctionComponent<RoleLinkProps> = ({ children, role }) => {
const { realm } = useRealm();
const clientRouteMatch = useRouteMatch<ClientParams>(ClientRoute.path);
const to = clientRouteMatch
? toClientRole({ ...clientRouteMatch.params, id: role.id!, tab: "details" })
: toRealmRole({ realm, id: role.id!, tab: "details" });
return <Link to={to}>{children}</Link>;
};
export const RolesList = ({ export const RolesList = ({
loader, loader,
paginated = true, paginated = true,
parentRoleId, parentRoleId,
messageBundle = "roles", messageBundle = "roles",
toCreate,
toDetail,
isReadOnly, isReadOnly,
}: RolesListProps) => { }: RolesListProps) => {
const { t } = useTranslation(messageBundle); const { t } = useTranslation(messageBundle);
const navigate = useNavigate(); const navigate = useNavigate();
const { adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
const { url } = useRouteMatch();
const { realm: realmName } = useRealm(); const { realm: realmName } = useRealm();
const [realm, setRealm] = useState<RealmRepresentation>(); const [realm, setRealm] = useState<RealmRepresentation>();
@ -75,7 +60,7 @@ export const RolesList = ({
const RoleDetailLink = (role: RoleRepresentation) => const RoleDetailLink = (role: RoleRepresentation) =>
role.name !== realm?.defaultRole?.name ? ( role.name !== realm?.defaultRole?.name ? (
<RoleLink role={role}>{role.name}</RoleLink> <Link to={toDetail(role.id!)}>{role.name}</Link>
) : ( ) : (
<> <>
<Link <Link
@ -116,8 +101,6 @@ export const RolesList = ({
}, },
}); });
const goToCreate = () => navigate(`${url}/new`);
if (!realm) { if (!realm) {
return <KeycloakSpinner />; return <KeycloakSpinner />;
} }
@ -133,7 +116,10 @@ export const RolesList = ({
isPaginated={paginated} isPaginated={paginated}
toolbarItem={ toolbarItem={
!isReadOnly && ( !isReadOnly && (
<Button data-testid="create-role" onClick={goToCreate}> <Button
data-testid="create-role"
component={(props) => <Link {...props} to={toCreate} />}
>
{t("createRole")} {t("createRole")}
</Button> </Button>
) )
@ -179,7 +165,7 @@ export const RolesList = ({
message={t("noRoles")} message={t("noRoles")}
instructions={isReadOnly ? "" : t("noRolesInstructions")} instructions={isReadOnly ? "" : t("noRolesInstructions")}
primaryActionText={isReadOnly ? "" : t("createRole")} primaryActionText={isReadOnly ? "" : t("createRole")}
onPrimaryAction={goToCreate} onPrimaryAction={() => navigate(toCreate)}
/> />
} }
/> />

View file

@ -92,7 +92,7 @@ const createLink = (realm: string, event: AdminEventRepresentation) => {
} }
if (event.resourcePath?.startsWith("roles-by-id")) { if (event.resourcePath?.startsWith("roles-by-id")) {
return toRealmRole({ realm, id }); return toRealmRole({ realm, id, tab: "details" });
} }
return ""; return "";

View file

@ -16,6 +16,11 @@ import { useRouteMatch } from "react-router-dom";
import { useNavigate } from "react-router-dom-v5-compat"; import { useNavigate } from "react-router-dom-v5-compat";
import { toClient } from "../clients/routes/Client"; import { toClient } from "../clients/routes/Client";
import {
ClientRoleParams,
ClientRoleRoute,
toClientRole,
} from "../clients/routes/ClientRole";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { import {
@ -37,11 +42,6 @@ import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { useParams } from "../utils/useParams"; import { useParams } from "../utils/useParams";
import {
ClientRoleParams,
ClientRoleRoute,
toClientRole,
} from "./routes/ClientRole";
import { toRealmRole } from "./routes/RealmRole"; import { toRealmRole } from "./routes/RealmRole";
import { toRealmRoles } from "./routes/RealmRoles"; import { toRealmRoles } from "./routes/RealmRoles";
import { UsersInRoleTab } from "./UsersInRoleTab"; import { UsersInRoleTab } from "./UsersInRoleTab";

View file

@ -1,13 +1,17 @@
import { PageSection } from "@patternfly/react-core"; import { PageSection } from "@patternfly/react-core";
import { RolesList } from "../components/roles-list/RolesList";
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient } from "../context/auth/AdminClient";
import { RolesList } from "./RolesList";
import helpUrls from "../help-urls";
import { useAccess } from "../context/access/Access"; import { useAccess } from "../context/access/Access";
import { useAdminClient } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import helpUrls from "../help-urls";
import { toAddRole } from "./routes/AddRole";
import { toRealmRole } from "./routes/RealmRole";
export default function RealmRolesSection() { export default function RealmRolesSection() {
const { adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
const { realm } = useRealm();
const { hasAccess } = useAccess(); const { hasAccess } = useAccess();
const isManager = hasAccess("manage-realm"); const isManager = hasAccess("manage-realm");
@ -34,7 +38,14 @@ export default function RealmRolesSection() {
helpUrl={helpUrls.realmRolesUrl} helpUrl={helpUrls.realmRolesUrl}
/> />
<PageSection variant="light" padding={{ default: "noPadding" }}> <PageSection variant="light" padding={{ default: "noPadding" }}>
<RolesList loader={loader} isReadOnly={!isManager} /> <RolesList
loader={loader}
toCreate={toAddRole({ realm })}
toDetail={(roleId) =>
toRealmRole({ realm, id: roleId, tab: "details" })
}
isReadOnly={!isManager}
/>
</PageSection> </PageSection>
</> </>
); );

View file

@ -3,6 +3,7 @@ import { QuestionCircleIcon } from "@patternfly/react-icons";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom-v5-compat"; import { useNavigate } from "react-router-dom-v5-compat";
import type { ClientRoleParams } from "../clients/routes/ClientRole";
import { useHelp } from "../components/help-enabler/HelpHeader"; import { useHelp } from "../components/help-enabler/HelpHeader";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
@ -10,7 +11,6 @@ import { useAdminClient } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { emptyFormatter, upperCaseFormatter } from "../util"; import { emptyFormatter, upperCaseFormatter } from "../util";
import { useParams } from "../utils/useParams"; import { useParams } from "../utils/useParams";
import type { ClientRoleParams } from "./routes/ClientRole";
export const UsersInRoleTab = () => { export const UsersInRoleTab = () => {
const navigate = useNavigate(); const navigate = useNavigate();

View file

@ -1,16 +1,8 @@
import type { RouteDef } from "../route-config"; import type { RouteDef } from "../route-config";
import { AddRoleRoute } from "./routes/AddRole"; import { AddRoleRoute } from "./routes/AddRole";
import { ClientRoleRoute, ClientRoleRouteWithTab } from "./routes/ClientRole"; import { RealmRoleRoute } from "./routes/RealmRole";
import { RealmRoleRoute, RealmRoleRouteWithTab } from "./routes/RealmRole";
import { RealmRolesRoute } from "./routes/RealmRoles"; import { RealmRolesRoute } from "./routes/RealmRoles";
const routes: RouteDef[] = [ const routes: RouteDef[] = [RealmRolesRoute, AddRoleRoute, RealmRoleRoute];
ClientRoleRoute,
ClientRoleRouteWithTab,
RealmRolesRoute,
AddRoleRoute,
RealmRoleRoute,
RealmRoleRouteWithTab,
];
export default routes; export default routes;

View file

@ -1,6 +1,7 @@
import { lazy } from "react"; import { lazy } from "react";
import type { Path } from "react-router-dom-v5-compat"; import type { Path } from "react-router-dom-v5-compat";
import { generatePath } from "react-router-dom-v5-compat"; import { generatePath } from "react-router-dom-v5-compat";
import type { RouteDef } from "../../route-config"; import type { RouteDef } from "../../route-config";
export type RealmRoleTab = export type RealmRoleTab =
@ -12,25 +13,16 @@ export type RealmRoleTab =
export type RealmRoleParams = { export type RealmRoleParams = {
realm: string; realm: string;
id: string; id: string;
tab?: RealmRoleTab; tab: RealmRoleTab;
}; };
export const RealmRoleRoute: RouteDef = { export const RealmRoleRoute: RouteDef = {
path: "/:realm/roles/:id", path: "/:realm/roles/:id/:tab",
component: lazy(() => import("../RealmRoleTabs")), component: lazy(() => import("../RealmRoleTabs")),
breadcrumb: (t) => t("roles:roleDetails"), breadcrumb: (t) => t("roles:roleDetails"),
access: ["view-realm", "view-users"], access: ["view-realm", "view-users"],
}; };
export const RealmRoleRouteWithTab: RouteDef = { export const toRealmRole = (params: RealmRoleParams): Partial<Path> => ({
...RealmRoleRoute, pathname: generatePath(RealmRoleRoute.path, params),
path: "/:realm/roles/:id/:tab", });
};
export const toRealmRole = (params: RealmRoleParams): Partial<Path> => {
const path = params.tab ? RealmRoleRouteWithTab.path : RealmRoleRoute.path;
return {
pathname: generatePath(path, params),
};
};