Use routable tabs for realm role details (#3963)

This commit is contained in:
Jon Koops 2023-01-14 17:05:09 +01:00 committed by GitHub
parent fe4c3dc151
commit 5ba147b384
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 42 additions and 113 deletions

View file

@ -11,7 +11,7 @@ export default class ClientRolesTab extends CommonPage {
private createRoleEmptyStateBtn = "no-roles-for-this-client-empty-action";
private hideInheritedRolesChkBox = "#hideInheritedRoles";
private rolesTab = "rolesTab";
private associatedRolesTab = ".kc-associated-roles-tab > button";
private associatedRolesTab = "associatedRolesTab";
goToDetailsTab() {
this.tabUtils().clickTab(ClientRolesTabItems.Details);
@ -34,7 +34,7 @@ export default class ClientRolesTab extends CommonPage {
}
goToAssociatedRolesTab() {
cy.get(this.associatedRolesTab).click();
cy.findByTestId(this.associatedRolesTab).click();
return this;
}

View file

@ -63,7 +63,7 @@ class CreateRealmRolePage {
}
goToAttributesTab() {
cy.get(".kc-attributes-tab > button").click();
cy.findByTestId("attributesTab").click();
return this;
}
}

View file

@ -194,8 +194,6 @@
"emptyMappers": "No mappers",
"emptyMappersInstructions": "If you want to add mappers, please click the button below to add some predefined mappers or to configure a new mapper.",
"emptyPrimaryAction": "Add predefined mapper",
"leaveDirtyTitle": "Leave without saving?",
"leaveDirtyConfirm": "Do you want to leave this page without saving? Any unsaved changes will be lost.",
"leave": "Leave",
"reorder": "Reorder",
"onDragStart": "Dragging started for item {{item}}",

View file

@ -1,85 +0,0 @@
import { Children, isValidElement, useState } from "react";
import { useRouteMatch } from "react-router-dom";
import { useNavigate } from "react-router-dom-v5-compat";
import { TabProps, Tabs, TabsProps } from "@patternfly/react-core";
import { useFormContext } from "react-hook-form";
import { useConfirmDialog } from "../confirm-dialog/ConfirmDialog";
type KeycloakTabsProps = Omit<TabsProps, "ref" | "activeKey" | "onSelect"> & {
paramName?: string;
};
const createUrl = (
path: string,
params: { [index: string]: string }
): string => {
let url = path;
for (const key in params) {
const value = params[key];
if (url.includes(key)) {
url = url.replace(new RegExp(`:${key}\\??`), value || "");
}
}
return url;
};
export const KeycloakTabs = ({
paramName = "tab",
children,
...rest
}: KeycloakTabsProps) => {
const match = useRouteMatch();
const params = match.params as { [index: string]: string };
const navigate = useNavigate();
const form = useFormContext() as
| ReturnType<typeof useFormContext>
| undefined;
const [key, setKey] = useState("");
const firstTab = Children.toArray(children)[0];
const tab =
params[paramName] ||
(isValidElement<TabProps>(firstTab) && firstTab.props.eventKey) ||
"";
const pathIndex = match.path.indexOf(paramName) + paramName.length;
const path = match.path.substr(0, pathIndex);
const [toggleChangeTabDialog, ChangeTabConfirm] = useConfirmDialog({
titleKey: "common:leaveDirtyTitle",
messageKey: "common:leaveDirtyConfirm",
continueButtonLabel: "common:leave",
onConfirm: () => {
form?.reset();
navigate(createUrl(path, { ...params, [paramName]: key as string }));
},
});
return (
<>
<ChangeTabConfirm />
<Tabs
inset={{
default: "insetNone",
md: "insetSm",
xl: "inset2xl",
"2xl": "insetLg",
}}
activeKey={tab}
onSelect={(_, key) => {
if (form?.formState.isDirty) {
setKey(key as string);
toggleChangeTabDialog();
} else {
navigate(
createUrl(path, { ...params, [paramName]: key as string })
);
}
}}
{...rest}
>
{children}
</Tabs>
</>
);
};

View file

@ -4,7 +4,6 @@ import {
TabsComponent,
TabsProps,
} from "@patternfly/react-core";
import type { History } from "history";
import {
Children,
isValidElement,
@ -66,16 +65,6 @@ export const RoutableTabs = ({
);
};
type RoutableTabParams = {
to: Partial<Path>;
history: History<unknown>;
};
export const routableTab = ({ to, history }: RoutableTabParams) => ({
eventKey: to.pathname ?? "",
href: history.createHref(to),
});
export const useRoutableTab = (to: Partial<Path>) => ({
eventKey: to.pathname ?? "",
href: useHref(to),

View file

@ -19,6 +19,7 @@ import { toClient } from "../clients/routes/Client";
import {
ClientRoleParams,
ClientRoleRoute,
ClientRoleTab,
toClientRole,
} from "../clients/routes/ClientRole";
import { useAlerts } from "../components/alert/Alerts";
@ -32,17 +33,20 @@ import {
keyValueToArray,
} from "../components/key-value-form/key-value-convert";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { PermissionsTab } from "../components/permission-tab/PermissionTab";
import { RoleForm } from "../components/role-form/RoleForm";
import { AddRoleMappingModal } from "../components/role-mapping/AddRoleMappingModal";
import { RoleMapping } from "../components/role-mapping/RoleMapping";
import {
RoutableTabs,
useRoutableTab,
} from "../components/routable-tabs/RoutableTabs";
import { ViewHeader } from "../components/view-header/ViewHeader";
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 { RealmRoleRoute, toRealmRole } from "./routes/RealmRole";
import { RealmRoleRoute, RealmRoleTab, toRealmRole } from "./routes/RealmRole";
import { toRealmRoles } from "./routes/RealmRoles";
import { UsersInRoleTab } from "./UsersInRoleTab";
@ -57,7 +61,7 @@ export default function RealmRoleTabs() {
const { adminClient } = useAdminClient();
const [role, setRole] = useState<AttributeForm>();
const { id, clientId } = useParams<{ id: string; clientId: string }>();
const { id, clientId } = useParams<ClientRoleParams>();
const { pathname } = useLocation();
const { realm: realmName } = useRealm();
@ -292,6 +296,27 @@ export default function RealmRoleTabs() {
navigate(to);
};
const toTab = (tab: RealmRoleTab | ClientRoleTab) =>
clientRoleRouteMatch
? toClientRole({
...clientRoleRouteMatch.params,
tab: tab as ClientRoleTab,
})
: toRealmRole({
realm: realmName,
id,
tab,
});
const useTab = (tab: RealmRoleTab | ClientRoleTab) =>
useRoutableTab(toTab(tab));
const detailsTab = useTab("details");
const associatedRolesTab = useTab("associated-roles");
const attributesTab = useTab("attributes");
const usersInRoleTab = useTab("users-in-role");
const permissionsTab = useTab("permissions");
const addComposites = async (composites: RoleRepresentation[]) => {
try {
await adminClient.roles.createComposite(
@ -339,10 +364,10 @@ export default function RealmRoleTabs() {
divider={false}
/>
<PageSection variant="light" className="pf-u-p-0">
<KeycloakTabs isBox mountOnEnter>
<RoutableTabs isBox mountOnEnter defaultLocation={toTab("details")}>
<Tab
eventKey="details"
title={<TabTitleText>{t("common:details")}</TabTitleText>}
{...detailsTab}
>
<RoleForm
form={form}
@ -358,9 +383,9 @@ export default function RealmRoleTabs() {
</Tab>
{role.composite && (
<Tab
eventKey="associated-roles"
className="kc-associated-roles-tab"
data-testid="associatedRolesTab"
title={<TabTitleText>{t("associatedRolesText")}</TabTitleText>}
{...associatedRolesTab}
>
<RoleMapping
name={role.name!}
@ -373,9 +398,10 @@ export default function RealmRoleTabs() {
)}
{!isDefaultRole(role.name!) && (
<Tab
eventKey="attributes"
data-testid="attributesTab"
className="kc-attributes-tab"
title={<TabTitleText>{t("common:attributes")}</TabTitleText>}
{...attributesTab}
>
<AttributesForm
form={form}
@ -386,8 +412,8 @@ export default function RealmRoleTabs() {
)}
{!isDefaultRole(role.name!) && (
<Tab
eventKey="users-in-role"
title={<TabTitleText>{t("usersInRole")}</TabTitleText>}
{...usersInRoleTab}
>
<UsersInRoleTab data-cy="users-in-role-tab" />
</Tab>
@ -396,13 +422,13 @@ export default function RealmRoleTabs() {
"ADMIN_FINE_GRAINED_AUTHZ"
) && (
<Tab
eventKey="permissions"
title={<TabTitleText>{t("common:permissions")}</TabTitleText>}
{...permissionsTab}
>
<PermissionsTab id={role.id} type="roles" />
</Tab>
)}
</KeycloakTabs>
</RoutableTabs>
</PageSection>
</>
);

View file

@ -8,7 +8,8 @@ export type RealmRoleTab =
| "details"
| "associated-roles"
| "attributes"
| "users-in-role";
| "users-in-role"
| "permissions";
export type RealmRoleParams = {
realm: string;