added by configuration dialog into the flow (#221)

* updated the detail screen to support new mapping

* fix merge error

* added by configuration dialog
This commit is contained in:
Erik Jan de Wit 2020-11-17 22:28:34 +01:00 committed by GitHub
parent 8e33519df1
commit ba761c0526
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 138 additions and 65 deletions

View file

@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link, useHistory } from "react-router-dom";
import { import {
AlertVariant, AlertVariant,
ButtonVariant, ButtonVariant,
@ -43,6 +43,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
const { t } = useTranslation("client-scopes"); const { t } = useTranslation("client-scopes");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const history = useHistory();
const [filteredData, setFilteredData] = useState< const [filteredData, setFilteredData] = useState<
{ mapper: ProtocolMapperRepresentation; cells: Row }[] { mapper: ProtocolMapperRepresentation; cells: Row }[]
@ -53,12 +54,24 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
clientScope.protocol! clientScope.protocol!
]; ];
const [builtInDialogOpen, setBuiltInDialogOpen] = useState(false); const [addMapperDialogOpen, setAddMapperDialogOpen] = useState(false);
const toggleBuiltInMapperDialog = () => const [filter, setFilter] = useState(clientScope.protocolMappers);
setBuiltInDialogOpen(!builtInDialogOpen); const toggleAddMapperDialog = (buildIn: boolean) => {
if (buildIn) {
setFilter(mapperList);
} else {
setFilter(undefined);
}
setAddMapperDialogOpen(!addMapperDialogOpen);
};
const addMappers = async ( const addMappers = async (
mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[] mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[]
) => { ): Promise<void> => {
if (filter === undefined) {
const mapper = mappers as ProtocolMapperTypeRepresentation;
history.push(`/client-scopes/${clientScope.id}/${mapper.id}`);
} else {
try { try {
await adminClient.clientScopes.addMultipleProtocolMappers( await adminClient.clientScopes.addMultipleProtocolMappers(
{ id: clientScope.id! }, { id: clientScope.id! },
@ -69,6 +82,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
} catch (error) { } catch (error) {
addAlert(t("mappingCreatedError", { error }), AlertVariant.danger); addAlert(t("mappingCreatedError", { error }), AlertVariant.danger);
} }
}
}; };
if (!mapperList) { if (!mapperList) {
@ -76,20 +90,20 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
<> <>
<AddMapperDialog <AddMapperDialog
protocol={clientScope.protocol!} protocol={clientScope.protocol!}
filter={mapperList || []} filter={filter}
onConfirm={addMappers} onConfirm={addMappers}
open={builtInDialogOpen} open={addMapperDialogOpen}
toggleDialog={toggleBuiltInMapperDialog} toggleDialog={() => setAddMapperDialogOpen(!addMapperDialogOpen)}
/> />
<ListEmptyState <ListEmptyState
message={t("emptyMappers")} message={t("emptyMappers")}
instructions={t("emptyMappersInstructions")} instructions={t("emptyMappersInstructions")}
primaryActionText={t("emptyPrimaryAction")} primaryActionText={t("emptyPrimaryAction")}
onPrimaryAction={toggleBuiltInMapperDialog} onPrimaryAction={() => toggleAddMapperDialog(true)}
secondaryActions={[ secondaryActions={[
{ {
text: t("emptySecondaryAction"), text: t("emptySecondaryAction"),
onClick: () => {}, onClick: () => toggleAddMapperDialog(false),
type: ButtonVariant.secondary, type: ButtonVariant.secondary,
}, },
]} ]}
@ -149,10 +163,16 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
} }
isOpen={mapperAction} isOpen={mapperAction}
dropdownItems={[ dropdownItems={[
<DropdownItem key="predefined" onClick={toggleBuiltInMapperDialog}> <DropdownItem
key="predefined"
onClick={() => toggleAddMapperDialog(true)}
>
{t("fromPredefinedMapper")} {t("fromPredefinedMapper")}
</DropdownItem>, </DropdownItem>,
<DropdownItem key="byConfiguration"> <DropdownItem
key="byConfiguration"
onClick={() => toggleAddMapperDialog(false)}
>
{t("byConfiguration")} {t("byConfiguration")}
</DropdownItem>, </DropdownItem>,
]} ]}
@ -161,10 +181,10 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
> >
<AddMapperDialog <AddMapperDialog
protocol={clientScope.protocol!} protocol={clientScope.protocol!}
filter={mapperList || []} filter={filter}
onConfirm={addMappers} onConfirm={addMappers}
open={builtInDialogOpen} open={addMapperDialogOpen}
toggleDialog={toggleBuiltInMapperDialog} toggleDialog={() => setAddMapperDialogOpen(!addMapperDialogOpen)}
/> />
<Table <Table
variant={TableVariant.compact} variant={TableVariant.compact}

View file

@ -18,6 +18,7 @@ import {
SelectVariant, SelectVariant,
Switch, Switch,
TextInput, TextInput,
ValidatedOptions,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/configPropertyRepresentation"; import { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/configPropertyRepresentation";
@ -31,23 +32,30 @@ import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { convertFormValuesToObject, convertToFormValues } from "../../util"; import { convertFormValuesToObject, convertToFormValues } from "../../util";
type Params = {
scopeId: string;
id: string;
};
export const MappingDetails = () => { export const MappingDetails = () => {
const { t } = useTranslation("client-scopes"); const { t } = useTranslation("client-scopes");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const { scopeId, id } = useParams<{ scopeId: string; id: string }>(); const { scopeId, id } = useParams<Params>();
const { register, setValue, control, handleSubmit } = useForm(); const { register, errors, setValue, control, handleSubmit } = useForm();
const [mapping, setMapping] = useState<ProtocolMapperRepresentation>(); const [mapping, setMapping] = useState<ProtocolMapperRepresentation>();
const [typeOpen, setTypeOpen] = useState(false); const [typeOpen, setTypeOpen] = useState(false);
const [configProperties, setConfigProperties] = useState< const [configProperties, setConfigProperties] = useState<
ConfigPropertyRepresentation[] ConfigPropertyRepresentation[]
>(); >();
const serverInfo = useServerInfo();
const history = useHistory(); const history = useHistory();
const serverInfo = useServerInfo();
const isGuid = /^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/;
useEffect(() => { useEffect(() => {
if (id.match(isGuid)) {
(async () => { (async () => {
const data = await adminClient.clientScopes.findProtocolMapper({ const data = await adminClient.clientScopes.findProtocolMapper({
id: scopeId, id: scopeId,
@ -55,19 +63,23 @@ export const MappingDetails = () => {
}); });
if (data) { if (data) {
Object.entries(data).map((entry) => { Object.entries(data).map((entry) => {
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", setValue); convertToFormValues(entry[1], "config", setValue);
}
setValue(entry[0], entry[1]);
}); });
} }
setMapping(data);
const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!]; const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!];
const properties = mapperTypes.find( const properties = mapperTypes.find(
(type) => type.id === data.protocolMapper (type) => type.id === data!.protocolMapper
)?.properties!; )?.properties!;
setConfigProperties(properties); setConfigProperties(properties);
setMapping(data);
})(); })();
} else {
(async () => {
const scope = await adminClient.clientScopes.findOne({ id: scopeId });
setMapping({ protocol: scope.protocol, protocolMapper: id });
})();
}
}, []); }, []);
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
@ -91,15 +103,20 @@ export const MappingDetails = () => {
const save = async (formMapping: ProtocolMapperRepresentation) => { const save = async (formMapping: ProtocolMapperRepresentation) => {
const config = convertFormValuesToObject(formMapping.config); const config = convertFormValuesToObject(formMapping.config);
const map = { ...mapping, config }; const map = { ...mapping, ...formMapping, config };
const key = id.match(isGuid) ? "Updated" : "Created";
try { try {
if (id.match(isGuid)) {
await adminClient.clientScopes.updateProtocolMapper( await adminClient.clientScopes.updateProtocolMapper(
{ id: scopeId, mapperId: id }, { id: scopeId, mapperId: id },
map map
); );
addAlert(t("mappingUpdatedSuccess"), AlertVariant.success); } else {
await adminClient.clientScopes.addProtocolMapper({ id: scopeId }, map);
}
addAlert(t(`mapping${key}Success`), AlertVariant.success);
} catch (error) { } catch (error) {
addAlert(t("mappingUpdatedError", { error }), AlertVariant.danger); addAlert(t(`mapping${key}Error`, { error }), AlertVariant.danger);
} }
}; };
@ -107,10 +124,12 @@ export const MappingDetails = () => {
<> <>
<DeleteConfirm /> <DeleteConfirm />
<ViewHeader <ViewHeader
titleKey={mapping ? mapping.name! : ""} titleKey={mapping ? mapping.name! : t("addMapper")}
subKey={id} subKey={id.match(isGuid) ? id : ""}
badge={mapping?.protocol} badge={mapping?.protocol}
dropdownItems={[ dropdownItems={
id.match(isGuid)
? [
<DropdownItem <DropdownItem
key="delete" key="delete"
value="delete" value="delete"
@ -118,10 +137,42 @@ export const MappingDetails = () => {
> >
{t("common:delete")} {t("common:delete")}
</DropdownItem>, </DropdownItem>,
]} ]
: undefined
}
/> />
<PageSection variant="light"> <PageSection variant="light">
<Form isHorizontal onSubmit={handleSubmit(save)}> <Form isHorizontal onSubmit={handleSubmit(save)}>
{!id.match(isGuid) && (
<FormGroup
label={t("name")}
labelIcon={
<HelpItem
helpText="client-scopes-help:mapperName"
forLabel={t("name")}
forID="name"
/>
}
fieldId="name"
isRequired
validated={
errors.name ? ValidatedOptions.error : ValidatedOptions.default
}
helperTextInvalid={t("common:required")}
>
<TextInput
ref={register({ required: true })}
type="text"
id="name"
name="name"
validated={
errors.name
? ValidatedOptions.error
: ValidatedOptions.default
}
/>
</FormGroup>
)}
<FormGroup <FormGroup
label={t("realmRolePrefix")} label={t("realmRolePrefix")}
labelIcon={ labelIcon={

View file

@ -19,6 +19,8 @@
"deleteMappingConfirm": "Are you sure you want to delete this mapping?", "deleteMappingConfirm": "Are you sure you want to delete this mapping?",
"mappingUpdatedSuccess": "Mapping successfully updated", "mappingUpdatedSuccess": "Mapping successfully updated",
"mappingUpdatedError": "Could not update mapping: '{{error}}'", "mappingUpdatedError": "Could not update mapping: '{{error}}'",
"mappingCreatedSuccess": "Mapping successfully created",
"mappingCreatedError": "Could not create mapping: '{{error}}'",
"realmRolePrefix": "Realm role prefix", "realmRolePrefix": "Realm role prefix",
"multiValued": "Multivalued", "multiValued": "Multivalued",
"tokenClaimName": "Token claim name", "tokenClaimName": "Token claim name",

View file

@ -8,7 +8,7 @@ import { routes } from "../../route-config";
export const PageBreadCrumbs = () => { export const PageBreadCrumbs = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const crumbs = useBreadcrumbs(routes(t)).slice(1); const crumbs = useBreadcrumbs(routes(t), { excludePaths: ["/"] });
return ( return (
<> <>
{crumbs.length > 1 && ( {crumbs.length > 1 && (