From a9dc031fffbde0f61cecb0eecb8d74c54d11c7b3 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Wed, 21 Oct 2020 14:18:41 +0200 Subject: [PATCH] Introduced a predefined mapper dialog (#169) * added optional dialog variant * added predefined scope dialog * added mapper dialog for predefined and custom * format * changed to use dataList instead of table * fixed test --- src/client-scopes/add/MapperDialog.tsx | 167 + .../add/__tests__/MapperDialog.test.tsx | 75 + .../__snapshots__/MapperDialog.test.tsx.snap | 19167 ++++++++++++++++ src/client-scopes/messages.json | 5 +- src/common-messages.json | 1 + .../confirm-dialog/ConfirmDialog.tsx | 4 +- .../__snapshots__/ConfirmDialog.test.tsx.snap | 18 +- src/stories/AddMapperDialog.stories.tsx | 38 + 8 files changed, 19464 insertions(+), 11 deletions(-) create mode 100644 src/client-scopes/add/MapperDialog.tsx create mode 100644 src/client-scopes/add/__tests__/MapperDialog.test.tsx create mode 100644 src/client-scopes/add/__tests__/__snapshots__/MapperDialog.test.tsx.snap create mode 100644 src/stories/AddMapperDialog.stories.tsx diff --git a/src/client-scopes/add/MapperDialog.tsx b/src/client-scopes/add/MapperDialog.tsx new file mode 100644 index 0000000000..a35d1a76b1 --- /dev/null +++ b/src/client-scopes/add/MapperDialog.tsx @@ -0,0 +1,167 @@ +import React, { ReactElement, useState } from "react"; +import { + Button, + ButtonVariant, + DataList, + DataListCell, + DataListItem, + DataListItemCells, + DataListItemRow, + Modal, + Text, + TextContent, +} from "@patternfly/react-core"; +import { + Table, + TableBody, + TableHeader, + TableVariant, +} from "@patternfly/react-table"; +import { useTranslation } from "react-i18next"; + +import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; +import { + ProtocolMapperRepresentation, + ProtocolMapperTypeRepresentation, +} from "../../context/server-info/server-info"; + +export type AddMapperDialogProps = { + protocol: string; + buildIn: boolean; + onConfirm: ( + value: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[] + ) => {}; +}; + +type AddMapperDialogModalProps = AddMapperDialogProps & { + open: boolean; + toggleDialog: () => void; +}; + +export const useAddMapperDialog = ( + props: AddMapperDialogProps +): [() => void, () => ReactElement] => { + const [show, setShow] = useState(false); + + function toggleDialog() { + setShow((show) => !show); + } + + const Dialog = () => ( + + ); + 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 [rows, setRows] = useState( + buildInMappers.map((mapper) => { + const mapperType = protocolMappers.filter( + (type) => type.id === mapper.protocolMapper + )[0]; + return { + item: mapper, + selected: false, + cells: [mapper.name, mapperType.helpText], + }; + }) + ); + + return ( + { + onConfirm( + rows.filter((row) => row.selected).map((row) => row.item) + ); + toggleDialog(); + }} + > + {t("common:add")} + , + , + ] + : [] + } + > + + {t("predefinedMappingDescription")} + + {!buildIn && ( + { + const mapper = protocolMappers.find((mapper) => mapper.id === id); + onConfirm(mapper!); + toggleDialog(); + }} + aria-label={t("chooseAMapperType")} + isCompact + > + {protocolMappers.map((mapper) => ( + + + + <>{mapper.name} + , + + <>{mapper.helpText} + , + ]} + /> + + + ))} + + )} + {buildIn && ( + { + rows[rowIndex].selected = isSelected; + setRows([...rows]); + }} + canSelectAll={false} + rows={rows} + aria-label={t("chooseAMapperType")} + > + + +
+ )} +
+ ); +}; diff --git a/src/client-scopes/add/__tests__/MapperDialog.test.tsx b/src/client-scopes/add/__tests__/MapperDialog.test.tsx new file mode 100644 index 0000000000..5fff4ba058 --- /dev/null +++ b/src/client-scopes/add/__tests__/MapperDialog.test.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { mount } from "enzyme"; +import { Button } from "@patternfly/react-core"; + +import serverInfo from "../../../context/server-info/__tests__/mock.json"; +import { ServerInfoContext } from "../../../context/server-info/ServerInfoProvider"; +import { AddMapperDialogProps, useAddMapperDialog } from "../MapperDialog"; + +describe("", () => { + const Test = (args: AddMapperDialogProps) => { + const [toggle, Dialog] = useAddMapperDialog(args); + return ( + + + + + ); + }; + + it("should return empty array when selecting nothing", () => { + const onConfirm = jest.fn(); + const container = mount( + + ); + + container.find("button#open").simulate("click"); + expect(container).toMatchSnapshot(); + + container.find("button#modal-confirm").simulate("click"); + expect(onConfirm).toBeCalledWith([]); + }); + + it("should return array with selected build in protocol mapping", () => { + const onConfirm = jest.fn(); + const protocol = "openid-connect"; + const container = mount( + + ); + + container.find("button#open").simulate("click"); + container + .find("input[name='checkrow0']") + .simulate("change", { target: { value: true } }); + container + .find("input[name='checkrow1']") + .simulate("change", { target: { value: true } }); + + container.find("button#modal-confirm").simulate("click"); + expect(onConfirm).toBeCalledWith([ + serverInfo.builtinProtocolMappers[protocol][0], + serverInfo.builtinProtocolMappers[protocol][1], + ]); + }); + + it("should return selected protocol mapping type on click", () => { + const onConfirm = jest.fn(); + const protocol = "openid-connect"; + const container = mount( + + ); + + container.find("button#open").simulate("click"); + expect(container).toMatchSnapshot(); + + container + .find("div.pf-c-data-list__item-content") + .first() + .simulate("click"); + expect(onConfirm).toBeCalledWith( + serverInfo.protocolMapperTypes[protocol][0] + ); + }); +}); diff --git a/src/client-scopes/add/__tests__/__snapshots__/MapperDialog.test.tsx.snap b/src/client-scopes/add/__tests__/__snapshots__/MapperDialog.test.tsx.snap new file mode 100644 index 0000000000..869ec0e135 --- /dev/null +++ b/src/client-scopes/add/__tests__/__snapshots__/MapperDialog.test.tsx.snap @@ -0,0 +1,19167 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should return empty array when selecting nothing 1`] = ` + + + + + add + , + , + ] + } + appendTo={[Function]} + aria-describedby="" + aria-label="" + aria-labelledby="" + className="" + hasNoBodyWrapper={false} + isOpen={true} + onClose={[Function]} + ouiaSafe={true} + showClose={true} + title="chooseAMapperType" + variant="default" + > + +
+
+ +
+
+ + } + > + + add + , + , + ] + } + aria-describedby="" + aria-label="" + aria-labelledby="" + boxId="pf-modal-part-1" + className="" + descriptorId="pf-modal-part-3" + hasNoBodyWrapper={false} + isOpen={true} + labelId="pf-modal-part-2" + onClose={[Function]} + ouiaId="OUIA-Generated-Modal-default-2" + ouiaSafe={true} + showClose={true} + title="chooseAMapperType" + variant="default" + > + +
+ +
+ + + +
+
+
+
+
+
+
+
+
+ + +
+`; + +exports[` should return selected protocol mapping type on click 1`] = ` + + + + + +
+
+ +
+
+ + } + > + + +
+ +
+ + + +
+
+
+
+
+
+
+
+
+ + +
+`; diff --git a/src/client-scopes/messages.json b/src/client-scopes/messages.json index 282522d27b..58a6d0ea14 100644 --- a/src/client-scopes/messages.json +++ b/src/client-scopes/messages.json @@ -30,6 +30,9 @@ "mappingDeletedError": "Could not delete mapping: '{{error}}'", "displayOnConsentScreen": "Display on consent screen", "consentScreenText": "Consent screen text", - "guiOrder": "Display Order" + "guiOrder": "Display Order", + "chooseAMapperType": "Choose a mapper type", + "predefinedMappingDescription": "Choose one of the predefined mappings from this table", + "mappingTable": "Table with predefined mapping" } } diff --git a/src/common-messages.json b/src/common-messages.json index 9add76911c..e10edbdb69 100644 --- a/src/common-messages.json +++ b/src/common-messages.json @@ -3,6 +3,7 @@ "fullName": "{{givenName}} {{familyName}}", "unknownUser": "Anonymous", + "add": "Add", "create": "Create", "save": "Save", "cancel": "Cancel", diff --git a/src/components/confirm-dialog/ConfirmDialog.tsx b/src/components/confirm-dialog/ConfirmDialog.tsx index d53090c49d..a3a1719200 100644 --- a/src/components/confirm-dialog/ConfirmDialog.tsx +++ b/src/components/confirm-dialog/ConfirmDialog.tsx @@ -38,6 +38,7 @@ export type ConfirmDialogProps = { cancelButtonLabel?: string; continueButtonLabel?: string; continueButtonVariant?: ButtonVariant; + variant?: ModalVariant; onConfirm: () => void; onCancel?: () => void; children?: ReactNode; @@ -53,6 +54,7 @@ export const ConfirmDialogModal = ({ onCancel, children, open = true, + variant = ModalVariant.default, toggleDialog, }: ConfirmDialogModalProps) => { const { t } = useTranslation(); @@ -61,7 +63,7 @@ export const ConfirmDialogModal = ({ title={t(titleKey)} isOpen={open} onClose={toggleDialog} - variant={ModalVariant.small} + variant={variant} actions={[ + + ); +}; + +export const BuildInDialog = Template.bind({}); +BuildInDialog.args = { + protocol: "openid-connect", + buildIn: true, +}; + +export const ProtocolMapperDialog = Template.bind({}); +ProtocolMapperDialog.args = { + protocol: "openid-connect", + buildIn: false, +};