Changed to use routable tabs (#1934)

* Changed to use routable tabs

* fixed tests
This commit is contained in:
Erik Jan de Wit 2022-01-31 08:20:35 +01:00 committed by GitHub
parent b9e79b6d75
commit 8b0327fa2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 203 additions and 75 deletions

View file

@ -73,7 +73,7 @@ describe("Clients SAML tests", () => {
loginPage.logIn();
sidebarPage.goToClients();
listingPage.searchItem(clientId).goToItemDetails(clientId);
cy.get("#pf-tab-keys-keys").click();
cy.findByTestId("keysTab").click();
});
it("doesn't disable when no", () => {

View file

@ -279,7 +279,7 @@ describe("Clients test", () => {
});
it("add mapping to openid client", () => {
cy.get("#pf-tab-mappers-mappers").click();
cy.findByTestId("mappersTab").click();
cy.findByText("Add predefined mapper").click();
cy.get("table input").first().click();
cy.findByTestId("modalConfirm").click();
@ -343,15 +343,13 @@ describe("Clients test", () => {
it("displays the correct tabs", () => {
cy.findByTestId("client-tabs")
.find("#pf-tab-settings-settings")
.findByTestId("clientSettingsTab")
.should("exist");
cy.findByTestId("client-tabs")
.find("#pf-tab-roles-roles")
.should("exist");
cy.findByTestId("client-tabs").findByTestId("rolesTab").should("exist");
cy.findByTestId("client-tabs")
.find("#pf-tab-advanced-advanced")
.findByTestId("advancedTab")
.should("exist");
cy.findByTestId("client-tabs").find("li").should("have.length", 3);

View file

@ -1,6 +1,6 @@
const expect = chai.expect;
export default class RoleMappingTab {
private tab = "#pf-tab-serviceAccount-serviceAccount";
private tab = "serviceAccountTab";
private scopeTab = "scopeTab";
private assignEmptyRoleBtn = "no-roles-for-this-client-empty-action";
private assignRoleBtn = "assignRole";
@ -12,7 +12,7 @@ export default class RoleMappingTab {
private confirmModalBtn = "modalConfirm";
goToServiceAccountTab() {
cy.get(this.tab).click();
cy.findByTestId(this.tab).click();
return this;
}

View file

@ -17,10 +17,10 @@ export default class AdvancedTab {
private fineGrainSaveBtn = "#fineGrainSave";
private fineGrainRevertBtn = "#fineGrainRevert";
private advancedTab = "#pf-tab-advanced-advanced";
private advancedTab = "advancedTab";
goToAdvancedTab() {
cy.get(this.advancedTab).click();
cy.findByTestId(this.advancedTab).click();
return this;
}

View file

@ -5,11 +5,11 @@ import type ScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/s
type PermissionType = "resource" | "scope";
export default class AuthorizationTab {
private tabName = "#pf-tab-authorization-authorization";
private resourcesTabName = "#pf-tab-41-resources";
private scopeTabName = "#pf-tab-42-scopes";
private policyTabName = "#pf-tab-43-policies";
private permissionsTabName = "#pf-tab-44-permissions";
private tabName = "authorizationTab";
private resourcesTabName = "authorizationResources";
private scopeTabName = "authorizationScopes";
private policyTabName = "authorizationPolicies";
private permissionsTabName = "authorizationPermissions";
private nameColumnPrefix = "name-column-";
private emptyPolicyCreateButton = "no-policies-empty-action";
private createPolicyButton = "createPolicy";
@ -19,27 +19,27 @@ export default class AuthorizationTab {
private permissionResourceDropdown = "#resources";
goToAuthenticationTab() {
cy.get(this.tabName).click();
cy.findByTestId(this.tabName).click();
return this;
}
goToResourceSubTab() {
cy.get(this.resourcesTabName).click();
cy.findByTestId(this.resourcesTabName).click();
return this;
}
goToScopeSubTab() {
cy.get(this.scopeTabName).click();
cy.findByTestId(this.scopeTabName).click();
return this;
}
goToPolicySubTab() {
cy.get(this.policyTabName).click();
cy.findByTestId(this.policyTabName).click();
return this;
}
goToPermissionsSubTab() {
cy.get(this.permissionsTabName).click();
cy.findByTestId(this.permissionsTabName).click();
return this;
}

View file

@ -1,8 +1,8 @@
export default class ClientScopesTab {
private clientScopesTab = "#pf-tab-clientScopes-clientScopes";
private clientScopesTab = "clientScopesTab";
goToClientScopesTab() {
cy.get(this.clientScopesTab).click();
cy.findByTestId(this.clientScopesTab).click();
return this;
}
}

View file

@ -1,5 +1,5 @@
export default class KeysTab {
private tabName = "#pf-tab-keys-keys";
private tabName = "keysTab";
private useJwksUrl = "useJwksUrl";
private saveKeys = "saveKeys";
private generate = "generate";
@ -9,7 +9,7 @@ export default class KeysTab {
private confirm = "confirm";
goToTab() {
cy.get(this.tabName).click();
cy.findByTestId(this.tabName).click();
return this;
}

View file

@ -7,7 +7,6 @@ import {
Label,
PageSection,
Tab,
Tabs,
TabTitleText,
Tooltip,
} from "@patternfly/react-core";
@ -24,7 +23,6 @@ import {
useConfirmDialog,
} from "../components/confirm-dialog/ConfirmDialog";
import { DownloadDialog } from "../components/download-dialog/DownloadDialog";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import type { MultiLine } from "../components/multi-line-input/multi-line-convert";
import {
ViewHeader,
@ -44,7 +42,7 @@ import { AdvancedTab } from "./AdvancedTab";
import { ClientSettings } from "./ClientSettings";
import { Credentials } from "./credentials/Credentials";
import { Keys } from "./keys/Keys";
import type { ClientParams } from "./routes/Client";
import { ClientParams, ClientTab, toClient } from "./routes/Client";
import { toClients } from "./routes/Clients";
import { ClientScopes } from "./scopes/ClientScopes";
import { EvaluateScopes } from "./scopes/EvaluateScopes";
@ -63,6 +61,15 @@ import { AuthorizationPermissions } from "./authorization/Permissions";
import { AuthorizationEvaluate } from "./authorization/AuthorizationEvaluate";
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import {
routableTab,
RoutableTabs,
} from "../components/routable-tabs/RoutableTabs";
import {
AuthorizationTab,
toAuthorizationTab,
} from "./routes/AuthenticationTab";
import { toClientScopesTab } from "./routes/ClientScopeTab";
type ClientDetailHeaderProps = {
onChange: (value: boolean) => void;
@ -185,8 +192,6 @@ export default function ClientDetails() {
const [downloadDialogOpen, toggleDownloadDialogOpen] = useToggle();
const [changeAuthenticatorOpen, toggleChangeAuthenticatorOpen] = useToggle();
const [clientScopeSubTab, setClientScopeSubTab] = useState(30);
const [authorizationSubTab, setAuthorizationSubTab] = useState(40);
const form = useForm<ClientForm>({ shouldUnregister: false });
const { clientId } = useParams<ClientParams>();
@ -341,6 +346,26 @@ export default function ClientDetails() {
return <KeycloakSpinner />;
}
const route = (tab: ClientTab) =>
routableTab({
to: toClient({
realm,
clientId,
tab,
}),
history,
});
const authenticationRoute = (tab: AuthorizationTab) =>
routableTab({
to: toAuthorizationTab({
realm,
clientId,
tab,
}),
history,
});
return (
<>
<ConfirmDialogModal
@ -385,11 +410,12 @@ export default function ClientDetails() {
/>
<PageSection variant="light" className="pf-u-p-0">
<FormProvider {...form}>
<KeycloakTabs data-testid="client-tabs" isBox mountOnEnter>
<RoutableTabs data-testid="client-tabs" isBox mountOnEnter>
<Tab
id="settings"
eventKey="settings"
data-testid="clientSettingsTab"
title={<TabTitleText>{t("common:settings")}</TabTitleText>}
{...route("settings")}
>
<ClientSettings
client={client}
@ -401,8 +427,9 @@ export default function ClientDetails() {
client.protocol === "saml") && (
<Tab
id="keys"
eventKey="keys"
data-testid="keysTab"
title={<TabTitleText>{t("keys")}</TabTitleText>}
{...route("keys")}
>
{client.protocol === "openid-connect" && (
<Keys clientId={clientId} save={save} />
@ -415,8 +442,8 @@ export default function ClientDetails() {
{!client.publicClient && !isRealmClient(client) && (
<Tab
id="credentials"
eventKey="credentials"
title={<TabTitleText>{t("credentials")}</TabTitleText>}
{...route("credentials")}
>
<Credentials clientId={clientId} save={() => save()} />
</Tab>
@ -424,8 +451,9 @@ export default function ClientDetails() {
{!isRealmClient(client) && (
<Tab
id="mappers"
eventKey="mappers"
data-testid="mappersTab"
title={<TabTitleText>{t("mappers")}</TabTitleText>}
{...route("mappers")}
>
<MapperList
model={client}
@ -439,8 +467,9 @@ export default function ClientDetails() {
)}
<Tab
id="roles"
eventKey="roles"
data-testid="rolesTab"
title={<TabTitleText>{t("roles")}</TabTitleText>}
{...route("roles")}
>
<RolesList
loader={loader}
@ -451,17 +480,28 @@ export default function ClientDetails() {
{!isRealmClient(client) && (
<Tab
id="clientScopes"
eventKey="clientScopes"
data-testid="clientScopesTab"
title={<TabTitleText>{t("clientScopes")}</TabTitleText>}
{...route("clientScopes")}
>
<Tabs
activeKey={clientScopeSubTab}
onSelect={(_, key) => setClientScopeSubTab(key as number)}
<RoutableTabs
defaultLocation={toClientScopesTab({
realm,
clientId,
tab: "setup",
})}
>
<Tab
id="setup"
eventKey={30}
title={<TabTitleText>{t("setup")}</TabTitleText>}
{...routableTab({
to: toClientScopesTab({
realm,
clientId,
tab: "setup",
}),
history,
})}
>
<ClientScopes
clientName={client.clientId!}
@ -471,68 +511,85 @@ export default function ClientDetails() {
</Tab>
<Tab
id="evaluate"
eventKey={31}
title={<TabTitleText>{t("evaluate")}</TabTitleText>}
{...routableTab({
to: toClientScopesTab({
realm,
clientId,
tab: "evaluate",
}),
history,
})}
>
<EvaluateScopes
clientId={clientId}
protocol={client!.protocol!}
/>
</Tab>
</Tabs>
</RoutableTabs>
</Tab>
)}
{client!.serviceAccountsEnabled && (
<Tab
id="authorization"
eventKey="authorization"
data-testid="authorizationTab"
title={<TabTitleText>{t("authorization")}</TabTitleText>}
{...route("authorization")}
>
<Tabs
activeKey={authorizationSubTab}
onSelect={(_, key) => setAuthorizationSubTab(key as number)}
<RoutableTabs
mountOnEnter
unmountOnExit
defaultLocation={toAuthorizationTab({
realm,
clientId,
tab: "settings",
})}
>
<Tab
id="settings"
eventKey={40}
data-testid="authorizationSettings"
title={<TabTitleText>{t("settings")}</TabTitleText>}
{...authenticationRoute("settings")}
>
<AuthorizationSettings clientId={clientId} />
</Tab>
<Tab
id="resources"
eventKey={41}
data-testid="authorizationResources"
title={<TabTitleText>{t("resources")}</TabTitleText>}
{...authenticationRoute("resources")}
>
<AuthorizationResources clientId={clientId} />
</Tab>
<Tab
id="scopes"
eventKey={42}
data-testid="authorizationScopes"
title={<TabTitleText>{t("scopes")}</TabTitleText>}
{...authenticationRoute("scopes")}
>
<AuthorizationScopes clientId={clientId} />
</Tab>
<Tab
id="policies"
eventKey={43}
data-testid="authorizationPolicies"
title={<TabTitleText>{t("policies")}</TabTitleText>}
{...authenticationRoute("policies")}
>
<AuthorizationPolicies clientId={clientId} />
</Tab>
<Tab
id="permissions"
eventKey={44}
data-testid="authorizationPermissions"
title={<TabTitleText>{t("permissions")}</TabTitleText>}
{...authenticationRoute("permissions")}
>
<AuthorizationPermissions clientId={clientId} />
</Tab>
<Tab
id="Evaluate"
eventKey={44}
data-testid="authorizationEvaluate"
title={<TabTitleText>{t("evaluate")}</TabTitleText>}
{...authenticationRoute("evaluate")}
>
<AuthorizationEvaluate
clients={clients}
@ -543,26 +600,28 @@ export default function ClientDetails() {
reset={() => setupForm(client)}
/>
</Tab>
</Tabs>
</RoutableTabs>
</Tab>
)}
{client!.serviceAccountsEnabled && (
<Tab
id="serviceAccount"
eventKey="serviceAccount"
data-testid="serviceAccountTab"
title={<TabTitleText>{t("serviceAccount")}</TabTitleText>}
{...route("serviceAccount")}
>
<ServiceAccount client={client} />
</Tab>
)}
<Tab
id="advanced"
eventKey="advanced"
data-testid="advancedTab"
title={<TabTitleText>{t("advanced")}</TabTitleText>}
{...route("advanced")}
>
<AdvancedTab save={save} client={client} />
</Tab>
</KeycloakTabs>
</RoutableTabs>
</FormProvider>
</PageSection>
</>

View file

@ -27,9 +27,9 @@ import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"
import { ViewHeader } from "../../components/view-header/ViewHeader";
import { FormAccess } from "../../components/form-access/FormAccess";
import { useAlerts } from "../../components/alert/Alerts";
import { toClient } from "../routes/Client";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { ResourcesPolicySelect } from "./ResourcesPolicySelect";
import { toAuthorizationTab } from "../routes/AuthenticationTab";
const DECISION_STRATEGIES = ["UNANIMOUS", "AFFIRMATIVE", "CONSENSUS"] as const;
@ -140,7 +140,9 @@ export default function PermissionDetails() {
permissionId: permissionId,
});
addAlert(t("permissionDeletedSuccess"), AlertVariant.success);
history.push(toClient({ realm, clientId: id, tab: "authorization" }));
history.push(
toAuthorizationTab({ realm, clientId: id, tab: "permissions" })
);
} catch (error) {
addError("clients:permissionDeletedError", error);
}
@ -329,10 +331,10 @@ export default function PermissionDetails() {
component={(props) => (
<Link
{...props}
to={toClient({
to={toAuthorizationTab({
realm,
clientId: id,
tab: "authorization",
tab: "permissions",
})}
></Link>
)}

View file

@ -31,7 +31,7 @@ import type { MultiLine } from "../../components/multi-line-input/multi-line-con
import type { KeyValueType } from "../../components/attribute-form/attribute-convert";
import { convertFormValuesToObject, convertToFormValues } from "../../util";
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
import { toClient } from "../routes/Client";
import { toAuthorizationTab } from "../routes/AuthenticationTab";
import { ScopePicker } from "./ScopePicker";
import { AttributeInput } from "../../components/attribute-input/AttributeInput";
@ -142,7 +142,9 @@ export default function ResourceDetails() {
resourceId: resourceId!,
});
addAlert(t("resourceDeletedSuccess"), AlertVariant.success);
history.push(toClient({ realm, clientId: id, tab: "authorization" }));
history.push(
toAuthorizationTab({ realm, clientId: id, tab: "resources" })
);
} catch (error) {
addError("clients:resourceDeletedError", error);
}
@ -324,10 +326,10 @@ export default function ResourceDetails() {
component={(props) => (
<Link
{...props}
to={toClient({
to={toAuthorizationTab({
realm,
clientId: id,
tab: "authorization",
tab: "resources",
})}
></Link>
)}

View file

@ -19,7 +19,7 @@ import type { ScopeDetailsParams } from "../routes/Scope";
import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { ViewHeader } from "../../components/view-header/ViewHeader";
import { toClient } from "../routes/Client";
import { toAuthorizationTab } from "../routes/AuthenticationTab";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useAlerts } from "../../components/alert/Alerts";
import useToggle from "../../utils/useToggle";
@ -77,7 +77,9 @@ export default function ScopeDetails() {
iconUri: scope.iconUri,
}
);
history.push(toClient({ realm, clientId: id, tab: "authorization" }));
history.push(
toAuthorizationTab({ realm, clientId: id, tab: "scopes" })
);
}
addAlert(
t((scopeId ? "update" : "create") + "ScopeSuccess"),
@ -96,7 +98,9 @@ export default function ScopeDetails() {
toggleDialog={toggleDeleteDialog}
selectedScope={scope}
refresh={() =>
history.push(toClient({ realm, clientId: id, tab: "authorization" }))
history.push(
toAuthorizationTab({ realm, clientId: id, tab: "scopes" })
)
}
/>
<ViewHeader
@ -183,10 +187,10 @@ export default function ScopeDetails() {
component={(props) => (
<Link
{...props}
to={toClient({
to={toAuthorizationTab({
realm,
clientId: id,
tab: "authorization",
tab: "scopes",
})}
></Link>
)}

View file

@ -22,7 +22,7 @@ import { useConfirmDialog } from "../../../components/confirm-dialog/ConfirmDial
import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
import { FormAccess } from "../../../components/form-access/FormAccess";
import { useAlerts } from "../../../components/alert/Alerts";
import { toClient } from "../../routes/Client";
import { toAuthorizationTab } from "../../routes/AuthenticationTab";
import { Aggregate } from "./Aggregate";
import { Client } from "./Client";
import { User } from "./User";
@ -150,7 +150,9 @@ export default function PolicyDetails() {
policyId,
});
addAlert(t("policyDeletedSuccess"), AlertVariant.success);
history.push(toClient({ realm, clientId: id, tab: "authorization" }));
history.push(
toAuthorizationTab({ realm, clientId: id, tab: "policies" })
);
} catch (error) {
addError("clients:policyDeletedError", error);
}
@ -212,10 +214,10 @@ export default function PolicyDetails() {
component={(props) => (
<Link
{...props}
to={toClient({
to={toAuthorizationTab({
realm,
clientId: id,
tab: "authorization",
tab: "policies",
})}
/>
)}

View file

@ -5,6 +5,8 @@ import { ClientsRoute } 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 } from "./routes/Resource";
import { NewScopeRoute } from "./routes/NewScope";
@ -21,6 +23,8 @@ const routes: RouteDef[] = [
CreateInitialAccessTokenRoute,
ClientRoute,
MapperRoute,
ClientScopesRoute,
AuthorizationRoute,
NewResourceRoute,
ResourceDetailsRoute,
NewScopeRoute,

View file

@ -0,0 +1,30 @@
import type { LocationDescriptorObject } from "history";
import { lazy } from "react";
import { generatePath } from "react-router-dom";
import type { RouteDef } from "../../route-config";
export type AuthorizationTab =
| "settings"
| "resources"
| "scopes"
| "policies"
| "permissions"
| "evaluate";
export type AuthorizationParams = {
realm: string;
clientId: string;
tab: AuthorizationTab;
};
export const AuthorizationRoute: RouteDef = {
path: "/:realm/clients/:clientId/authorization/:tab",
component: lazy(() => import("../ClientDetails")),
breadcrumb: (t) => t("clients:clientSettings"),
access: "view-clients",
};
export const toAuthorizationTab = (
params: AuthorizationParams
): LocationDescriptorObject => ({
pathname: generatePath(AuthorizationRoute.path, params),
});

View file

@ -5,11 +5,14 @@ import type { RouteDef } from "../../route-config";
export type ClientTab =
| "settings"
| "keys"
| "credentials"
| "roles"
| "clientScopes"
| "advanced"
| "mappers"
| "authorization";
| "authorization"
| "serviceAccount";
export type ClientParams = {
realm: string;

View file

@ -0,0 +1,24 @@
import type { LocationDescriptorObject } from "history";
import { lazy } from "react";
import { generatePath } from "react-router-dom";
import type { RouteDef } from "../../route-config";
export type ClientScopesTab = "setup" | "evaluate";
export type ClientScopesParams = {
realm: string;
clientId: string;
tab: ClientScopesTab;
};
export const ClientScopesRoute: RouteDef = {
path: "/:realm/clients/:clientId/clientScopes/:tab",
component: lazy(() => import("../ClientDetails")),
breadcrumb: (t) => t("clients:clientSettings"),
access: "view-clients",
};
export const toClientScopesTab = (
params: ClientScopesParams
): LocationDescriptorObject => ({
pathname: generatePath(ClientScopesRoute.path, params),
});