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:
parent
b49d07e18c
commit
4fa325f45c
6 changed files with 7515 additions and 1 deletions
58
src/client-scopes/__tests__/mock-client-scope.json
Normal file
58
src/client-scopes/__tests__/mock-client-scope.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
151
src/client-scopes/details/MapperList.tsx
Normal file
151
src/client-scopes/details/MapperList.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
7272
src/context/server-info/__tests__/mock.json
Normal file
7272
src/context/server-info/__tests__/mock.json
Normal file
File diff suppressed because it is too large
Load diff
18
src/stories/MapperList.stories.tsx
Normal file
18
src/stories/MapperList.stories.tsx
Normal 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>
|
||||
);
|
Loading…
Reference in a new issue