Added pagination and filtering to dialog (#2526)

This commit is contained in:
Erik Jan de Wit 2022-05-03 12:42:24 +02:00 committed by GitHub
parent 2ac3a7457e
commit f4a7063852
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 673 deletions

View file

@ -1,58 +0,0 @@
{
"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

@ -1,485 +0,0 @@
[
{
"id": "3507ed12-d8b0-455c-b91a-62a6765ecf0f",
"name": "address",
"description": "OpenID Connect built-in scope: address",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true",
"consent.screen.text": "${addressScopeConsentText}"
},
"protocolMappers": [
{
"id": "b0582082-abab-4c63-b3b7-a92afe6b3436",
"name": "address",
"protocol": "openid-connect",
"protocolMapper": "oidc-address-mapper",
"consentRequired": false,
"config": {
"user.attribute.formatted": "formatted",
"user.attribute.country": "country",
"user.attribute.postal_code": "postal_code",
"userinfo.token.claim": "true",
"user.attribute.street": "street",
"id.token.claim": "true",
"user.attribute.region": "region",
"access.token.claim": "true",
"user.attribute.locality": "locality"
}
}
]
},
{
"id": "eb8c7985-5459-45a9-ace5-2959ce0fd1c9",
"name": "email",
"description": "OpenID Connect built-in scope: email",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true",
"consent.screen.text": "${emailScopeConsentText}"
},
"protocolMappers": [
{
"id": "348dfe5c-26e6-43e8-bc80-b7db9f842f24",
"name": "email",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "email",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "email",
"jsonType.label": "String"
}
},
{
"id": "bfe77908-4ca3-40ea-b5be-75bea87f5bb1",
"name": "email verified",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "emailVerified",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "email_verified",
"jsonType.label": "boolean"
}
}
]
},
{
"id": "e604d76d-20ec-4d80-acee-1885af201568",
"name": "microprofile-jwt",
"description": "Microprofile - JWT built-in scope",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "false"
},
"protocolMappers": [
{
"id": "63a71cf3-df7c-4a81-a23f-d3ba62801c72",
"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": "5eb3444b-8e96-4267-9afc-20abd56613aa",
"name": "groups",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-realm-role-mapper",
"consentRequired": false,
"config": {
"multivalued": "true",
"user.attribute": "foo",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "groups",
"jsonType.label": "String"
}
}
]
},
{
"id": "2cdac876-a8ce-4cde-8bcb-00e28804ec91",
"name": "offline_access",
"description": "OpenID Connect built-in scope: offline_access",
"protocol": "openid-connect",
"attributes": {
"consent.screen.text": "${offlineAccessScopeConsentText}",
"display.on.consent.screen": "true"
}
},
{
"id": "3db88729-214e-4c71-8fac-ee744279538b",
"name": "phone",
"description": "OpenID Connect built-in scope: phone",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true",
"consent.screen.text": "${phoneScopeConsentText}"
},
"protocolMappers": [
{
"id": "00ca4abc-fc26-4273-9d77-d7a793f38976",
"name": "phone number verified",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "phoneNumberVerified",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "phone_number_verified",
"jsonType.label": "boolean"
}
},
{
"id": "98885779-b84e-4565-bc1b-a0c703f03be0",
"name": "phone number",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "phoneNumber",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "phone_number",
"jsonType.label": "String"
}
}
]
},
{
"id": "82aca51c-22b4-4156-93a9-3ed33ec2adcc",
"name": "profile",
"description": "OpenID Connect built-in scope: profile",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true",
"consent.screen.text": "${profileScopeConsentText}"
},
"protocolMappers": [
{
"id": "d974a079-8416-4dea-9e49-76dab694e836",
"name": "full name",
"protocol": "openid-connect",
"protocolMapper": "oidc-full-name-mapper",
"consentRequired": false,
"config": {
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
},
{
"id": "2b0e5ec3-cc38-44c4-8851-98c0e3e3f60d",
"name": "birthdate",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "birthdate",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "birthdate",
"jsonType.label": "String"
}
},
{
"id": "feef3c77-5a8e-4f22-94c8-fc606eb8dad0",
"name": "website",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "website",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "website",
"jsonType.label": "String"
}
},
{
"id": "e0340530-efde-4bdf-8399-c98b994e3c4f",
"name": "nickname",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "nickname",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "nickname",
"jsonType.label": "String"
}
},
{
"id": "bef63a97-20a4-4595-9e31-881273af8b47",
"name": "locale",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "locale",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "locale",
"jsonType.label": "String"
}
},
{
"id": "3a4e571b-9ee4-4553-8a54-dcf0ab757b39",
"name": "picture",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "picture",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "picture",
"jsonType.label": "String"
}
},
{
"id": "d0c55da1-f814-4bfe-a311-b34ddd7ee2fb",
"name": "updated at",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "updatedAt",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "updated_at",
"jsonType.label": "String"
}
},
{
"id": "392fa527-96e9-41a5-8fa4-6deb1f3916a5",
"name": "middle name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "middleName",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "middle_name",
"jsonType.label": "String"
}
},
{
"id": "042e6e1e-f041-432f-88bc-79421366fb99",
"name": "gender",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "gender",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "gender",
"jsonType.label": "String"
}
},
{
"id": "e269f729-2eca-4ff0-9caf-3baa4f6188c5",
"name": "zoneinfo",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "zoneinfo",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "zoneinfo",
"jsonType.label": "String"
}
},
{
"id": "b3929aa6-6acf-4b13-9d23-ee459926feef",
"name": "given name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "firstName",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "given_name",
"jsonType.label": "String"
}
},
{
"id": "877d4b97-2520-40f7-9e58-cd99560a4637",
"name": "profile",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "profile",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "profile",
"jsonType.label": "String"
}
},
{
"id": "f6aab00d-4b15-4ef3-a037-50d8a6c047ff",
"name": "family name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "lastName",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "family_name",
"jsonType.label": "String"
}
},
{
"id": "52483504-1da0-4645-8df0-d7ec36bf835a",
"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"
}
}
]
},
{
"id": "dc401683-7876-4a01-a670-73deae0a10c2",
"name": "role_list",
"description": "SAML role list",
"protocol": "saml",
"attributes": {
"consent.screen.text": "${samlRoleListScopeConsentText}",
"display.on.consent.screen": "true"
},
"protocolMappers": [
{
"id": "4903f029-ca74-4447-b9ac-cf7799f2391c",
"name": "role list",
"protocol": "saml",
"protocolMapper": "saml-role-list-mapper",
"consentRequired": false,
"config": {
"single": "false",
"attribute.nameformat": "Basic",
"attribute.name": "Role"
}
}
]
},
{
"id": "715eec20-9d2b-45cf-b2c3-fd11aae96b63",
"name": "roles",
"description": "OpenID Connect scope for add user roles to the access token",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "false",
"display.on.consent.screen": "true",
"consent.screen.text": "${rolesScopeConsentText}"
},
"protocolMappers": [
{
"id": "d1464021-822d-41d8-8195-d8962fe70f61",
"name": "audience resolve",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-resolve-mapper",
"consentRequired": false,
"config": {}
},
{
"id": "b85d197d-f195-4dcd-a873-77ee4ec9fcea",
"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": "ef5b5c95-5236-41f1-ab9b-3e4213abbe76",
"name": "client roles",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-client-role-mapper",
"consentRequired": false,
"config": {
"user.attribute": "foo",
"access.token.claim": "true",
"claim.name": "resource_access.${client_id}.roles",
"jsonType.label": "String",
"multivalued": "true"
}
}
]
},
{
"id": "30b4d89f-bfd9-45d4-b71f-01dd0f64da57",
"name": "web-origins",
"description": "OpenID Connect scope for add allowed web origins to the access token",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "false",
"display.on.consent.screen": "false",
"consent.screen.text": ""
},
"protocolMappers": [
{
"id": "76f254c5-dc78-4048-abc9-c9de9d55f5a4",
"name": "allowed web origins",
"protocol": "openid-connect",
"protocolMapper": "oidc-allowed-origins-mapper",
"consentRequired": false,
"config": {}
}
]
}
]

