hooked up the build in mapper dialog (#197)

* hooked up the build in mapper dialog

* spelling

* useEffect for setTimout

* fix state

* fix bug with alerts clearing checked items

* fixed tests

* Update src/client-scopes/messages.json

Co-authored-by: Stan Silvert <ssilvert@redhat.com>

* simplified dialog usage

Co-authored-by: Stan Silvert <ssilvert@redhat.com>
This commit is contained in:
Erik Jan de Wit 2020-11-05 22:26:43 +01:00 committed by GitHub
parent 7ebe695921
commit d6e1161c83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 13814 additions and 13741 deletions

View file

@ -1,4 +1,4 @@
import React, { ReactElement, useState } from "react"; import React, { useState } from "react";
import { import {
Button, Button,
ButtonVariant, ButtonVariant,
@ -8,6 +8,7 @@ import {
DataListItemCells, DataListItemCells,
DataListItemRow, DataListItemRow,
Modal, Modal,
ModalVariant,
Text, Text,
TextContent, TextContent,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
@ -24,48 +25,31 @@ import {
ProtocolMapperRepresentation, ProtocolMapperRepresentation,
ProtocolMapperTypeRepresentation, ProtocolMapperTypeRepresentation,
} from "../../context/server-info/server-info"; } from "../../context/server-info/server-info";
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
export type AddMapperDialogProps = { export type AddMapperDialogModalProps = {
protocol: string; protocol: string;
buildIn: boolean; filter?: ProtocolMapperRepresentation[];
onConfirm: ( onConfirm: (
value: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[] value: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[]
) => {}; ) => void;
}; };
type AddMapperDialogModalProps = AddMapperDialogProps & { export type AddMapperDialogProps = AddMapperDialogModalProps & {
open: boolean; open: boolean;
toggleDialog: () => void; toggleDialog: () => void;
}; };
export const useAddMapperDialog = ( export const AddMapperDialog = (props: AddMapperDialogProps) => {
props: AddMapperDialogProps
): [() => void, () => ReactElement] => {
const [show, setShow] = useState(false);
function toggleDialog() {
setShow((show) => !show);
}
const Dialog = () => (
<AddMapperDialog {...props} open={show} toggleDialog={toggleDialog} />
);
return [toggleDialog, Dialog];
};
export const AddMapperDialog = ({
protocol,
buildIn,
open,
toggleDialog,
onConfirm,
}: AddMapperDialogModalProps) => {
const serverInfo = useServerInfo();
const protocolMappers = serverInfo.protocolMapperTypes[protocol];
const buildInMappers = serverInfo.builtinProtocolMappers[protocol];
const { t } = useTranslation("client-scopes"); const { t } = useTranslation("client-scopes");
const [rows, setRows] = useState(
buildInMappers.map((mapper) => { const serverInfo = useServerInfo();
const protocol = props.protocol;
const protocolMappers = serverInfo.protocolMapperTypes[protocol];
const builtInMappers = serverInfo.builtinProtocolMappers[protocol];
const [filter, setFilter] = useState<ProtocolMapperRepresentation[]>([]);
const allRows = builtInMappers.map((mapper) => {
const mapperType = protocolMappers.filter( const mapperType = protocolMappers.filter(
(type) => type.id === mapper.protocolMapper (type) => type.id === mapper.protocolMapper
)[0]; )[0];
@ -74,25 +58,34 @@ export const AddMapperDialog = ({
selected: false, selected: false,
cells: [mapper.name, mapperType.helpText], cells: [mapper.name, mapperType.helpText],
}; };
}) });
); const [rows, setRows] = useState(allRows);
if (props.filter && props.filter.length !== filter.length) {
setFilter(props.filter);
const nameFilter = props.filter.map((f) => f.name);
setRows([...allRows.filter((row) => !nameFilter.includes(row.item.name))]);
}
const isBuiltIn = !!props.filter;
return ( return (
<Modal <Modal
variant={ModalVariant.medium}
title={t("chooseAMapperType")} title={t("chooseAMapperType")}
isOpen={open} isOpen={props.open}
onClose={toggleDialog}
actions={ actions={
buildIn isBuiltIn
? [ ? [
<Button <Button
id="modal-confirm" id="modal-confirm"
key="confirm" key="confirm"
isDisabled={rows.length === 0}
onClick={() => { onClick={() => {
onConfirm( props.onConfirm(
rows.filter((row) => row.selected).map((row) => row.item) rows.filter((row) => row.selected).map((row) => row.item)
); );
toggleDialog(); props.toggleDialog();
}} }}
> >
{t("common:add")} {t("common:add")}
@ -102,7 +95,7 @@ export const AddMapperDialog = ({
key="cancel" key="cancel"
variant={ButtonVariant.secondary} variant={ButtonVariant.secondary}
onClick={() => { onClick={() => {
toggleDialog(); props.toggleDialog();
}} }}
> >
{t("common:cancel")} {t("common:cancel")}
@ -114,12 +107,12 @@ export const AddMapperDialog = ({
<TextContent> <TextContent>
<Text>{t("predefinedMappingDescription")}</Text> <Text>{t("predefinedMappingDescription")}</Text>
</TextContent> </TextContent>
{!buildIn && ( {!isBuiltIn && (
<DataList <DataList
onSelectDataListItem={(id) => { onSelectDataListItem={(id) => {
const mapper = protocolMappers.find((mapper) => mapper.id === id); const mapper = protocolMappers.find((mapper) => mapper.id === id);
onConfirm(mapper!); props.onConfirm(mapper!);
toggleDialog(); props.toggleDialog();
}} }}
aria-label={t("chooseAMapperType")} aria-label={t("chooseAMapperType")}
isCompact isCompact
@ -146,7 +139,7 @@ export const AddMapperDialog = ({
))} ))}
</DataList> </DataList>
)} )}
{buildIn && ( {isBuiltIn && rows.length > 0 && (
<Table <Table
variant={TableVariant.compact} variant={TableVariant.compact}
cells={[t("name"), t("description")]} cells={[t("name"), t("description")]}
@ -162,6 +155,12 @@ export const AddMapperDialog = ({
<TableBody /> <TableBody />
</Table> </Table>
)} )}
{isBuiltIn && rows.length === 0 && (
<ListEmptyState
message={t("emptyMappers")}
instructions={t("emptyBuiltInMappersInstructions")}
/>
)}
</Modal> </Modal>
); );
}; };

View file

@ -1,18 +1,22 @@
import React from "react"; import React, { useState } from "react";
import { mount } from "enzyme"; import { mount } from "enzyme";
import { Button } from "@patternfly/react-core"; import { Button } from "@patternfly/react-core";
import serverInfo from "../../../context/server-info/__tests__/mock.json"; import serverInfo from "../../../context/server-info/__tests__/mock.json";
import { ServerInfoContext } from "../../../context/server-info/ServerInfoProvider"; import { ServerInfoContext } from "../../../context/server-info/ServerInfoProvider";
import { AddMapperDialogProps, useAddMapperDialog } from "../MapperDialog"; import { AddMapperDialogModalProps, AddMapperDialog } from "../MapperDialog";
describe("<MapperDialog/>", () => { describe("<MapperDialog/>", () => {
const Test = (args: AddMapperDialogProps) => { const Test = (args: AddMapperDialogModalProps) => {
const [toggle, Dialog] = useAddMapperDialog(args); const [open, setOpen] = useState(false);
return ( return (
<ServerInfoContext.Provider value={serverInfo}> <ServerInfoContext.Provider value={serverInfo}>
<Dialog /> <AddMapperDialog
<Button id="open" onClick={toggle}> {...args}
open={open}
toggleDialog={() => setOpen(!open)}
/>
<Button id="open" onClick={() => setOpen(true)}>
Show Show
</Button> </Button>
</ServerInfoContext.Provider> </ServerInfoContext.Provider>
@ -22,7 +26,7 @@ describe("<MapperDialog/>", () => {
it("should return empty array when selecting nothing", () => { it("should return empty array when selecting nothing", () => {
const onConfirm = jest.fn(); const onConfirm = jest.fn();
const container = mount( const container = mount(
<Test buildIn={true} protocol="openid-connect" onConfirm={onConfirm} /> <Test filter={[]} protocol="openid-connect" onConfirm={onConfirm} />
); );
container.find("button#open").simulate("click"); container.find("button#open").simulate("click");
@ -36,7 +40,7 @@ describe("<MapperDialog/>", () => {
const onConfirm = jest.fn(); const onConfirm = jest.fn();
const protocol = "openid-connect"; const protocol = "openid-connect";
const container = mount( const container = mount(
<Test buildIn={true} protocol={protocol} onConfirm={onConfirm} /> <Test filter={[]} protocol={protocol} onConfirm={onConfirm} />
); );
container.find("button#open").simulate("click"); container.find("button#open").simulate("click");
@ -57,9 +61,7 @@ describe("<MapperDialog/>", () => {
it("should return selected protocol mapping type on click", () => { it("should return selected protocol mapping type on click", () => {
const onConfirm = jest.fn(); const onConfirm = jest.fn();
const protocol = "openid-connect"; const protocol = "openid-connect";
const container = mount( const container = mount(<Test protocol={protocol} onConfirm={onConfirm} />);
<Test buildIn={false} protocol={protocol} onConfirm={onConfirm} />
);
container.find("button#open").simulate("click"); container.find("button#open").simulate("click");
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();

View file

@ -2,13 +2,12 @@
exports[`<MapperDialog/> should return empty array when selecting nothing 1`] = ` exports[`<MapperDialog/> should return empty array when selecting nothing 1`] = `
<Test <Test
buildIn={true} filter={Array []}
onConfirm={[MockFunction]} onConfirm={[MockFunction]}
protocol="openid-connect" protocol="openid-connect"
> >
<Dialog>
<AddMapperDialog <AddMapperDialog
buildIn={true} filter={Array []}
onConfirm={[MockFunction]} onConfirm={[MockFunction]}
open={true} open={true}
protocol="openid-connect" protocol="openid-connect"
@ -19,6 +18,7 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
Array [ Array [
<Button <Button
id="modal-confirm" id="modal-confirm"
isDisabled={false}
onClick={[Function]} onClick={[Function]}
> >
add add
@ -43,7 +43,7 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
ouiaSafe={true} ouiaSafe={true}
showClose={true} showClose={true}
title="chooseAMapperType" title="chooseAMapperType"
variant="default" variant="medium"
> >
<Portal <Portal
containerInfo={ containerInfo={
@ -55,14 +55,14 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
class="pf-l-bullseye" class="pf-l-bullseye"
> >
<div <div
aria-describedby="pf-modal-part-3" aria-describedby="pf-modal-part-2"
aria-labelledby="pf-modal-part-2" aria-labelledby="pf-modal-part-1"
aria-modal="true" aria-modal="true"
class="pf-c-modal-box" class="pf-c-modal-box pf-m-md"
data-ouia-component-id="OUIA-Generated-Modal-default-2" data-ouia-component-id="OUIA-Generated-Modal-medium-1"
data-ouia-component-type="PF4/ModalContent" data-ouia-component-type="PF4/ModalContent"
data-ouia-safe="true" data-ouia-safe="true"
id="pf-modal-part-1" id="pf-modal-part-0"
role="dialog" role="dialog"
> >
<button <button
@ -93,14 +93,14 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
> >
<h1 <h1
class="pf-c-modal-box__title pf-c-modal-box__title" class="pf-c-modal-box__title pf-c-modal-box__title"
id="pf-modal-part-2" id="pf-modal-part-1"
> >
chooseAMapperType chooseAMapperType
</h1> </h1>
</header> </header>
<div <div
class="pf-c-modal-box__body" class="pf-c-modal-box__body"
id="pf-modal-part-3" id="pf-modal-part-2"
> >
<div <div
class="pf-c-content" class="pf-c-content"
@ -1059,6 +1059,7 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
Array [ Array [
<Button <Button
id="modal-confirm" id="modal-confirm"
isDisabled={false}
onClick={[Function]} onClick={[Function]}
> >
add add
@ -1075,18 +1076,18 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
aria-describedby="" aria-describedby=""
aria-label="" aria-label=""
aria-labelledby="" aria-labelledby=""
boxId="pf-modal-part-1" boxId="pf-modal-part-0"
className="" className=""
descriptorId="pf-modal-part-3" descriptorId="pf-modal-part-2"
hasNoBodyWrapper={false} hasNoBodyWrapper={false}
isOpen={true} isOpen={true}
labelId="pf-modal-part-2" labelId="pf-modal-part-1"
onClose={[Function]} onClose={[Function]}
ouiaId="OUIA-Generated-Modal-default-2" ouiaId="OUIA-Generated-Modal-medium-1"
ouiaSafe={true} ouiaSafe={true}
showClose={true} showClose={true}
title="chooseAMapperType" title="chooseAMapperType"
variant="default" variant="medium"
> >
<Backdrop> <Backdrop>
<div <div
@ -1106,27 +1107,27 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
className="pf-l-bullseye" className="pf-l-bullseye"
> >
<ModalBox <ModalBox
aria-describedby="pf-modal-part-3" aria-describedby="pf-modal-part-2"
aria-label="" aria-label=""
aria-labelledby="pf-modal-part-2" aria-labelledby="pf-modal-part-1"
className="" className=""
data-ouia-component-id="OUIA-Generated-Modal-default-2" data-ouia-component-id="OUIA-Generated-Modal-medium-1"
data-ouia-component-type="PF4/ModalContent" data-ouia-component-type="PF4/ModalContent"
data-ouia-safe={true} data-ouia-safe={true}
id="pf-modal-part-1" id="pf-modal-part-0"
style={Object {}} style={Object {}}
variant="default" variant="medium"
> >
<div <div
aria-describedby="pf-modal-part-3" aria-describedby="pf-modal-part-2"
aria-label={null} aria-label={null}
aria-labelledby="pf-modal-part-2" aria-labelledby="pf-modal-part-1"
aria-modal="true" aria-modal="true"
className="pf-c-modal-box" className="pf-c-modal-box pf-m-md"
data-ouia-component-id="OUIA-Generated-Modal-default-2" data-ouia-component-id="OUIA-Generated-Modal-medium-1"
data-ouia-component-type="PF4/ModalContent" data-ouia-component-type="PF4/ModalContent"
data-ouia-safe={true} data-ouia-safe={true}
id="pf-modal-part-1" id="pf-modal-part-0"
role="dialog" role="dialog"
style={Object {}} style={Object {}}
> >
@ -1183,12 +1184,12 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
> >
<ModalBoxTitle <ModalBoxTitle
className="pf-c-modal-box__title" className="pf-c-modal-box__title"
id="pf-modal-part-2" id="pf-modal-part-1"
title="chooseAMapperType" title="chooseAMapperType"
> >
<h1 <h1
className="pf-c-modal-box__title pf-c-modal-box__title" className="pf-c-modal-box__title pf-c-modal-box__title"
id="pf-modal-part-2" id="pf-modal-part-1"
> >
chooseAMapperType chooseAMapperType
</h1> </h1>
@ -1196,11 +1197,11 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
</header> </header>
</ModalBoxHeader> </ModalBoxHeader>
<ModalBoxBody <ModalBoxBody
id="pf-modal-part-3" id="pf-modal-part-2"
> >
<div <div
className="pf-c-modal-box__body" className="pf-c-modal-box__body"
id="pf-modal-part-3" id="pf-modal-part-2"
> >
<TextContent> <TextContent>
<div <div
@ -17439,6 +17440,7 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
> >
<Button <Button
id="modal-confirm" id="modal-confirm"
isDisabled={false}
key="confirm" key="confirm"
onClick={[Function]} onClick={[Function]}
> >
@ -17490,7 +17492,6 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
</Portal> </Portal>
</Modal> </Modal>
</AddMapperDialog> </AddMapperDialog>
</Dialog>
<Button <Button
id="open" id="open"
onClick={[Function]} onClick={[Function]}
@ -17515,13 +17516,10 @@ exports[`<MapperDialog/> should return empty array when selecting nothing 1`] =
exports[`<MapperDialog/> should return selected protocol mapping type on click 1`] = ` exports[`<MapperDialog/> should return selected protocol mapping type on click 1`] = `
<Test <Test
buildIn={false}
onConfirm={[MockFunction]} onConfirm={[MockFunction]}
protocol="openid-connect" protocol="openid-connect"
> >
<Dialog>
<AddMapperDialog <AddMapperDialog
buildIn={false}
onConfirm={[MockFunction]} onConfirm={[MockFunction]}
open={true} open={true}
protocol="openid-connect" protocol="openid-connect"
@ -17540,7 +17538,7 @@ exports[`<MapperDialog/> should return selected protocol mapping type on click 1
ouiaSafe={true} ouiaSafe={true}
showClose={true} showClose={true}
title="chooseAMapperType" title="chooseAMapperType"
variant="default" variant="medium"
> >
<Portal <Portal
containerInfo={ containerInfo={
@ -17552,14 +17550,14 @@ exports[`<MapperDialog/> should return selected protocol mapping type on click 1
class="pf-l-bullseye" class="pf-l-bullseye"
> >
<div <div
aria-describedby="pf-modal-part-9" aria-describedby="pf-modal-part-4"
aria-labelledby="pf-modal-part-8" aria-labelledby="pf-modal-part-3"
aria-modal="true" aria-modal="true"
class="pf-c-modal-box" class="pf-c-modal-box pf-m-md"
data-ouia-component-id="OUIA-Generated-Modal-default-8" data-ouia-component-id="OUIA-Generated-Modal-medium-3"
data-ouia-component-type="PF4/ModalContent" data-ouia-component-type="PF4/ModalContent"
data-ouia-safe="true" data-ouia-safe="true"
id="pf-modal-part-7" id="pf-modal-part-2"
role="dialog" role="dialog"
> >
<button <button
@ -17590,14 +17588,14 @@ exports[`<MapperDialog/> should return selected protocol mapping type on click 1
> >
<h1 <h1
class="pf-c-modal-box__title pf-c-modal-box__title" class="pf-c-modal-box__title pf-c-modal-box__title"
id="pf-modal-part-8" id="pf-modal-part-3"
> >
chooseAMapperType chooseAMapperType
</h1> </h1>
</header> </header>
<div <div
class="pf-c-modal-box__body" class="pf-c-modal-box__body"
id="pf-modal-part-9" id="pf-modal-part-4"
> >
<div <div
class="pf-c-content" class="pf-c-content"
@ -18002,18 +18000,18 @@ exports[`<MapperDialog/> should return selected protocol mapping type on click 1
aria-describedby="" aria-describedby=""
aria-label="" aria-label=""
aria-labelledby="" aria-labelledby=""
boxId="pf-modal-part-7" boxId="pf-modal-part-2"
className="" className=""
descriptorId="pf-modal-part-9" descriptorId="pf-modal-part-4"
hasNoBodyWrapper={false} hasNoBodyWrapper={false}
isOpen={true} isOpen={true}
labelId="pf-modal-part-8" labelId="pf-modal-part-3"
onClose={[Function]} onClose={[Function]}
ouiaId="OUIA-Generated-Modal-default-8" ouiaId="OUIA-Generated-Modal-medium-3"
ouiaSafe={true} ouiaSafe={true}
showClose={true} showClose={true}
title="chooseAMapperType" title="chooseAMapperType"
variant="default" variant="medium"
> >
<Backdrop> <Backdrop>
<div <div
@ -18033,27 +18031,27 @@ exports[`<MapperDialog/> should return selected protocol mapping type on click 1
className="pf-l-bullseye" className="pf-l-bullseye"
> >
<ModalBox <ModalBox
aria-describedby="pf-modal-part-9" aria-describedby="pf-modal-part-4"
aria-label="" aria-label=""
aria-labelledby="pf-modal-part-8" aria-labelledby="pf-modal-part-3"
className="" className=""
data-ouia-component-id="OUIA-Generated-Modal-default-8" data-ouia-component-id="OUIA-Generated-Modal-medium-3"
data-ouia-component-type="PF4/ModalContent" data-ouia-component-type="PF4/ModalContent"
data-ouia-safe={true} data-ouia-safe={true}
id="pf-modal-part-7" id="pf-modal-part-2"
style={Object {}} style={Object {}}
variant="default" variant="medium"
> >
<div <div
aria-describedby="pf-modal-part-9" aria-describedby="pf-modal-part-4"
aria-label={null} aria-label={null}
aria-labelledby="pf-modal-part-8" aria-labelledby="pf-modal-part-3"
aria-modal="true" aria-modal="true"
className="pf-c-modal-box" className="pf-c-modal-box pf-m-md"
data-ouia-component-id="OUIA-Generated-Modal-default-8" data-ouia-component-id="OUIA-Generated-Modal-medium-3"
data-ouia-component-type="PF4/ModalContent" data-ouia-component-type="PF4/ModalContent"
data-ouia-safe={true} data-ouia-safe={true}
id="pf-modal-part-7" id="pf-modal-part-2"
role="dialog" role="dialog"
style={Object {}} style={Object {}}
> >
@ -18110,12 +18108,12 @@ exports[`<MapperDialog/> should return selected protocol mapping type on click 1
> >
<ModalBoxTitle <ModalBoxTitle
className="pf-c-modal-box__title" className="pf-c-modal-box__title"
id="pf-modal-part-8" id="pf-modal-part-3"
title="chooseAMapperType" title="chooseAMapperType"
> >
<h1 <h1
className="pf-c-modal-box__title pf-c-modal-box__title" className="pf-c-modal-box__title pf-c-modal-box__title"
id="pf-modal-part-8" id="pf-modal-part-3"
> >
chooseAMapperType chooseAMapperType
</h1> </h1>
@ -18123,11 +18121,11 @@ exports[`<MapperDialog/> should return selected protocol mapping type on click 1
</header> </header>
</ModalBoxHeader> </ModalBoxHeader>
<ModalBoxBody <ModalBoxBody
id="pf-modal-part-9" id="pf-modal-part-4"
> >
<div <div
className="pf-c-modal-box__body" className="pf-c-modal-box__body"
id="pf-modal-part-9" id="pf-modal-part-4"
> >
<TextContent> <TextContent>
<div <div
@ -19143,7 +19141,6 @@ exports[`<MapperDialog/> should return selected protocol mapping type on click 1
</Portal> </Portal>
</Modal> </Modal>
</AddMapperDialog> </AddMapperDialog>
</Dialog>
<Button <Button
id="open" id="open"
onClick={[Function]} onClick={[Function]}

View file

@ -1,5 +1,6 @@
import React, { useContext, useState } from "react"; import React, { useContext, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { import {
AlertVariant, AlertVariant,
ButtonVariant, ButtonVariant,
@ -16,6 +17,11 @@ import {
import { CaretDownIcon } from "@patternfly/react-icons"; import { CaretDownIcon } from "@patternfly/react-icons";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import {
ProtocolMapperRepresentation as ServerInfoProtocolMapper,
ProtocolMapperTypeRepresentation,
} from "../../context/server-info/server-info";
import { import {
ClientScopeRepresentation, ClientScopeRepresentation,
ProtocolMapperRepresentation, ProtocolMapperRepresentation,
@ -25,10 +31,11 @@ import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState
import { HttpClientContext } from "../../context/http-service/HttpClientContext"; import { HttpClientContext } from "../../context/http-service/HttpClientContext";
import { RealmContext } from "../../context/realm-context/RealmContext"; import { RealmContext } from "../../context/realm-context/RealmContext";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import { Link } from "react-router-dom"; import { AddMapperDialog } from "../add/MapperDialog";
type MapperListProps = { type MapperListProps = {
clientScope: ClientScopeRepresentation; clientScope: ClientScopeRepresentation;
refresh: () => void;
}; };
type Row = { type Row = {
@ -38,7 +45,7 @@ type Row = {
priority: number; priority: number;
}; };
export const MapperList = ({ clientScope }: MapperListProps) => { export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
const { t } = useTranslation("client-scopes"); const { t } = useTranslation("client-scopes");
const httpClient = useContext(HttpClientContext)!; const httpClient = useContext(HttpClientContext)!;
const { realm } = useContext(RealmContext); const { realm } = useContext(RealmContext);
@ -53,13 +60,39 @@ export const MapperList = ({ clientScope }: MapperListProps) => {
clientScope.protocol! clientScope.protocol!
]; ];
const [builtInDialogOpen, setBuiltInDialogOpen] = useState(false);
const toggleBuiltInMapperDialog = () =>
setBuiltInDialogOpen(!builtInDialogOpen);
const addMappers = async (
mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[]
) => {
try {
await httpClient.doPost(
`/admin/realms/${realm}/client-scopes/${clientScope.id}/protocol-mappers/add-models`,
mappers
);
refresh();
addAlert(t("mappingCreatedSuccess"), AlertVariant.success);
} catch (error) {
addAlert(t("mappingCreatedError", { error }), AlertVariant.danger);
}
};
if (!mapperList) { if (!mapperList) {
return ( return (
<>
<AddMapperDialog
protocol={clientScope.protocol!}
filter={(mapperList as ServerInfoProtocolMapper[]) || []}
onConfirm={addMappers}
open={builtInDialogOpen}
toggleDialog={toggleBuiltInMapperDialog}
/>
<ListEmptyState <ListEmptyState
message={t("emptyMappers")} message={t("emptyMappers")}
instructions={t("emptyMappersInstructions")} instructions={t("emptyMappersInstructions")}
primaryActionText={t("emptyPrimaryAction")} primaryActionText={t("emptyPrimaryAction")}
onPrimaryAction={() => {}} onPrimaryAction={toggleBuiltInMapperDialog}
secondaryActions={[ secondaryActions={[
{ {
text: t("emptySecondaryAction"), text: t("emptySecondaryAction"),
@ -68,6 +101,7 @@ export const MapperList = ({ clientScope }: MapperListProps) => {
}, },
]} ]}
/> />
</>
); );
} }
@ -122,7 +156,7 @@ export const MapperList = ({ clientScope }: MapperListProps) => {
} }
isOpen={mapperAction} isOpen={mapperAction}
dropdownItems={[ dropdownItems={[
<DropdownItem key="predefined"> <DropdownItem key="predefined" onClick={toggleBuiltInMapperDialog}>
{t("fromPredefinedMapper")} {t("fromPredefinedMapper")}
</DropdownItem>, </DropdownItem>,
<DropdownItem key="byConfiguration"> <DropdownItem key="byConfiguration">
@ -132,6 +166,13 @@ export const MapperList = ({ clientScope }: MapperListProps) => {
/> />
} }
> >
<AddMapperDialog
protocol={clientScope.protocol!}
filter={(mapperList as ServerInfoProtocolMapper[]) || []}
onConfirm={addMappers}
open={builtInDialogOpen}
toggleDialog={toggleBuiltInMapperDialog}
/>
<Table <Table
variant={TableVariant.compact} variant={TableVariant.compact}
cells={[t("name"), t("category"), t("type"), t("priority")]} cells={[t("name"), t("category"), t("type"), t("priority")]}
@ -147,6 +188,7 @@ export const MapperList = ({ clientScope }: MapperListProps) => {
await httpClient.doDelete( await httpClient.doDelete(
`/admin/realms/${realm}/client-scopes/${clientScope.id}/protocol-mappers/models/${data[rowId].mapper.id}` `/admin/realms/${realm}/client-scopes/${clientScope.id}/protocol-mappers/models/${data[rowId].mapper.id}`
); );
refresh();
addAlert(t("mappingDeletedSuccess"), AlertVariant.success); addAlert(t("mappingDeletedSuccess"), AlertVariant.success);
} catch (error) { } catch (error) {
addAlert( addAlert(

View file

@ -46,8 +46,7 @@ export const ClientScopeForm = () => {
const [open, isOpen] = useState(false); const [open, isOpen] = useState(false);
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
useEffect(() => { const load = async () => {
(async () => {
if (id) { if (id) {
const response = await httpClient.doGet<ClientScopeRepresentation>( const response = await httpClient.doGet<ClientScopeRepresentation>(
`/admin/realms/${realm}/client-scopes/${id}` `/admin/realms/${realm}/client-scopes/${id}`
@ -63,7 +62,10 @@ export const ClientScopeForm = () => {
setClientScope(response.data); setClientScope(response.data);
} }
})(); };
useEffect(() => {
load();
}, []); }, []);
const save = async (clientScopes: ClientScopeRepresentation) => { const save = async (clientScopes: ClientScopeRepresentation) => {
@ -297,7 +299,9 @@ export const ClientScopeForm = () => {
</Form> </Form>
</Tab> </Tab>
<Tab eventKey={1} title={<TabTitleText>{t("mappers")}</TabTitleText>}> <Tab eventKey={1} title={<TabTitleText>{t("mappers")}</TabTitleText>}>
{clientScope && <MapperList clientScope={clientScope} />} {clientScope && (
<MapperList clientScope={clientScope} refresh={load} />
)}
</Tab> </Tab>
</Tabs> </Tabs>
</PageSection> </PageSection>

View file

@ -13,6 +13,8 @@
"protocol": "Protocol", "protocol": "Protocol",
"includeInTokenScope": "Include in token scope", "includeInTokenScope": "Include in token scope",
"mappingDetails": "Mapper details", "mappingDetails": "Mapper details",
"mappingCreatedSuccess": "Mapping successfully created",
"mappingCreatedError": "Could not create mapping: '{{error}}'",
"deleteMappingTitle": "Delete mapping?", "deleteMappingTitle": "Delete mapping?",
"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",
@ -49,6 +51,7 @@
"byConfiguration": "By configuration", "byConfiguration": "By configuration",
"emptyMappers": "No mappers", "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.", "emptyMappersInstructions": "If you want to add mappers, please click the button below to add some predefined mappers or to configure a new mapper.",
"emptyBuiltInMappersInstructions": "All built in mappers were added to this client",
"emptyPrimaryAction": "Add predefined mapper", "emptyPrimaryAction": "Add predefined mapper",
"emptySecondaryAction": "Configure a new mapper", "emptySecondaryAction": "Configure a new mapper",
"mappingDeletedSuccess": "Mapping successfully deleted", "mappingDeletedSuccess": "Mapping successfully deleted",

View file

@ -1,4 +1,10 @@
import React, { useState, createContext, ReactNode, useContext } from "react"; import React, {
useState,
createContext,
ReactNode,
useContext,
useEffect,
} from "react";
import { AlertType, AlertPanel } from "./AlertPanel"; import { AlertType, AlertPanel } from "./AlertPanel";
import { AlertVariant } from "@patternfly/react-core"; import { AlertVariant } from "@patternfly/react-core";
@ -12,21 +18,39 @@ export const AlertContext = createContext<AlertProps>({
export const useAlerts = () => useContext(AlertContext); export const useAlerts = () => useContext(AlertContext);
type TimeOut = {
key: number;
timeOut: NodeJS.Timeout;
};
export const AlertProvider = ({ children }: { children: ReactNode }) => { export const AlertProvider = ({ children }: { children: ReactNode }) => {
const [alerts, setAlerts] = useState<AlertType[]>([]); const [alerts, setAlerts] = useState<AlertType[]>([]);
const [timers, setTimers] = useState<TimeOut[]>([]);
useEffect(() => {
const timersKeys = timers.map((timer) => timer.key);
const timeOuts = alerts
.filter((alert) => !timersKeys.includes(alert.key))
.map((alert) => {
const timeOut = setTimeout(() => hideAlert(alert.key), 8000);
return { key: alert.key, timeOut };
});
setTimers([...timers, ...timeOuts]);
return () => timers.forEach((timer) => clearTimeout(timer.timeOut));
}, [alerts]);
const createId = () => new Date().getTime(); const createId = () => new Date().getTime();
const hideAlert = (key: number) => { const hideAlert = (key: number) => {
setAlerts((alerts) => [...alerts.filter((el) => el.key !== key)]); setAlerts((alerts) => [...alerts.filter((el) => el.key !== key)]);
setTimers((timers) => [...timers.filter((timer) => timer.key === key)]);
}; };
const addAlert = ( const addAlert = (
message: string, message: string,
variant: AlertVariant = AlertVariant.default variant: AlertVariant = AlertVariant.default
) => { ) => {
const key = createId(); setAlerts([...alerts, { key: createId(), message, variant }]);
setAlerts([...alerts, { key, message, variant }]);
setTimeout(() => hideAlert(key), 8000);
}; };
return ( return (

View file

@ -1,4 +1,4 @@
import React from "react"; import React, { useState } from "react";
import { Button } from "@patternfly/react-core"; import { Button } from "@patternfly/react-core";
import { Meta, Story } from "@storybook/react"; import { Meta, Story } from "@storybook/react";
@ -7,7 +7,6 @@ import { ServerInfoContext } from "../context/server-info/ServerInfoProvider";
import { import {
AddMapperDialog, AddMapperDialog,
AddMapperDialogProps, AddMapperDialogProps,
useAddMapperDialog,
} from "../client-scopes/add/MapperDialog"; } from "../client-scopes/add/MapperDialog";
export default { export default {
@ -16,11 +15,15 @@ export default {
} as Meta; } as Meta;
const Template: Story<AddMapperDialogProps> = (args) => { const Template: Story<AddMapperDialogProps> = (args) => {
const [toggle, Dialog] = useAddMapperDialog(args); const [open, setOpen] = useState(false);
return ( return (
<ServerInfoContext.Provider value={serverInfo}> <ServerInfoContext.Provider value={serverInfo}>
<Dialog /> <AddMapperDialog
<Button onClick={toggle}>Show</Button> {...args}
open={open}
toggleDialog={() => setOpen(!open)}
/>
<Button onClick={() => setOpen(true)}>Show</Button>
</ServerInfoContext.Provider> </ServerInfoContext.Provider>
); );
}; };
@ -28,11 +31,10 @@ const Template: Story<AddMapperDialogProps> = (args) => {
export const BuildInDialog = Template.bind({}); export const BuildInDialog = Template.bind({});
BuildInDialog.args = { BuildInDialog.args = {
protocol: "openid-connect", protocol: "openid-connect",
buildIn: true, filter: [],
}; };
export const ProtocolMapperDialog = Template.bind({}); export const ProtocolMapperDialog = Template.bind({});
ProtocolMapperDialog.args = { ProtocolMapperDialog.args = {
protocol: "openid-connect", protocol: "openid-connect",
buildIn: false,
}; };

View file

@ -13,6 +13,6 @@ export default {
export const MapperListExample = () => ( export const MapperListExample = () => (
<ServerInfoContext.Provider value={serverInfo}> <ServerInfoContext.Provider value={serverInfo}>
<MapperList clientScope={clientScopeMock} /> <MapperList clientScope={clientScopeMock} refresh={() => {}} />
</ServerInfoContext.Provider> </ServerInfoContext.Provider>
); );