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
This commit is contained in:
parent
49284a0f11
commit
a9dc031fff
8 changed files with 19464 additions and 11 deletions
167
src/client-scopes/add/MapperDialog.tsx
Normal file
167
src/client-scopes/add/MapperDialog.tsx
Normal file
|
@ -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 = () => (
|
||||||
|
<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 [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 (
|
||||||
|
<Modal
|
||||||
|
title={t("chooseAMapperType")}
|
||||||
|
isOpen={open}
|
||||||
|
onClose={toggleDialog}
|
||||||
|
actions={
|
||||||
|
buildIn
|
||||||
|
? [
|
||||||
|
<Button
|
||||||
|
id="modal-confirm"
|
||||||
|
key="confirm"
|
||||||
|
onClick={() => {
|
||||||
|
onConfirm(
|
||||||
|
rows.filter((row) => row.selected).map((row) => row.item)
|
||||||
|
);
|
||||||
|
toggleDialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("common:add")}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
id="modal-cancel"
|
||||||
|
key="cancel"
|
||||||
|
variant={ButtonVariant.secondary}
|
||||||
|
onClick={() => {
|
||||||
|
toggleDialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("common:cancel")}
|
||||||
|
</Button>,
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TextContent>
|
||||||
|
<Text>{t("predefinedMappingDescription")}</Text>
|
||||||
|
</TextContent>
|
||||||
|
{!buildIn && (
|
||||||
|
<DataList
|
||||||
|
onSelectDataListItem={(id) => {
|
||||||
|
const mapper = protocolMappers.find((mapper) => mapper.id === id);
|
||||||
|
onConfirm(mapper!);
|
||||||
|
toggleDialog();
|
||||||
|
}}
|
||||||
|
aria-label={t("chooseAMapperType")}
|
||||||
|
isCompact
|
||||||
|
>
|
||||||
|
{protocolMappers.map((mapper) => (
|
||||||
|
<DataListItem
|
||||||
|
aria-labelledby={mapper.name}
|
||||||
|
key={mapper.id}
|
||||||
|
id={mapper.id}
|
||||||
|
>
|
||||||
|
<DataListItemRow>
|
||||||
|
<DataListItemCells
|
||||||
|
dataListCells={[
|
||||||
|
<DataListCell key={`name-${mapper.id}`}>
|
||||||
|
<>{mapper.name}</>
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell key={`helpText-${mapper.id}`}>
|
||||||
|
<>{mapper.helpText}</>
|
||||||
|
</DataListCell>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</DataListItemRow>
|
||||||
|
</DataListItem>
|
||||||
|
))}
|
||||||
|
</DataList>
|
||||||
|
)}
|
||||||
|
{buildIn && (
|
||||||
|
<Table
|
||||||
|
variant={TableVariant.compact}
|
||||||
|
cells={[t("name"), t("description")]}
|
||||||
|
onSelect={(_, isSelected, rowIndex) => {
|
||||||
|
rows[rowIndex].selected = isSelected;
|
||||||
|
setRows([...rows]);
|
||||||
|
}}
|
||||||
|
canSelectAll={false}
|
||||||
|
rows={rows}
|
||||||
|
aria-label={t("chooseAMapperType")}
|
||||||
|
>
|
||||||
|
<TableHeader />
|
||||||
|
<TableBody />
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
75
src/client-scopes/add/__tests__/MapperDialog.test.tsx
Normal file
75
src/client-scopes/add/__tests__/MapperDialog.test.tsx
Normal file
|
@ -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("<MapperDialog/>", () => {
|
||||||
|
const Test = (args: AddMapperDialogProps) => {
|
||||||
|
const [toggle, Dialog] = useAddMapperDialog(args);
|
||||||
|
return (
|
||||||
|
<ServerInfoContext.Provider value={serverInfo}>
|
||||||
|
<Dialog />
|
||||||
|
<Button id="open" onClick={toggle}>
|
||||||
|
Show
|
||||||
|
</Button>
|
||||||
|
</ServerInfoContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should return empty array when selecting nothing", () => {
|
||||||
|
const onConfirm = jest.fn();
|
||||||
|
const container = mount(
|
||||||
|
<Test buildIn={true} protocol="openid-connect" onConfirm={onConfirm} />
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
<Test buildIn={true} protocol={protocol} onConfirm={onConfirm} />
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
<Test buildIn={false} protocol={protocol} onConfirm={onConfirm} />
|
||||||
|
);
|
||||||
|
|
||||||
|
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]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
19167
src/client-scopes/add/__tests__/__snapshots__/MapperDialog.test.tsx.snap
Normal file
19167
src/client-scopes/add/__tests__/__snapshots__/MapperDialog.test.tsx.snap
Normal file
File diff suppressed because it is too large
Load diff
|
@ -30,6 +30,9 @@
|
||||||
"mappingDeletedError": "Could not delete mapping: '{{error}}'",
|
"mappingDeletedError": "Could not delete mapping: '{{error}}'",
|
||||||
"displayOnConsentScreen": "Display on consent screen",
|
"displayOnConsentScreen": "Display on consent screen",
|
||||||
"consentScreenText": "Consent screen text",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"fullName": "{{givenName}} {{familyName}}",
|
"fullName": "{{givenName}} {{familyName}}",
|
||||||
"unknownUser": "Anonymous",
|
"unknownUser": "Anonymous",
|
||||||
|
|
||||||
|
"add": "Add",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
|
|
@ -38,6 +38,7 @@ export type ConfirmDialogProps = {
|
||||||
cancelButtonLabel?: string;
|
cancelButtonLabel?: string;
|
||||||
continueButtonLabel?: string;
|
continueButtonLabel?: string;
|
||||||
continueButtonVariant?: ButtonVariant;
|
continueButtonVariant?: ButtonVariant;
|
||||||
|
variant?: ModalVariant;
|
||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
@ -53,6 +54,7 @@ export const ConfirmDialogModal = ({
|
||||||
onCancel,
|
onCancel,
|
||||||
children,
|
children,
|
||||||
open = true,
|
open = true,
|
||||||
|
variant = ModalVariant.default,
|
||||||
toggleDialog,
|
toggleDialog,
|
||||||
}: ConfirmDialogModalProps) => {
|
}: ConfirmDialogModalProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -61,7 +63,7 @@ export const ConfirmDialogModal = ({
|
||||||
title={t(titleKey)}
|
title={t(titleKey)}
|
||||||
isOpen={open}
|
isOpen={open}
|
||||||
onClose={toggleDialog}
|
onClose={toggleDialog}
|
||||||
variant={ModalVariant.small}
|
variant={variant}
|
||||||
actions={[
|
actions={[
|
||||||
<Button
|
<Button
|
||||||
id="modal-confirm"
|
id="modal-confirm"
|
||||||
|
|
|
@ -48,7 +48,7 @@ exports[`Confirmation dialog renders simple confirm dialog 1`] = `
|
||||||
ouiaSafe={true}
|
ouiaSafe={true}
|
||||||
showClose={true}
|
showClose={true}
|
||||||
title="Delete app02?"
|
title="Delete app02?"
|
||||||
variant="small"
|
variant="default"
|
||||||
>
|
>
|
||||||
<Portal
|
<Portal
|
||||||
containerInfo={
|
containerInfo={
|
||||||
|
@ -63,8 +63,8 @@ exports[`Confirmation dialog renders simple confirm dialog 1`] = `
|
||||||
aria-describedby="pf-modal-part-3"
|
aria-describedby="pf-modal-part-3"
|
||||||
aria-labelledby="pf-modal-part-2"
|
aria-labelledby="pf-modal-part-2"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
class="pf-c-modal-box pf-m-sm"
|
class="pf-c-modal-box"
|
||||||
data-ouia-component-id="OUIA-Generated-Modal-small-2"
|
data-ouia-component-id="OUIA-Generated-Modal-default-2"
|
||||||
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-1"
|
||||||
|
@ -170,11 +170,11 @@ exports[`Confirmation dialog renders simple confirm dialog 1`] = `
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
labelId="pf-modal-part-2"
|
labelId="pf-modal-part-2"
|
||||||
onClose={[Function]}
|
onClose={[Function]}
|
||||||
ouiaId="OUIA-Generated-Modal-small-2"
|
ouiaId="OUIA-Generated-Modal-default-2"
|
||||||
ouiaSafe={true}
|
ouiaSafe={true}
|
||||||
showClose={true}
|
showClose={true}
|
||||||
title="Delete app02?"
|
title="Delete app02?"
|
||||||
variant="small"
|
variant="default"
|
||||||
>
|
>
|
||||||
<Backdrop>
|
<Backdrop>
|
||||||
<div
|
<div
|
||||||
|
@ -198,20 +198,20 @@ exports[`Confirmation dialog renders simple confirm dialog 1`] = `
|
||||||
aria-label=""
|
aria-label=""
|
||||||
aria-labelledby="pf-modal-part-2"
|
aria-labelledby="pf-modal-part-2"
|
||||||
className=""
|
className=""
|
||||||
data-ouia-component-id="OUIA-Generated-Modal-small-2"
|
data-ouia-component-id="OUIA-Generated-Modal-default-2"
|
||||||
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-1"
|
||||||
style={Object {}}
|
style={Object {}}
|
||||||
variant="small"
|
variant="default"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-describedby="pf-modal-part-3"
|
aria-describedby="pf-modal-part-3"
|
||||||
aria-label={null}
|
aria-label={null}
|
||||||
aria-labelledby="pf-modal-part-2"
|
aria-labelledby="pf-modal-part-2"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
className="pf-c-modal-box pf-m-sm"
|
className="pf-c-modal-box"
|
||||||
data-ouia-component-id="OUIA-Generated-Modal-small-2"
|
data-ouia-component-id="OUIA-Generated-Modal-default-2"
|
||||||
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-1"
|
||||||
|
|
38
src/stories/AddMapperDialog.stories.tsx
Normal file
38
src/stories/AddMapperDialog.stories.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Button } from "@patternfly/react-core";
|
||||||
|
import { Meta, Story } from "@storybook/react";
|
||||||
|
|
||||||
|
import serverInfo from "../context/server-info/__tests__/mock.json";
|
||||||
|
import { ServerInfoContext } from "../context/server-info/ServerInfoProvider";
|
||||||
|
import {
|
||||||
|
AddMapperDialog,
|
||||||
|
AddMapperDialogProps,
|
||||||
|
useAddMapperDialog,
|
||||||
|
} from "../client-scopes/add/MapperDialog";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Add mapper dialog",
|
||||||
|
component: AddMapperDialog,
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
const Template: Story<AddMapperDialogProps> = (args) => {
|
||||||
|
const [toggle, Dialog] = useAddMapperDialog(args);
|
||||||
|
return (
|
||||||
|
<ServerInfoContext.Provider value={serverInfo}>
|
||||||
|
<Dialog />
|
||||||
|
<Button onClick={toggle}>Show</Button>
|
||||||
|
</ServerInfoContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BuildInDialog = Template.bind({});
|
||||||
|
BuildInDialog.args = {
|
||||||
|
protocol: "openid-connect",
|
||||||
|
buildIn: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProtocolMapperDialog = Template.bind({});
|
||||||
|
ProtocolMapperDialog.args = {
|
||||||
|
protocol: "openid-connect",
|
||||||
|
buildIn: false,
|
||||||
|
};
|
Loading…
Reference in a new issue