Identity Providers(SAML): Adds data table for SAML mappers (#1058)

* data table for saml mappers

saml mapeprs tab

fix localized text

PR feedback from Jon

remove jsx fragment

remove comment

remove toolbar button

* try fixing cypress test

* remove optional chaining

* PR feedback from Jon

* more PR feedback from Jon

* rename variable and use find()

* useFetch() change as suggested by Jon

Co-authored-by: Jon Koops <jonkoops@gmail.com>

* rebase and use reset function

Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
Jenny 2021-09-08 10:33:19 -04:00 committed by GitHub
parent 5ca4aba375
commit 2923a4c16f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 82 additions and 13 deletions

View file

@ -75,7 +75,7 @@ export const IdentityProvidersSection = () => {
key={identityProvider.providerId} key={identityProvider.providerId}
to={toIdentityProviderTab({ to={toIdentityProviderTab({
realm, realm,
providerId: identityProvider.providerId!, providerId: identityProvider.providerId,
alias: identityProvider.alias!, alias: identityProvider.alias!,
})} })}
> >

View file

@ -18,6 +18,7 @@ import { useAdminClient } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext"; import { useRealm } from "../../context/realm-context/RealmContext";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import { GeneralSettings } from "./GeneralSettings"; import { GeneralSettings } from "./GeneralSettings";
import { toIdentityProviderTab } from "../routes/IdentityProviderTab";
export const IdentityProviderCrumb = ({ match, location }: BreadcrumbData) => { export const IdentityProviderCrumb = ({ match, location }: BreadcrumbData) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -64,7 +65,7 @@ export const AddIdentityProvider = () => {
alias: id, alias: id,
}); });
addAlert(t("createSuccess"), AlertVariant.success); addAlert(t("createSuccess"), AlertVariant.success);
history.push(`/${realm}/identity-providers/${id}/settings`); history.push(toIdentityProviderTab({ realm, providerId: id, alias: id }));
} catch (error) { } catch (error) {
addError("identity-providers:createError", error); addError("identity-providers:createError", error);
} }

