From 1860ea5b58c7abe09403d9bb245451c9b916cbf2 Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Wed, 21 Jul 2021 17:08:40 +0200 Subject: [PATCH] Use new routing conventions for client routes (#888) --- .../pages/admin_console/ListingPage.ts | 4 +- src/clients/AdvancedTab.tsx | 46 +++++++++---------- src/clients/ClientDetails.tsx | 43 ++++++++--------- src/clients/ClientsSection.tsx | 43 ++++++++--------- src/clients/add/NewClientForm.tsx | 27 ++++++----- src/clients/import/ImportForm.tsx | 39 ++++++++-------- .../CreateInitialAccessToken.tsx | 11 +++-- .../initial-access/InitialAccessTokenList.tsx | 24 +++++----- src/clients/routes.ts | 16 +++++++ src/clients/routes/AddClient.ts | 19 ++++++++ src/clients/routes/Client.ts | 23 ++++++++++ src/clients/routes/Clients.ts | 22 +++++++++ .../routes/CreateInitialAccessToken.ts | 19 ++++++++ src/clients/routes/ImportClient.ts | 19 ++++++++ src/route-config.ts | 37 +-------------- 15 files changed, 243 insertions(+), 149 deletions(-) create mode 100644 src/clients/routes.ts create mode 100644 src/clients/routes/AddClient.ts create mode 100644 src/clients/routes/Client.ts create mode 100644 src/clients/routes/Clients.ts create mode 100644 src/clients/routes/CreateInitialAccessToken.ts create mode 100644 src/clients/routes/ImportClient.ts diff --git a/cypress/support/pages/admin_console/ListingPage.ts b/cypress/support/pages/admin_console/ListingPage.ts index c8bc0b1791..1dcb454103 100644 --- a/cypress/support/pages/admin_console/ListingPage.ts +++ b/cypress/support/pages/admin_console/ListingPage.ts @@ -7,9 +7,9 @@ export default class ListingPage { private searchBtn = ".pf-c-page__main .pf-c-toolbar__content-section button.pf-m-control:visible"; private createBtn = - ".pf-c-page__main .pf-c-toolbar__content-section button.pf-m-primary:visible"; + ".pf-c-page__main .pf-c-toolbar__content-section .pf-m-primary:visible"; private importBtn = - ".pf-c-page__main .pf-c-toolbar__content-section button.pf-m-link"; + ".pf-c-page__main .pf-c-toolbar__content-section .pf-m-link"; goToCreateItem() { cy.get(this.createBtn).click(); diff --git a/src/clients/AdvancedTab.tsx b/src/clients/AdvancedTab.tsx index 9e47fadc9e..cdcfc637bf 100644 --- a/src/clients/AdvancedTab.tsx +++ b/src/clients/AdvancedTab.tsx @@ -1,8 +1,3 @@ -import React, { useEffect, useState } from "react"; -import { Link } from "react-router-dom"; -import { Trans, useTranslation } from "react-i18next"; -import { Controller, useFormContext } from "react-hook-form"; -import moment from "moment"; import { ActionGroup, AlertVariant, @@ -17,27 +12,32 @@ import { TextInput, ToolbarItem, } from "@patternfly/react-core"; - -import type GlobalRequestResult from "keycloak-admin/lib/defs/globalRequestResult"; import type ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; -import { convertToFormValues, toUpperCase } from "../util"; -import { FormAccess } from "../components/form-access/FormAccess"; -import { ScrollForm } from "../components/scroll-form/ScrollForm"; -import { HelpItem } from "../components/help-enabler/HelpItem"; -import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; -import { FineGrainOpenIdConnect } from "./advanced/FineGrainOpenIdConnect"; -import { OpenIdConnectCompatibilityModes } from "./advanced/OpenIdConnectCompatibilityModes"; -import { AdvancedSettings } from "./advanced/AdvancedSettings"; -import { TimeSelector } from "../components/time-selector/TimeSelector"; -import { useAdminClient } from "../context/auth/AdminClient"; +import type GlobalRequestResult from "keycloak-admin/lib/defs/globalRequestResult"; +import moment from "moment"; +import React, { useEffect, useState } from "react"; +import { Controller, useFormContext } from "react-hook-form"; +import { Trans, useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; import { useAlerts } from "../components/alert/Alerts"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; -import { AddHostDialog } from "./advanced/AddHostDialog"; -import { FineGrainSamlEndpointConfig } from "./advanced/FineGrainSamlEndpointConfig"; -import { AuthenticationOverrides } from "./advanced/AuthenticationOverrides"; -import { useRealm } from "../context/realm-context/RealmContext"; -import type { SaveOptions } from "./ClientDetails"; +import { FormAccess } from "../components/form-access/FormAccess"; +import { HelpItem } from "../components/help-enabler/HelpItem"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; +import { ScrollForm } from "../components/scroll-form/ScrollForm"; +import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; +import { TimeSelector } from "../components/time-selector/TimeSelector"; +import { useAdminClient } from "../context/auth/AdminClient"; +import { useRealm } from "../context/realm-context/RealmContext"; +import { convertToFormValues, toUpperCase } from "../util"; +import { AddHostDialog } from "./advanced/AddHostDialog"; +import { AdvancedSettings } from "./advanced/AdvancedSettings"; +import { AuthenticationOverrides } from "./advanced/AuthenticationOverrides"; +import { FineGrainOpenIdConnect } from "./advanced/FineGrainOpenIdConnect"; +import { FineGrainSamlEndpointConfig } from "./advanced/FineGrainSamlEndpointConfig"; +import { OpenIdConnectCompatibilityModes } from "./advanced/OpenIdConnectCompatibilityModes"; +import type { SaveOptions } from "./ClientDetails"; +import { toClient } from "./routes/Client"; type AdvancedProps = { save: (options?: SaveOptions) => void; @@ -184,7 +184,7 @@ export const AdvancedTab = ({ In order to successfully push setup url on - + {t("settings")} tab diff --git a/src/clients/ClientDetails.tsx b/src/clients/ClientDetails.tsx index de86268853..e5ae7edad0 100644 --- a/src/clients/ClientDetails.tsx +++ b/src/clients/ClientDetails.tsx @@ -1,4 +1,3 @@ -import React, { useState } from "react"; import { Alert, AlertVariant, @@ -11,40 +10,42 @@ import { Tabs, TabTitleText, } from "@patternfly/react-core"; -import { useHistory, useParams } from "react-router-dom"; -import { useTranslation } from "react-i18next"; -import { Controller, FormProvider, useForm, useWatch } from "react-hook-form"; import type ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; import _ from "lodash"; - -import { ClientSettings } from "./ClientSettings"; +import React, { useState } from "react"; +import { Controller, FormProvider, useForm, useWatch } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { useHistory, useParams } from "react-router-dom"; import { useAlerts } from "../components/alert/Alerts"; import { ConfirmDialogModal, useConfirmDialog, } from "../components/confirm-dialog/ConfirmDialog"; import { DownloadDialog } from "../components/download-dialog/DownloadDialog"; -import { ViewHeader } from "../components/view-header/ViewHeader"; -import { useAdminClient, useFetch } from "../context/auth/AdminClient"; -import { Credentials } from "./credentials/Credentials"; -import { - convertFormValuesToObject, - convertToFormValues, - exportClient, -} from "../util"; +import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; import { convertToMultiline, MultiLine, toValue, } from "../components/multi-line-input/MultiLineInput"; +import { ViewHeader } from "../components/view-header/ViewHeader"; +import { useAdminClient, useFetch } from "../context/auth/AdminClient"; +import { useRealm } from "../context/realm-context/RealmContext"; +import { RolesList } from "../realm-roles/RolesList"; +import { + convertFormValuesToObject, + convertToFormValues, + exportClient, +} from "../util"; +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 { toClients } from "./routes/Clients"; import { ClientScopes } from "./scopes/ClientScopes"; import { EvaluateScopes } from "./scopes/EvaluateScopes"; -import { RolesList } from "../realm-roles/RolesList"; import { ServiceAccount } from "./service-account/ServiceAccount"; -import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; -import { AdvancedTab } from "./AdvancedTab"; -import { useRealm } from "../context/realm-context/RealmContext"; -import { Keys } from "./keys/Keys"; type ClientDetailHeaderProps = { onChange: (value: boolean) => void; @@ -137,7 +138,7 @@ export const ClientDetails = () => { const [activeTab2, setActiveTab2] = useState(30); const form = useForm(); - const { clientId } = useParams<{ clientId: string }>(); + const { clientId } = useParams(); const clientAuthenticatorType = useWatch({ control: form.control, @@ -161,7 +162,7 @@ export const ClientDetails = () => { try { await adminClient.clients.del({ id: clientId }); addAlert(t("clientDeletedSuccess"), AlertVariant.success); - history.push(`/${realm}/clients`); + history.push(toClients({ realm })); } catch (error) { addAlert(`${t("clientDeleteError")} ${error}`, AlertVariant.danger); } diff --git a/src/clients/ClientsSection.tsx b/src/clients/ClientsSection.tsx index 413d2f8d52..9ad27097c8 100644 --- a/src/clients/ClientsSection.tsx +++ b/src/clients/ClientsSection.tsx @@ -1,34 +1,35 @@ -import React, { useState } from "react"; -import { Link, useHistory } from "react-router-dom"; -import { useTranslation } from "react-i18next"; import { AlertVariant, Badge, Button, ButtonVariant, PageSection, - ToolbarItem, Tab, TabTitleText, + ToolbarItem, } from "@patternfly/react-core"; - +import { cellWidth, TableText } from "@patternfly/react-table"; import type ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; -import { emptyFormatter, exportClient, getBaseUrl } from "../util"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { useAlerts } from "../components/alert/Alerts"; +import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { formattedLinkTableCell } from "../components/external-link/FormattedLink"; +import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; +import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useAdminClient } from "../context/auth/AdminClient"; import { useRealm } from "../context/realm-context/RealmContext"; -import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; -import { useAlerts } from "../components/alert/Alerts"; -import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; -import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; +import { emptyFormatter, exportClient, getBaseUrl } from "../util"; import { InitialAccessTokenList } from "./initial-access/InitialAccessTokenList"; -import { cellWidth, TableText } from "@patternfly/react-table"; +import { toAddClient } from "./routes/AddClient"; +import { toClient } from "./routes/Client"; +import { toImportClient } from "./routes/ImportClient"; export const ClientsSection = () => { const { t } = useTranslation("clients"); const { addAlert } = useAlerts(); - const history = useHistory(); const adminClient = useAdminClient(); const { realm } = useRealm(); @@ -70,7 +71,10 @@ export const ClientsSection = () => { const ClientDetailLink = (client: ClientRepresentation) => ( <> - + {client.clientId} {!client.enabled && ( @@ -114,19 +118,16 @@ export const ClientsSection = () => { toolbarItem={ <> - - diff --git a/src/clients/initial-access/CreateInitialAccessToken.tsx b/src/clients/initial-access/CreateInitialAccessToken.tsx index 330bf84d58..cba9b89bd6 100644 --- a/src/clients/initial-access/CreateInitialAccessToken.tsx +++ b/src/clients/initial-access/CreateInitialAccessToken.tsx @@ -15,11 +15,12 @@ import { FormAccess } from "../../components/form-access/FormAccess"; import { ViewHeader } from "../../components/view-header/ViewHeader"; import { HelpItem } from "../../components/help-enabler/HelpItem"; import { TimeSelector } from "../../components/time-selector/TimeSelector"; -import { useHistory } from "react-router-dom"; +import { Link, useHistory } from "react-router-dom"; import { useRealm } from "../../context/realm-context/RealmContext"; import { useAdminClient } from "../../context/auth/AdminClient"; import { useAlerts } from "../../components/alert/Alerts"; import { AccessTokenDialog } from "./AccessTokenDialog"; +import { toClients } from "../routes/Clients"; export const CreateInitialAccessToken = () => { const { t } = useTranslation("clients"); @@ -52,7 +53,7 @@ export const CreateInitialAccessToken = () => { toggleDialog={() => { setToken(""); addAlert(t("tokenSaveSuccess"), AlertVariant.success); - history.push(`/${realm}/clients/initialAccessToken`); + history.push(toClients({ realm, tab: "initialAccessToken" })); }} /> )} @@ -129,9 +130,9 @@ export const CreateInitialAccessToken = () => { diff --git a/src/clients/initial-access/InitialAccessTokenList.tsx b/src/clients/initial-access/InitialAccessTokenList.tsx index c7ca2f1124..c0b4db74b4 100644 --- a/src/clients/initial-access/InitialAccessTokenList.tsx +++ b/src/clients/initial-access/InitialAccessTokenList.tsx @@ -1,17 +1,17 @@ -import React, { useState } from "react"; -import { useHistory, useRouteMatch } from "react-router-dom"; -import moment from "moment"; -import { useTranslation } from "react-i18next"; import { AlertVariant, Button, ButtonVariant } from "@patternfly/react-core"; import { wrappable } from "@patternfly/react-table"; - import type ClientInitialAccessPresentation from "keycloak-admin/lib/defs/clientInitialAccessPresentation"; +import moment from "moment"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link, useHistory } from "react-router-dom"; +import { useAlerts } from "../../components/alert/Alerts"; +import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; +import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState"; import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable"; import { useAdminClient } from "../../context/auth/AdminClient"; import { useRealm } from "../../context/realm-context/RealmContext"; -import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState"; -import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; -import { useAlerts } from "../../components/alert/Alerts"; +import { toCreateInitialAccessToken } from "../routes/CreateInitialAccessToken"; export const InitialAccessTokenList = () => { const { t } = useTranslation("clients"); @@ -21,7 +21,6 @@ export const InitialAccessTokenList = () => { const { realm } = useRealm(); const history = useHistory(); - const { url } = useRouteMatch(); const [token, setToken] = useState(); @@ -57,7 +56,8 @@ export const InitialAccessTokenList = () => { loader={loader} toolbarItem={ <> - @@ -104,7 +104,9 @@ export const InitialAccessTokenList = () => { message={t("noTokens")} instructions={t("noTokensInstructions")} primaryActionText={t("common:create")} - onPrimaryAction={() => history.push(`${url}/create`)} + onPrimaryAction={() => + history.push(toCreateInitialAccessToken({ realm })) + } /> } /> diff --git a/src/clients/routes.ts b/src/clients/routes.ts new file mode 100644 index 0000000000..ede01be916 --- /dev/null +++ b/src/clients/routes.ts @@ -0,0 +1,16 @@ +import type { RouteDef } from "../route-config"; +import { AddClientRoute } from "./routes/AddClient"; +import { ClientRoute } from "./routes/Client"; +import { ClientsRoute } from "./routes/Clients"; +import { CreateInitialAccessTokenRoute } from "./routes/CreateInitialAccessToken"; +import { ImportClientRoute } from "./routes/ImportClient"; + +const routes: RouteDef[] = [ + AddClientRoute, + ImportClientRoute, + ClientsRoute, + CreateInitialAccessTokenRoute, + ClientRoute, +]; + +export default routes; diff --git a/src/clients/routes/AddClient.ts b/src/clients/routes/AddClient.ts new file mode 100644 index 0000000000..8050a9b023 --- /dev/null +++ b/src/clients/routes/AddClient.ts @@ -0,0 +1,19 @@ +import type { LocationDescriptorObject } from "history"; +import { generatePath } from "react-router-dom"; +import type { RouteDef } from "../../route-config"; +import { NewClientForm } from "../add/NewClientForm"; + +export type AddClientParams = { realm: string }; + +export const AddClientRoute: RouteDef = { + path: "/:realm/clients/add-client", + component: NewClientForm, + breadcrumb: (t) => t("clients:createClient"), + access: "manage-clients", +}; + +export const toAddClient = ( + params: AddClientParams +): LocationDescriptorObject => ({ + pathname: generatePath(AddClientRoute.path, params), +}); diff --git a/src/clients/routes/Client.ts b/src/clients/routes/Client.ts new file mode 100644 index 0000000000..96570aeb03 --- /dev/null +++ b/src/clients/routes/Client.ts @@ -0,0 +1,23 @@ +import type { LocationDescriptorObject } from "history"; +import { generatePath } from "react-router-dom"; +import type { RouteDef } from "../../route-config"; +import { ClientDetails } from "../ClientDetails"; + +export type ClientTab = "settings" | "roles" | "clientScopes" | "advanced"; + +export type ClientParams = { + realm: string; + clientId: string; + tab: ClientTab; +}; + +export const ClientRoute: RouteDef = { + path: "/:realm/clients/:clientId/:tab", + component: ClientDetails, + breadcrumb: (t) => t("clients:clientSettings"), + access: "view-clients", +}; + +export const toClient = (params: ClientParams): LocationDescriptorObject => ({ + pathname: generatePath(ClientRoute.path, params), +}); diff --git a/src/clients/routes/Clients.ts b/src/clients/routes/Clients.ts new file mode 100644 index 0000000000..e96bd5602d --- /dev/null +++ b/src/clients/routes/Clients.ts @@ -0,0 +1,22 @@ +import type { LocationDescriptorObject } from "history"; +import { generatePath } from "react-router-dom"; +import type { RouteDef } from "../../route-config"; +import { ClientsSection } from "../ClientsSection"; + +export type ClientsTab = "list" | "initialAccessToken"; + +export type ClientsParams = { + realm: string; + tab?: ClientsTab; +}; + +export const ClientsRoute: RouteDef = { + path: "/:realm/clients/:tab?", + component: ClientsSection, + breadcrumb: (t) => t("clients:clientList"), + access: "query-clients", +}; + +export const toClients = (params: ClientsParams): LocationDescriptorObject => ({ + pathname: generatePath(ClientsRoute.path, params), +}); diff --git a/src/clients/routes/CreateInitialAccessToken.ts b/src/clients/routes/CreateInitialAccessToken.ts new file mode 100644 index 0000000000..7255034855 --- /dev/null +++ b/src/clients/routes/CreateInitialAccessToken.ts @@ -0,0 +1,19 @@ +import type { LocationDescriptorObject } from "history"; +import { generatePath } from "react-router-dom"; +import type { RouteDef } from "../../route-config"; +import { CreateInitialAccessToken } from "../initial-access/CreateInitialAccessToken"; + +export type CreateInitialAccessTokenParams = { realm: string }; + +export const CreateInitialAccessTokenRoute: RouteDef = { + path: "/:realm/clients/initialAccessToken/create", + component: CreateInitialAccessToken, + breadcrumb: (t) => t("clients:createToken"), + access: "manage-clients", +}; + +export const toCreateInitialAccessToken = ( + params: CreateInitialAccessTokenParams +): LocationDescriptorObject => ({ + pathname: generatePath(CreateInitialAccessTokenRoute.path, params), +}); diff --git a/src/clients/routes/ImportClient.ts b/src/clients/routes/ImportClient.ts new file mode 100644 index 0000000000..46ead9eae4 --- /dev/null +++ b/src/clients/routes/ImportClient.ts @@ -0,0 +1,19 @@ +import type { LocationDescriptorObject } from "history"; +import { generatePath } from "react-router-dom"; +import type { RouteDef } from "../../route-config"; +import { ImportForm } from "../import/ImportForm"; + +export type ImportClientParams = { realm: string }; + +export const ImportClientRoute: RouteDef = { + path: "/:realm/clients/import-client", + component: ImportForm, + breadcrumb: (t) => t("clients:importClient"), + access: "manage-clients", +}; + +export const toImportClient = ( + params: ImportClientParams +): LocationDescriptorObject => ({ + pathname: generatePath(ImportClientRoute.path, params), +}); diff --git a/src/route-config.ts b/src/route-config.ts index 72cd30ac08..67447b69f2 100644 --- a/src/route-config.ts +++ b/src/route-config.ts @@ -7,11 +7,7 @@ import { RoleMappingForm } from "./client-scopes/add/RoleMappingForm"; import { ClientScopesSection } from "./client-scopes/ClientScopesSection"; import { MappingDetails } from "./client-scopes/details/MappingDetails"; import { ClientScopeForm } from "./client-scopes/form/ClientScopeForm"; -import { NewClientForm } from "./clients/add/NewClientForm"; -import { ClientDetails } from "./clients/ClientDetails"; -import { ClientsSection } from "./clients/ClientsSection"; -import { ImportForm } from "./clients/import/ImportForm"; -import { CreateInitialAccessToken } from "./clients/initial-access/CreateInitialAccessToken"; +import clientRoutes from "./clients/routes"; import { DashboardSection } from "./dashboard/Dashboard"; import { EventsSection } from "./events/EventsSection"; import { GroupsSection } from "./groups/GroupsSection"; @@ -55,36 +51,13 @@ export type RouteDef = { }; export const routes: RouteDef[] = [ + ...clientRoutes, { path: "/:realm/add-realm", component: NewRealmForm, breadcrumb: (t) => t("realm:createRealm"), access: "manage-realm", }, - { - path: "/:realm/clients/add-client", - component: NewClientForm, - breadcrumb: (t) => t("clients:createClient"), - access: "manage-clients", - }, - { - path: "/:realm/clients/import-client", - component: ImportForm, - breadcrumb: (t) => t("clients:importClient"), - access: "manage-clients", - }, - { - path: "/:realm/clients/:tab?", - component: ClientsSection, - breadcrumb: (t) => t("clients:clientList"), - access: "query-clients", - }, - { - path: "/:realm/clients/initialAccessToken/create", - component: CreateInitialAccessToken, - breadcrumb: (t) => t("clients:createToken"), - access: "manage-clients", - }, { path: "/:realm/clients/:clientId/roles/add-role", component: RealmRoleTabs, @@ -97,12 +70,6 @@ export const routes: RouteDef[] = [ breadcrumb: (t) => t("roles:roleDetails"), access: "view-realm", }, - { - path: "/:realm/clients/:clientId/:tab", - component: ClientDetails, - breadcrumb: (t) => t("clients:clientSettings"), - access: "view-clients", - }, { path: "/:realm/client-scopes/new", component: ClientScopeForm,