From d11ed1ccef45ebf370d3e4bd97369ba35801230e Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Thu, 12 Jan 2023 16:51:08 +0100 Subject: [PATCH] Refactor roles list to React Router v6 (#4187) --- apps/admin-ui/src/clients/ClientDetails.tsx | 13 +++- .../src/clients/roles/CreateClientRole.tsx | 2 +- apps/admin-ui/src/clients/routes.ts | 50 +++++++------- .../routes/ClientRole.ts | 21 ++---- .../roles-list/RolesList.css} | 0 .../roles-list}/RolesList.tsx | 68 ++++++++----------- apps/admin-ui/src/events/ResourceLinks.tsx | 2 +- .../src/realm-roles/RealmRoleTabs.tsx | 10 +-- .../src/realm-roles/RealmRolesSection.tsx | 21 ++++-- .../src/realm-roles/UsersInRoleTab.tsx | 2 +- apps/admin-ui/src/realm-roles/routes.ts | 12 +--- .../src/realm-roles/routes/RealmRole.ts | 20 ++---- 12 files changed, 103 insertions(+), 118 deletions(-) rename apps/admin-ui/src/{realm-roles => clients}/routes/ClientRole.ts (63%) rename apps/admin-ui/src/{realm-roles/RealmRolesSection.css => components/roles-list/RolesList.css} (100%) rename apps/admin-ui/src/{realm-roles => components/roles-list}/RolesList.tsx (70%) diff --git a/apps/admin-ui/src/clients/ClientDetails.tsx b/apps/admin-ui/src/clients/ClientDetails.tsx index c2aa92915e..028d98f55e 100644 --- a/apps/admin-ui/src/clients/ClientDetails.tsx +++ b/apps/admin-ui/src/clients/ClientDetails.tsx @@ -31,6 +31,7 @@ import { DownloadDialog } from "../components/download-dialog/DownloadDialog"; import type { KeyValueType } from "../components/key-value-form/key-value-convert"; import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; import { PermissionsTab } from "../components/permission-tab/PermissionTab"; +import { RolesList } from "../components/roles-list/RolesList"; import { RoutableTabs, useRoutableTab, @@ -43,7 +44,6 @@ import { useAccess } from "../context/access/Access"; import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import { useRealm } from "../context/realm-context/RealmContext"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; -import { RolesList } from "../realm-roles/RolesList"; import { convertAttributeNameToForm, convertFormValuesToObject, @@ -70,8 +70,10 @@ import { toAuthorizationTab, } from "./routes/AuthenticationTab"; import { ClientParams, ClientTab, toClient } from "./routes/Client"; +import { toClientRole } from "./routes/ClientRole"; import { toClients } from "./routes/Clients"; import { ClientScopesTab, toClientScopesTab } from "./routes/ClientScopeTab"; +import { toCreateRole } from "./routes/NewRole"; import { ClientScopes } from "./scopes/ClientScopes"; import { EvaluateScopes } from "./scopes/EvaluateScopes"; import { ServiceAccount } from "./service-account/ServiceAccount"; @@ -474,6 +476,15 @@ export default function ClientDetails() { loader={loader} paginated={false} messageBundle="clients" + toCreate={toCreateRole({ realm, clientId: client.id! })} + toDetail={(roleId) => + toClientRole({ + realm, + clientId: client.id!, + id: roleId, + tab: "details", + }) + } isReadOnly={!(hasManageClients || client.access?.configure)} /> diff --git a/apps/admin-ui/src/clients/roles/CreateClientRole.tsx b/apps/admin-ui/src/clients/roles/CreateClientRole.tsx index 81c83b55c4..eddcb8e6a9 100644 --- a/apps/admin-ui/src/clients/roles/CreateClientRole.tsx +++ b/apps/admin-ui/src/clients/roles/CreateClientRole.tsx @@ -9,8 +9,8 @@ import { AttributeForm } from "../../components/key-value-form/AttributeForm"; import { RoleForm } from "../../components/role-form/RoleForm"; import { useAdminClient } from "../../context/auth/AdminClient"; import { useRealm } from "../../context/realm-context/RealmContext"; -import { toClientRole } from "../../realm-roles/routes/ClientRole"; import { toClient } from "../routes/Client"; +import { toClientRole } from "../routes/ClientRole"; import { NewRoleParams } from "../routes/NewRole"; export default function CreateClientRole() { diff --git a/apps/admin-ui/src/clients/routes.ts b/apps/admin-ui/src/clients/routes.ts index 532de0e7b6..330f0279c8 100644 --- a/apps/admin-ui/src/clients/routes.ts +++ b/apps/admin-ui/src/clients/routes.ts @@ -1,34 +1,35 @@ import type { RouteDef } from "../route-config"; 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 { NewResourceRoute } from "./routes/NewResource"; -import { - ResourceDetailsRoute, - ResourceDetailsWithResourceIdRoute, -} from "./routes/Resource"; -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 { ClientRoute } from "./routes/Client"; +import { ClientRoleRoute } from "./routes/ClientRole"; +import { ClientsRoute, ClientsRouteWithTab } from "./routes/Clients"; +import { ClientScopesRoute } from "./routes/ClientScopeTab"; +import { CreateInitialAccessTokenRoute } from "./routes/CreateInitialAccessToken"; import { DedicatedScopeDetailsRoute, DedicatedScopeDetailsWithTabRoute, } 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[] = [ AddClientRoute, @@ -41,6 +42,7 @@ const routes: RouteDef[] = [ DedicatedScopeDetailsRoute, DedicatedScopeDetailsWithTabRoute, ClientScopesRoute, + ClientRoleRoute, AuthorizationRoute, NewResourceRoute, ResourceDetailsRoute, diff --git a/apps/admin-ui/src/realm-roles/routes/ClientRole.ts b/apps/admin-ui/src/clients/routes/ClientRole.ts similarity index 63% rename from apps/admin-ui/src/realm-roles/routes/ClientRole.ts rename to apps/admin-ui/src/clients/routes/ClientRole.ts index b2861d94a4..075537e5e0 100644 --- a/apps/admin-ui/src/realm-roles/routes/ClientRole.ts +++ b/apps/admin-ui/src/clients/routes/ClientRole.ts @@ -13,25 +13,16 @@ export type ClientRoleParams = { realm: string; clientId: string; id: string; - tab?: ClientRoleTab; + tab: ClientRoleTab; }; export const ClientRoleRoute: RouteDef = { - path: "/:realm/clients/:clientId/roles/:id", - component: lazy(() => import("../RealmRoleTabs")), + path: "/:realm/clients/:clientId/roles/:id/:tab", + component: lazy(() => import("../../realm-roles/RealmRoleTabs")), breadcrumb: (t) => t("roles:roleDetails"), access: "view-realm", }; -export const ClientRoleRouteWithTab: RouteDef = { - ...ClientRoleRoute, - path: "/:realm/clients/:clientId/roles/:id/:tab", -}; - -export const toClientRole = (params: ClientRoleParams): Partial => { - const path = params.tab ? ClientRoleRouteWithTab.path : ClientRoleRoute.path; - - return { - pathname: generatePath(path, params), - }; -}; +export const toClientRole = (params: ClientRoleParams): Partial => ({ + pathname: generatePath(ClientRoleRoute.path, params), +}); diff --git a/apps/admin-ui/src/realm-roles/RealmRolesSection.css b/apps/admin-ui/src/components/roles-list/RolesList.css similarity index 100% rename from apps/admin-ui/src/realm-roles/RealmRolesSection.css rename to apps/admin-ui/src/components/roles-list/RolesList.css diff --git a/apps/admin-ui/src/realm-roles/RolesList.tsx b/apps/admin-ui/src/components/roles-list/RolesList.tsx similarity index 70% rename from apps/admin-ui/src/realm-roles/RolesList.tsx rename to apps/admin-ui/src/components/roles-list/RolesList.tsx index 8893c2a7cf..c62994fddb 100644 --- a/apps/admin-ui/src/realm-roles/RolesList.tsx +++ b/apps/admin-ui/src/components/roles-list/RolesList.tsx @@ -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 { HelpItem } from "../components/help-enabler/HelpItem"; -import { ClientParams, ClientRoute } from "../clients/routes/Client"; -import { toClientRole } from "./routes/ClientRole"; -import { toRealmRole } from "./routes/RealmRole"; -import { toRealmSettings } from "../realm-settings/routes/RealmSettings"; +import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation"; +import { AlertVariant, Button, ButtonVariant } from "@patternfly/react-core"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +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 = { paginated?: boolean; parentRoleId?: string; messageBundle?: string; isReadOnly: boolean; + toCreate: To; + toDetail: (roleId: string) => To; loader?: ( first?: number, max?: number, @@ -34,32 +32,19 @@ type RolesListProps = { ) => Promise; }; -type RoleLinkProps = { - role: RoleRepresentation; -}; - -const RoleLink: FunctionComponent = ({ children, role }) => { - const { realm } = useRealm(); - const clientRouteMatch = useRouteMatch(ClientRoute.path); - const to = clientRouteMatch - ? toClientRole({ ...clientRouteMatch.params, id: role.id!, tab: "details" }) - : toRealmRole({ realm, id: role.id!, tab: "details" }); - - return {children}; -}; - export const RolesList = ({ loader, paginated = true, parentRoleId, messageBundle = "roles", + toCreate, + toDetail, isReadOnly, }: RolesListProps) => { const { t } = useTranslation(messageBundle); const navigate = useNavigate(); const { adminClient } = useAdminClient(); const { addAlert, addError } = useAlerts(); - const { url } = useRouteMatch(); const { realm: realmName } = useRealm(); const [realm, setRealm] = useState(); @@ -75,7 +60,7 @@ export const RolesList = ({ const RoleDetailLink = (role: RoleRepresentation) => role.name !== realm?.defaultRole?.name ? ( - {role.name} + {role.name} ) : ( <> navigate(`${url}/new`); - if (!realm) { return ; } @@ -133,7 +116,10 @@ export const RolesList = ({ isPaginated={paginated} toolbarItem={ !isReadOnly && ( - ) @@ -179,7 +165,7 @@ export const RolesList = ({ message={t("noRoles")} instructions={isReadOnly ? "" : t("noRolesInstructions")} primaryActionText={isReadOnly ? "" : t("createRole")} - onPrimaryAction={goToCreate} + onPrimaryAction={() => navigate(toCreate)} /> } /> diff --git a/apps/admin-ui/src/events/ResourceLinks.tsx b/apps/admin-ui/src/events/ResourceLinks.tsx index 773603c95c..c230b2653f 100644 --- a/apps/admin-ui/src/events/ResourceLinks.tsx +++ b/apps/admin-ui/src/events/ResourceLinks.tsx @@ -92,7 +92,7 @@ const createLink = (realm: string, event: AdminEventRepresentation) => { } if (event.resourcePath?.startsWith("roles-by-id")) { - return toRealmRole({ realm, id }); + return toRealmRole({ realm, id, tab: "details" }); } return ""; diff --git a/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx b/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx index a3ad3d7a4c..e9f12ea0b9 100644 --- a/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx +++ b/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx @@ -16,6 +16,11 @@ import { useRouteMatch } from "react-router-dom"; import { useNavigate } from "react-router-dom-v5-compat"; import { toClient } from "../clients/routes/Client"; +import { + ClientRoleParams, + ClientRoleRoute, + toClientRole, +} from "../clients/routes/ClientRole"; import { useAlerts } from "../components/alert/Alerts"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { @@ -37,11 +42,6 @@ import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import { useRealm } from "../context/realm-context/RealmContext"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useParams } from "../utils/useParams"; -import { - ClientRoleParams, - ClientRoleRoute, - toClientRole, -} from "./routes/ClientRole"; import { toRealmRole } from "./routes/RealmRole"; import { toRealmRoles } from "./routes/RealmRoles"; import { UsersInRoleTab } from "./UsersInRoleTab"; diff --git a/apps/admin-ui/src/realm-roles/RealmRolesSection.tsx b/apps/admin-ui/src/realm-roles/RealmRolesSection.tsx index 3b8142cf78..e1b63c9a61 100644 --- a/apps/admin-ui/src/realm-roles/RealmRolesSection.tsx +++ b/apps/admin-ui/src/realm-roles/RealmRolesSection.tsx @@ -1,13 +1,17 @@ import { PageSection } from "@patternfly/react-core"; + +import { RolesList } from "../components/roles-list/RolesList"; 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 { 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() { const { adminClient } = useAdminClient(); - + const { realm } = useRealm(); const { hasAccess } = useAccess(); const isManager = hasAccess("manage-realm"); @@ -34,7 +38,14 @@ export default function RealmRolesSection() { helpUrl={helpUrls.realmRolesUrl} /> - + + toRealmRole({ realm, id: roleId, tab: "details" }) + } + isReadOnly={!isManager} + /> ); diff --git a/apps/admin-ui/src/realm-roles/UsersInRoleTab.tsx b/apps/admin-ui/src/realm-roles/UsersInRoleTab.tsx index 181fa0b661..5bacebc486 100644 --- a/apps/admin-ui/src/realm-roles/UsersInRoleTab.tsx +++ b/apps/admin-ui/src/realm-roles/UsersInRoleTab.tsx @@ -3,6 +3,7 @@ import { QuestionCircleIcon } from "@patternfly/react-icons"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom-v5-compat"; +import type { ClientRoleParams } from "../clients/routes/ClientRole"; import { useHelp } from "../components/help-enabler/HelpHeader"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; 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 { emptyFormatter, upperCaseFormatter } from "../util"; import { useParams } from "../utils/useParams"; -import type { ClientRoleParams } from "./routes/ClientRole"; export const UsersInRoleTab = () => { const navigate = useNavigate(); diff --git a/apps/admin-ui/src/realm-roles/routes.ts b/apps/admin-ui/src/realm-roles/routes.ts index 517872943e..6a0c842f7e 100644 --- a/apps/admin-ui/src/realm-roles/routes.ts +++ b/apps/admin-ui/src/realm-roles/routes.ts @@ -1,16 +1,8 @@ import type { RouteDef } from "../route-config"; import { AddRoleRoute } from "./routes/AddRole"; -import { ClientRoleRoute, ClientRoleRouteWithTab } from "./routes/ClientRole"; -import { RealmRoleRoute, RealmRoleRouteWithTab } from "./routes/RealmRole"; +import { RealmRoleRoute } from "./routes/RealmRole"; import { RealmRolesRoute } from "./routes/RealmRoles"; -const routes: RouteDef[] = [ - ClientRoleRoute, - ClientRoleRouteWithTab, - RealmRolesRoute, - AddRoleRoute, - RealmRoleRoute, - RealmRoleRouteWithTab, -]; +const routes: RouteDef[] = [RealmRolesRoute, AddRoleRoute, RealmRoleRoute]; export default routes; diff --git a/apps/admin-ui/src/realm-roles/routes/RealmRole.ts b/apps/admin-ui/src/realm-roles/routes/RealmRole.ts index c81dc3b363..4e0866683d 100644 --- a/apps/admin-ui/src/realm-roles/routes/RealmRole.ts +++ b/apps/admin-ui/src/realm-roles/routes/RealmRole.ts @@ -1,6 +1,7 @@ import { lazy } from "react"; import type { Path } from "react-router-dom-v5-compat"; import { generatePath } from "react-router-dom-v5-compat"; + import type { RouteDef } from "../../route-config"; export type RealmRoleTab = @@ -12,25 +13,16 @@ export type RealmRoleTab = export type RealmRoleParams = { realm: string; id: string; - tab?: RealmRoleTab; + tab: RealmRoleTab; }; export const RealmRoleRoute: RouteDef = { - path: "/:realm/roles/:id", + path: "/:realm/roles/:id/:tab", component: lazy(() => import("../RealmRoleTabs")), breadcrumb: (t) => t("roles:roleDetails"), access: ["view-realm", "view-users"], }; -export const RealmRoleRouteWithTab: RouteDef = { - ...RealmRoleRoute, - path: "/:realm/roles/:id/:tab", -}; - -export const toRealmRole = (params: RealmRoleParams): Partial => { - const path = params.tab ? RealmRoleRouteWithTab.path : RealmRoleRoute.path; - - return { - pathname: generatePath(path, params), - }; -}; +export const toRealmRole = (params: RealmRoleParams): Partial => ({ + pathname: generatePath(RealmRoleRoute.path, params), +});