View file

@ -1,89 +0,0 @@
/**
* @jest-environment jsdom
*/
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import { Button } from "@patternfly/react-core";
import type { ServerInfoRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation";
import type WhoAmIRepresentation from "@keycloak/keycloak-admin-client/lib/defs/whoAmIRepresentation";
import { ServerInfoContext } from "../../context/server-info/ServerInfoProvider";
import serverInfo from "../../context/server-info/__tests__/mock.json";
import useToggle from "../../utils/useToggle";
import { AddMapperDialog, AddMapperDialogModalProps } from "./MapperDialog";
import { WhoAmI, WhoAmIContext } from "../../context/whoami/WhoAmI";
import whoami from "../../context/whoami/__tests__/mock-whoami.json";
describe("MapperDialog", () => {
const Test = (args: AddMapperDialogModalProps) => {
const [open, toggleOpen, setOpen] = useToggle();
return (
<ServerInfoContext.Provider
value={serverInfo as unknown as ServerInfoRepresentation}
>
<WhoAmIContext.Provider
value={{
refresh: () => {},
whoAmI: new WhoAmI(whoami as WhoAmIRepresentation),
}}
>
<AddMapperDialog {...args} open={open} toggleDialog={toggleOpen} />
<Button onClick={() => setOpen(true)}>
{!open ? "Show" : "Hide"}
</Button>
</WhoAmIContext.Provider>
</ServerInfoContext.Provider>
);
};
it("disables the add button when nothing is selected", () => {
render(<Test filter={[]} protocol="openid-connect" onConfirm={() => {}} />);
fireEvent.click(screen.getByText("Show"));
expect(screen.getByTestId("confirm")).toHaveClass("pf-m-disabled");
});
it("returns array with selected build in protocol mapping", () => {
const onConfirm = jest.fn();
const protocol = "openid-connect";
render(<Test filter={[]} protocol={protocol} onConfirm={onConfirm} />);
fireEvent.click(screen.getByText("Show"));
fireEvent.click(screen.getByLabelText("Select row 0"));
fireEvent.click(screen.getByLabelText("Select row 1"));
fireEvent.click(screen.getByTestId("confirm"));
expect(onConfirm).toBeCalledWith([
serverInfo.builtinProtocolMappers[protocol][0],
serverInfo.builtinProtocolMappers[protocol][1],
]);
});
it("returns selected protocol mapping type on click", () => {
const onConfirm = jest.fn();
const protocol = "openid-connect";
render(<Test protocol={protocol} onConfirm={onConfirm} />);
fireEvent.click(screen.getByText("Show"));
fireEvent.click(screen.getByLabelText("Allowed Web Origins"));
expect(onConfirm).toBeCalledWith(
serverInfo.protocolMapperTypes[protocol][0]
);
});
it("closes the dialog on 'X' click", () => {
render(<Test protocol="openid-connect" onConfirm={() => {}} />);
fireEvent.click(screen.getByText("Show"));
expect(screen.getByText("Hide")).toBeInTheDocument();
fireEvent.click(screen.getByLabelText("Close"));
expect(screen.getByText("Show")).toBeInTheDocument();
});
});

View file

@ -1,4 +1,5 @@
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { import {
Button, Button,
ButtonVariant, ButtonVariant,
@ -13,19 +14,20 @@ import {
TextContent, TextContent,
TextVariants, TextVariants,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import {
Table,
TableBody,
TableHeader,
TableVariant,
} from "@patternfly/react-table";
import { useTranslation } from "react-i18next";
import type ProtocolMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/protocolMapperRepresentation"; import type ProtocolMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/protocolMapperRepresentation";
import type { ProtocolMapperTypeRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation"; import type { ProtocolMapperTypeRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { useWhoAmI } from "../../context/whoami/WhoAmI"; import { useWhoAmI } from "../../context/whoami/WhoAmI";
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState"; import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
type Row = {
name: string;
description: string;
item: ProtocolMapperRepresentation;
};
export type AddMapperDialogModalProps = { export type AddMapperDialogModalProps = {
protocol: string; protocol: string;
@ -49,6 +51,7 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => {
const protocolMappers = serverInfo.protocolMapperTypes![protocol]; const protocolMappers = serverInfo.protocolMapperTypes![protocol];
const builtInMappers = serverInfo.builtinProtocolMappers![protocol]; const builtInMappers = serverInfo.builtinProtocolMappers![protocol];
const [filter, setFilter] = useState<ProtocolMapperRepresentation[]>([]); const [filter, setFilter] = useState<ProtocolMapperRepresentation[]>([]);
const [selectedRows, setSelectedRows] = useState<Row[]>([]);
const allRows = useMemo( const allRows = useMemo(
() => () =>
@ -60,8 +63,8 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => {
)[0]; )[0];
return { return {
item: mapper, item: mapper,
selected: false, name: mapper.name!,
cells: [mapper.name, mapperType.helpText], description: mapperType.helpText,
}; };
}), }),
[] []
@ -74,10 +77,6 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => {
setRows([...allRows.filter((row) => !nameFilter.includes(row.item.name))]); setRows([...allRows.filter((row) => !nameFilter.includes(row.item.name))]);
} }
const selectedRows = rows
.filter((row) => row.selected)
.map((row) => row.item);
const sortedProtocolMappers = useMemo( const sortedProtocolMappers = useMemo(
() => () =>
protocolMappers.sort((a, b) => protocolMappers.sort((a, b) =>
@ -175,36 +174,30 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => {
))} ))}
</DataList> </DataList>
)} )}
{isBuiltIn && rows.length > 0 && ( {isBuiltIn && (
<Table <KeycloakDataTable
variant={TableVariant.compact} loader={rows}
cells={header} onSelect={setSelectedRows}
onSelect={(_, isSelected, rowIndex) => {
if (rowIndex === -1) {
setRows(
rows.map((row) => ({
...row,
selected: isSelected,
}))
);
} else {
rows[rowIndex].selected = isSelected;
setRows([...rows]);
}
}}
canSelectAll canSelectAll
rows={rows} ariaLabelKey="client-scopes:addPredefinedMappers"
aria-label={t("addPredefinedMappers")} searchPlaceholderKey="common:searchForMapper"
> columns={[
<TableHeader /> {
<TableBody /> name: "name",
</Table> displayKey: "common:name",
)} },
{isBuiltIn && rows.length === 0 && ( {
name: "description",
displayKey: "common:description",
},
]}
emptyState={
<ListEmptyState <ListEmptyState
message={t("common:emptyMappers")} message={t("common:emptyMappers")}
instructions={t("client-scopes:emptyBuiltInMappersInstructions")} instructions={t("client-scopes:emptyBuiltInMappersInstructions")}
/> />
}
/>
)} )}
</Modal> </Modal>
); );