View file

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React from "react";
import { useHistory, useParams } from "react-router-dom"; import { useHistory, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, FormProvider, useForm } from "react-hook-form"; import { Controller, FormProvider, useForm } from "react-hook-form";
@ -33,6 +33,9 @@ import { OIDCGeneralSettings } from "./OIDCGeneralSettings";
import { SamlGeneralSettings } from "./SamlGeneralSettings"; import { SamlGeneralSettings } from "./SamlGeneralSettings";
import { OIDCAuthentication } from "./OIDCAuthentication"; import { OIDCAuthentication } from "./OIDCAuthentication";
import { ReqAuthnConstraints } from "./ReqAuthnConstraintsSettings"; import { ReqAuthnConstraints } from "./ReqAuthnConstraintsSettings";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
import type IdentityProviderMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderMapperRepresentation";
type HeaderProps = { type HeaderProps = {
onChange: (value: boolean) => void; onChange: (value: boolean) => void;
@ -41,6 +44,13 @@ type HeaderProps = {
toggleDeleteDialog: () => void; toggleDeleteDialog: () => void;
}; };
type IdPWithMapperAttributes = IdentityProviderMapperRepresentation & {
name: string;
category?: string;
helpText?: string;
type: string;
};
const Header = ({ onChange, value, save, toggleDeleteDialog }: HeaderProps) => { const Header = ({ onChange, value, save, toggleDeleteDialog }: HeaderProps) => {
const { t } = useTranslation("identity-providers"); const { t } = useTranslation("identity-providers");
const { providerId, alias } = const { providerId, alias } =
@ -86,9 +96,8 @@ export const DetailSettings = () => {
const { providerId, alias } = const { providerId, alias } =
useParams<{ providerId: string; alias: string }>(); useParams<{ providerId: string; alias: string }>();
const [provider, setProvider] = useState<IdentityProviderRepresentation>();
const form = useForm<IdentityProviderRepresentation>(); const form = useForm<IdentityProviderRepresentation>();
const { handleSubmit, setValue, getValues, reset } = form; const { handleSubmit, getValues, reset } = form;
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
@ -97,11 +106,8 @@ export const DetailSettings = () => {
useFetch( useFetch(
() => adminClient.identityProviders.findOne({ alias: alias }), () => adminClient.identityProviders.findOne({ alias: alias }),
(provider) => { (fetchedProvider) => {
if (provider) { reset(fetchedProvider);
setProvider(provider);
Object.entries(provider).map((entry) => setValue(entry[0], entry[1]));
}
}, },
[] []
); );
@ -113,7 +119,6 @@ export const DetailSettings = () => {
{ alias }, { alias },
{ ...p, alias, providerId } { ...p, alias, providerId }
); );
setProvider(p);
addAlert(t("updateSuccess"), AlertVariant.success); addAlert(t("updateSuccess"), AlertVariant.success);
} catch (error) { } catch (error) {
addError("identity-providers:updateError", error); addError("identity-providers:updateError", error);
@ -141,6 +146,30 @@ export const DetailSettings = () => {
const isOIDC = providerId.includes("oidc"); const isOIDC = providerId.includes("oidc");
const isSAML = providerId.includes("saml"); const isSAML = providerId.includes("saml");
const loader = async () => {
const [loaderMappers, loaderMapperTypes] = await Promise.all([
adminClient.identityProviders.findMappers({ alias }),
adminClient.identityProviders.findMapperTypes({ alias }),
]);
const components = loaderMappers.map((loaderMapper) => {
const mapperType = Object.values(loaderMapperTypes).find(
(loaderMapperType) =>
loaderMapper.identityProviderMapper! === loaderMapperType.id!
);
const result: IdPWithMapperAttributes = {
...mapperType,
name: loaderMapper.name!,
type: mapperType?.name!,
};
return result;
});
return components;
};
if (isOIDC) { if (isOIDC) {
sections.splice(1, 0, t("oidcSettings")); sections.splice(1, 0, t("oidcSettings"));
} }
@ -222,7 +251,7 @@ export const DetailSettings = () => {
data-testid={"revert"} data-testid={"revert"}
variant="link" variant="link"
onClick={() => { onClick={() => {
reset(provider); reset();
}} }}
> >
{t("common:revert")} {t("common:revert")}
@ -231,6 +260,39 @@ export const DetailSettings = () => {
</FormAccess> </FormAccess>
</ScrollForm> </ScrollForm>
</Tab> </Tab>
<Tab
id="mappers"
eventKey="mappers"
title={<TabTitleText>{t("common:mappers")}</TabTitleText>}
>
<KeycloakDataTable
emptyState={
<ListEmptyState
message={t("identity-providers:noMappers")}
instructions={t("identity-providers:noMappersInstructions")}
primaryActionText={t("identity-providers:addMapper")}
/>
}
loader={loader}
isPaginated
ariaLabelKey="identity-providers:mappersList"
searchPlaceholderKey="identity-providers:searchForMapper"
columns={[
{
name: "name",
displayKey: "common:name",
},
{
name: "category",
displayKey: "common:category",
},
{
name: "type",
displayKey: "common:type",
},
]}
/>
</Tab>
</KeycloakTabs> </KeycloakTabs>
</FormProvider> </FormProvider>
</PageSection> </PageSection>

View file

@ -5,6 +5,12 @@ export default {
searchForProvider: "Search for provider", searchForProvider: "Search for provider",
provider: "Provider details", provider: "Provider details",
addProvider: "Add provider", addProvider: "Add provider",
addMapper: "Add mapper",
mappersList: "Mappers list",
noMappers: "No Mappers",
noMappersInstructions:
"There are currently no mappers for this identity provider.",
searchForMapper: "Search for mapper",
addKeycloakOpenIdProvider: "Add Keycloak OpenID Connect provider", addKeycloakOpenIdProvider: "Add Keycloak OpenID Connect provider",
addOpenIdProvider: "Add OpenID Connect provider", addOpenIdProvider: "Add OpenID Connect provider",
addSamlProvider: "Add SAML provider", addSamlProvider: "Add SAML provider",

View file

@ -13,7 +13,7 @@ export type IdentityProviderTabParams = {
}; };
export const IdentityProviderTabRoute: RouteDef = { export const IdentityProviderTabRoute: RouteDef = {
path: "/:realm/identity-providers/:providerId/:alias/:tab?", path: "/:realm/identity-providers/:providerId?/:alias/:tab?",
component: DetailSettings, component: DetailSettings,
access: "manage-identity-providers", access: "manage-identity-providers",
}; };