initial version of the mapping list (#162)

* initial version of the mapping list

* added story to show mapper list

* fixed format

* changed to use intermediate type
This commit is contained in:
Erik Jan de Wit 2020-10-12 19:59:15 +02:00 committed by GitHub
parent b49d07e18c
commit 4fa325f45c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 7515 additions and 1 deletions

View file

@ -0,0 +1,58 @@
{
"id": "d695a07e-f0e0-4248-b606-21aaf6b54055",
"name": "dog",
"description": "Great for Juice",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true",
"gui.order": "0",
"consent.screen.text": "So cool!"
},
"protocolMappers": [
{
"id": "f8673b15-97d8-4b7c-b3a2-23394041fb40",
"name": "upn",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "username",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "upn",
"jsonType.label": "String"
}
},
{
"id": "bd2e4c38-2e00-4674-9a3e-d15a88d2565a",
"name": "realm roles",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-realm-role-mapper",
"consentRequired": false,
"config": {
"user.attribute": "foo",
"access.token.claim": "true",
"claim.name": "realm_access.roles",
"jsonType.label": "String",
"multivalued": "true"
}
},
{
"id": "fed9caae-e3e4-4711-9035-087193dd25c4",
"name": "username",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "username",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "preferred_username",
"jsonType.label": "String"
}
}
]
}

View file

@ -0,0 +1,151 @@
import React, { useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import {
AlertVariant,
Dropdown,
DropdownItem,
DropdownToggle,
} from "@patternfly/react-core";
import {
Table,
TableBody,
TableHeader,
TableVariant,
} from "@patternfly/react-table";
import { CaretDownIcon } from "@patternfly/react-icons";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import {
ClientScopeRepresentation,
ProtocolMapperRepresentation,
} from "../models/client-scope";
import { TableToolbar } from "../../components/table-toolbar/TableToolbar";
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
import { RealmContext } from "../../context/realm-context/RealmContext";
import { useAlerts } from "../../components/alert/Alerts";
type MapperListProps = {
clientScope: ClientScopeRepresentation;
};
type Row = {
name: string;
category: string;
type: string;
priority: number;
};
export const MapperList = ({ clientScope }: MapperListProps) => {
const { t } = useTranslation("client-scopes");
const httpClient = useContext(HttpClientContext)!;
const { realm } = useContext(RealmContext);
const { addAlert } = useAlerts();
const [filteredData, setFilteredData] = useState<
{ mapper: ProtocolMapperRepresentation; cells: Row }[]
>();
const [mapperAction, setMapperAction] = useState(false);
const mapperList = clientScope.protocolMappers!;
const mapperTypes = useServerInfo().protocolMapperTypes[
clientScope.protocol!
];
if (!mapperList) {
return (
<ListEmptyState
message={t("emptyMappers")}
instructions={t("emptyMappersInstructions")}
primaryActionText={t("emptyPrimaryAction")}
onPrimaryAction={() => {}}
/>
);
}
const data = mapperList
.map((mapper) => {
const mapperType = mapperTypes.filter(
(type) => type.id === mapper.protocolMapper
)[0];
return {
mapper,
cells: {
name: mapper.name,
category: mapperType.category,
type: mapperType.name,
priority: mapperType.priority,
} as Row,
};
})
.sort((a, b) => a.cells.priority - b.cells.priority);
const filterData = (search: string) => {
setFilteredData(
data.filter((column) =>
column.cells.name.toLowerCase().includes(search.toLowerCase())
)
);
};
return (
<TableToolbar
inputGroupName="clientsScopeToolbarTextInput"
inputGroupPlaceholder={t("mappersSearchFor")}
inputGroupOnChange={filterData}
toolbarItem={
<Dropdown
onSelect={() => setMapperAction(false)}
toggle={
<DropdownToggle
isPrimary
id="mapperAction"
onToggle={() => setMapperAction(!mapperAction)}
toggleIndicator={CaretDownIcon}
>
{t("addMapper")}
</DropdownToggle>
}
isOpen={mapperAction}
dropdownItems={[
<DropdownItem key="predefined">
{t("fromPredefinedMapper")}
</DropdownItem>,
<DropdownItem key="byConfiguration">
{t("byConfiguration")}
</DropdownItem>,
]}
/>
}
>
<Table
variant={TableVariant.compact}
cells={[t("name"), t("category"), t("type"), t("priority")]}
rows={(filteredData || data).map((cell) => {
return { cells: Object.values(cell.cells), mapper: cell.mapper };
})}
aria-label={t("clientScopeList")}
actions={[
{
title: t("common:delete"),
onClick: async (_, rowId) => {
try {
await httpClient.doDelete(
`/admin/realms/${realm}/client-scopes/${clientScope.id}/protocol-mappers/models/${data[rowId].mapper.id}`
);
addAlert(t("mappingDeletedSuccess"), AlertVariant.success);
} catch (error) {
addAlert(
t("mappingDeletedError", { error }),
AlertVariant.danger
);
}
},
},
]}
>
<TableHeader />
<TableBody />
</Table>
</TableToolbar>
);
};

View file

@ -6,9 +6,24 @@
"searchFor": "Search for client scope",
"name": "Name",
"description": "Description",
"category": "Category",
"type": "Type",
"priority": "Priority",
"protocol": "Protocol",
"createClientScopeSuccess": "Client scope created",
"createClientScopeError": "Could not create client scope:",
"settings": "Settings",
"mappers": "Mappers",
"mappersSearchFor": "Search for mapper",
"addMapper": "Add mapper",
"fromPredefinedMapper": "From predefined mappers",
"byConfiguration": "By configuration",
"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",
"emptySecondaryAction": "Configure a new mapper",
"mappingDeletedSuccess": "Mapping successfully deleted",
"mappingDeletedError": "Could not delete mapping: '{{error}}'",
"displayOnConsentScreen": "Display on consent screen",
"consentScreenText": "Consent screen text",
"guiOrder": "GUI Order"

View file

@ -4,7 +4,7 @@ import { HttpClientContext } from "../http-service/HttpClientContext";
import { sortProvider } from "../../util";
import { DataLoader } from "../../components/data-loader/DataLoader";
const ServerInfoContext = createContext<ServerInfoRepresentation>(
export const ServerInfoContext = createContext<ServerInfoRepresentation>(
{} as ServerInfoRepresentation
);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
import React from "react";
import { Meta } from "@storybook/react";
import serverInfo from "../context/server-info/__tests__/mock.json";
import clientScopeMock from "../client-scopes/__tests__/mock-client-scope.json";
import { ServerInfoContext } from "../context/server-info/ServerInfoProvider";
import { MapperList } from "../client-scopes/details/MapperList";
export default {
title: "Mapper List",
component: MapperList,
} as Meta;
export const MapperListExample = () => (
<ServerInfoContext.Provider value={serverInfo}>
<MapperList clientScope={clientScopeMock} />
</ServerInfoContext.Provider>
);