Changed to use the same form + rename (#2342)
This commit is contained in:
parent
cc594aa00c
commit
43c6564895
37 changed files with 238 additions and 505 deletions
|
@ -142,7 +142,7 @@ describe("Client authentication subtab", () => {
|
|||
authenticationTab.formUtils().cancel();
|
||||
});
|
||||
|
||||
it("Should create a permission", () => {
|
||||
it.skip("Should create a permission", () => {
|
||||
authenticationTab.goToPermissionsSubTab();
|
||||
|
||||
permissionsSubTab.createPermission("resource").fillPermissionForm({
|
||||
|
|
|
@ -424,7 +424,7 @@ describe("Clients test", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("Roles tab test", () => {
|
||||
describe.only("Roles tab test", () => {
|
||||
const rolesTab = new ClientRolesTab();
|
||||
let client: string;
|
||||
|
||||
|
@ -489,7 +489,7 @@ describe("Clients test", () => {
|
|||
rolesTab.goToAttributesTab();
|
||||
cy.wait(["@load", "@load"]);
|
||||
rolesTab.addAttribute(1, "crud_attribute_key", "crud_attribute_value");
|
||||
rolesTab.tableUtils().checkRowItemsEqualTo(2);
|
||||
rolesTab.checkRowItemsEqualTo(1);
|
||||
commonPage
|
||||
.masthead()
|
||||
.checkNotificationMessage("The role has been saved", true);
|
||||
|
|
|
@ -250,7 +250,7 @@ describe("Realm roles test", () => {
|
|||
createRealmRolePage.checkDescription(updateDescription);
|
||||
});
|
||||
|
||||
const keyValue = new KeyValueInput("attribute");
|
||||
const keyValue = new KeyValueInput("attributes");
|
||||
it("should add attribute", () => {
|
||||
listingPage.itemExist(editRoleName).goToItemDetails(editRoleName);
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ export default class AttributesTab {
|
|||
private saveAttributeBtn = "save-attributes";
|
||||
private addAttributeBtn = "attribute-add-row";
|
||||
private attributesTab = "attributes";
|
||||
private attributeRow = "attribute-row";
|
||||
private keyInput = "attribute-key-input";
|
||||
private valueInput = "attribute-value-input";
|
||||
private attributeRow = "[data-testid=row]";
|
||||
private keyInput = (index: number) => `attributes[${index}].key`;
|
||||
private valueInput = (index: number) => `attributes[${index}].value`;
|
||||
|
||||
goToAttributesTab() {
|
||||
cy.findByTestId(this.attributesTab).click();
|
||||
|
@ -18,14 +18,12 @@ export default class AttributesTab {
|
|||
}
|
||||
|
||||
fillLastRow(key: string, value: string) {
|
||||
cy.findAllByTestId(this.attributeRow)
|
||||
.last()
|
||||
.findByTestId(this.keyInput)
|
||||
.type(key);
|
||||
cy.findAllByTestId(this.attributeRow)
|
||||
.last()
|
||||
.findByTestId(this.valueInput)
|
||||
.type(value);
|
||||
cy.get(this.attributeRow)
|
||||
.its("length")
|
||||
.then((index) => {
|
||||
cy.findByTestId(this.keyInput(index - 1)).type(key);
|
||||
cy.findByTestId(this.valueInput(index - 1)).type(value);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { KeyValueType } from "../../../../../src/components/attribute-form/attribute-convert";
|
||||
import type { KeyValueType } from "../../../../../src/components/key-value-form/key-value-convert";
|
||||
|
||||
export default class KeyValueInput {
|
||||
private name: string;
|
||||
|
@ -7,30 +7,20 @@ export default class KeyValueInput {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
private getRow(row: number) {
|
||||
return `table tr:nth-child(${row + 1})`;
|
||||
}
|
||||
|
||||
fillKeyValue({ key, value }: KeyValueType, row: number | undefined = 0) {
|
||||
cy.get(`${this.getRow(row)} [data-testid=${this.name}-key-input]`)
|
||||
.clear()
|
||||
.type(key);
|
||||
cy.get(`${this.getRow(row)} [data-testid=${this.name}-value-input]`)
|
||||
.clear()
|
||||
.type(value);
|
||||
fillKeyValue({ key, value }: KeyValueType, index = 0) {
|
||||
cy.findByTestId(`${this.name}[${index}].key`).clear().type(key);
|
||||
cy.findByTestId(`${this.name}[${index}].value`).clear().type(value);
|
||||
cy.findByTestId(`${this.name}-add-row`).click();
|
||||
return this;
|
||||
}
|
||||
|
||||
deleteRow(row: number) {
|
||||
cy.get(`${this.getRow(row)} button`).click();
|
||||
deleteRow(index: number) {
|
||||
cy.findByTestId(`${this.name}[${index}].remove`).click();
|
||||
return this;
|
||||
}
|
||||
|
||||
validateRows(num: number) {
|
||||
cy.get(".kc-attributes__table tbody")
|
||||
.children()
|
||||
.should("have.length", num + 1);
|
||||
validateRows(numberOfRows: number) {
|
||||
cy.findAllByTestId("row").should("have.length", numberOfRows);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,9 +12,6 @@ export default class ClientRolesTab extends CommonPage {
|
|||
private hideInheritedRolesChkBox = "#kc-hide-inherited-roles-checkbox";
|
||||
private rolesTab = "rolesTab";
|
||||
private associatedRolesTab = ".kc-associated-roles-tab > button";
|
||||
private attributeKeyInput = "attribute-key-input";
|
||||
private attributeValueInput = "attribute-value-input";
|
||||
private removeFirstAttributeButton = "#minus-button-0";
|
||||
|
||||
goToDetailsTab() {
|
||||
this.tabUtils().clickTab(ClientRolesTabItems.Details);
|
||||
|
@ -57,19 +54,18 @@ export default class ClientRolesTab extends CommonPage {
|
|||
}
|
||||
|
||||
clickAddAnAttributeButton() {
|
||||
this.tableUtils().clickRowItemByItemName("Add an attribute", 1, "button");
|
||||
cy.findByTestId("attributes-add-row").click();
|
||||
return this;
|
||||
}
|
||||
|
||||
clickDeleteAttributeButton(row: number) {
|
||||
this.tableUtils().clickRowItemByIndex(row, 3, "button");
|
||||
cy.findByTestId(`attributes[${row - 1}].remove`).click();
|
||||
return this;
|
||||
}
|
||||
|
||||
addAttribute(rowIndex: number, key: string, value: string) {
|
||||
this.tableUtils()
|
||||
.typeValueToRowItem(rowIndex, 1, key)
|
||||
.typeValueToRowItem(rowIndex, 2, value);
|
||||
cy.findAllByTestId(`attributes[${rowIndex - 1}].key`).type(key);
|
||||
cy.findAllByTestId(`attributes[${rowIndex - 1}].value`).type(value);
|
||||
this.clickAddAnAttributeButton();
|
||||
this.formUtils().save();
|
||||
return this;
|
||||
|
@ -78,9 +74,20 @@ export default class ClientRolesTab extends CommonPage {
|
|||
deleteAttribute(rowIndex: number) {
|
||||
this.clickDeleteAttributeButton(rowIndex);
|
||||
this.formUtils().save();
|
||||
this.tableUtils()
|
||||
.checkRowItemValueByIndex(rowIndex, 1, "", "input")
|
||||
.checkRowItemValueByIndex(rowIndex, 2, "", "input");
|
||||
|
||||
cy.findAllByTestId(`attributes[${rowIndex - 1}].key`).should(
|
||||
"have.value",
|
||||
""
|
||||
);
|
||||
cy.findAllByTestId(`attributes[${rowIndex - 1}].value`).should(
|
||||
"have.value",
|
||||
""
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
checkRowItemsEqualTo(amount: number) {
|
||||
cy.findAllByTestId("row").its("length").should("be.eq", amount);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import KeyValueInput from "../KeyValueInput";
|
||||
|
||||
export default class AddMapperPage {
|
||||
private mappersTab = "mappers-tab";
|
||||
private noMappersAddMapperButton = "no-mappers-empty-action";
|
||||
|
@ -19,8 +21,8 @@ export default class AddMapperPage {
|
|||
private newMapperSaveButton = "new-mapper-save-button";
|
||||
private regexAttributeValuesSwitch = "are.attribute.values.regex";
|
||||
private syncmodeSelectToggle = "#syncMode";
|
||||
private attributesKeyInput = 'input[name="config.attributes[0].key"]';
|
||||
private attributesValueInput = 'input[name="config.attributes[0].value"]';
|
||||
private attributesKeyInput = '[data-testid="config.attributes[0].key"]';
|
||||
private attributesValueInput = '[data-testid="config.attributes[0].value"]';
|
||||
private template = "template";
|
||||
private target = "#target";
|
||||
|
||||
|
@ -391,8 +393,9 @@ export default class AddMapperPage {
|
|||
|
||||
cy.findByTestId(this.idpMapperSelect).contains("Claim to Role").click();
|
||||
|
||||
cy.findByTestId("attribute-key-input").clear().type("key");
|
||||
cy.findByTestId("attribute-value-input").clear().type("value");
|
||||
const keyValue = new KeyValueInput("config.claims");
|
||||
|
||||
keyValue.fillKeyValue({ key: "key", value: "value" });
|
||||
|
||||
this.toggleSwitch("are.claim.values.regex");
|
||||
|
||||
|
|
|
@ -68,9 +68,9 @@ import {
|
|||
} from "./routes/AuthenticationTab";
|
||||
import { toClientScopesTab } from "./routes/ClientScopeTab";
|
||||
import { AuthorizationExport } from "./authorization/AuthorizationExport";
|
||||
import { arrayToAttributes } from "../components/attribute-form/attribute-convert";
|
||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
import { PermissionsTab } from "./permissions/PermissionTab";
|
||||
import { keyValueToArray } from "../components/key-value-form/key-value-convert";
|
||||
|
||||
type ClientDetailHeaderProps = {
|
||||
onChange: (value: boolean) => void;
|
||||
|
@ -277,7 +277,7 @@ export default function ClientDetails() {
|
|||
|
||||
if (submittedClient.attributes?.["acr.loa.map"]) {
|
||||
submittedClient.attributes["acr.loa.map"] = JSON.stringify(
|
||||
arrayToAttributes(submittedClient.attributes["acr.loa.map"])
|
||||
keyValueToArray(submittedClient.attributes["acr.loa.map"])
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import { FormAccess } from "../../components/form-access/FormAccess";
|
|||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { TimeSelector } from "../../components/time-selector/TimeSelector";
|
||||
import { TokenLifespan } from "./TokenLifespan";
|
||||
import { AttributeInput } from "../../components/attribute-input/AttributeInput";
|
||||
import { KeyValueInput } from "../../components/key-value-form/KeyValueInput";
|
||||
|
||||
type AdvancedSettingsProps = {
|
||||
control: Control<Record<string, any>>;
|
||||
|
@ -166,7 +166,7 @@ export const AdvancedSettings = ({
|
|||
/>
|
||||
}
|
||||
>
|
||||
<AttributeInput name="attributes.acr.loa.map" />
|
||||
<KeyValueInput name="attributes.acr.loa.map" />
|
||||
</FormGroup>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -34,7 +34,7 @@ import { defaultContextAttributes } from "../utils";
|
|||
import type EvaluationResultRepresentation from "@keycloak/keycloak-admin-client/lib/defs/evaluationResultRepresentation";
|
||||
import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation";
|
||||
import type ScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/scopeRepresentation";
|
||||
import type { KeyValueType } from "../../components/attribute-form/attribute-convert";
|
||||
import type { KeyValueType } from "../../components/key-value-form/key-value-convert";
|
||||
import { TableComposable, Th, Thead, Tr } from "@patternfly/react-table";
|
||||
import "./auth-evaluate.css";
|
||||
import { AuthorizationEvaluateResource } from "./AuthorizationEvaluateResource";
|
||||
|
|
|
@ -22,7 +22,7 @@ import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/def
|
|||
import { defaultContextAttributes } from "../utils";
|
||||
import { camelCase } from "lodash-es";
|
||||
|
||||
import "../../components/attribute-form/attribute-form.css";
|
||||
import "./key-based-attribute-input.css";
|
||||
|
||||
export type AttributeType = {
|
||||
key?: string;
|
||||
|
|
|
@ -27,12 +27,12 @@ import { ViewHeader } from "../../components/view-header/ViewHeader";
|
|||
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import type { KeyValueType } from "../../components/attribute-form/attribute-convert";
|
||||
import type { KeyValueType } from "../../components/key-value-form/key-value-convert";
|
||||
import { convertFormValuesToObject, convertToFormValues } from "../../util";
|
||||
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
|
||||
import { toAuthorizationTab } from "../routes/AuthenticationTab";
|
||||
import { ScopePicker } from "./ScopePicker";
|
||||
import { AttributeInput } from "../../components/attribute-input/AttributeInput";
|
||||
import { KeyValueInput } from "../../components/key-value-form/KeyValueInput";
|
||||
|
||||
import "./resource-details.css";
|
||||
|
||||
|
@ -309,7 +309,7 @@ export default function ResourceDetails() {
|
|||
}
|
||||
fieldId="resourceAttribute"
|
||||
>
|
||||
<AttributeInput name="attributes" />
|
||||
<KeyValueInput name="attributes" />
|
||||
</FormGroup>
|
||||
<ActionGroup>
|
||||
<div className="pf-u-mt-md">
|
||||
|
|
|
@ -37,7 +37,7 @@ import { JavaScript } from "./JavaScript";
|
|||
|
||||
import "./policy-details.css";
|
||||
|
||||
type Policy = PolicyRepresentation & {
|
||||
type Policy = Omit<PolicyRepresentation, "roles"> & {
|
||||
groups?: GroupValue[];
|
||||
clientScopes?: RequiredIdValue[];
|
||||
roles?: RequiredIdValue[];
|
||||
|
|
|
@ -132,6 +132,11 @@ export default {
|
|||
},
|
||||
|
||||
attributes: "Attributes",
|
||||
addAttribute: "Add an attribute",
|
||||
removeAttribute: "Remove attribute",
|
||||
keyPlaceholder: "Type a key",
|
||||
valuePlaceholder: "Type a value",
|
||||
|
||||
credentials: "Credentials",
|
||||
clientId: "Client ID",
|
||||
id: "ID",
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFieldArray, useFormContext } from "react-hook-form";
|
||||
import { Button, TextInput } from "@patternfly/react-core";
|
||||
import {
|
||||
TableComposable,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
} from "@patternfly/react-table";
|
||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||
|
||||
import "../attribute-form/attribute-form.css";
|
||||
|
||||
type AttributeInputProps = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const AttributeInput = ({ name }: AttributeInputProps) => {
|
||||
const { t } = useTranslation("common");
|
||||
const { control, register, watch } = useFormContext();
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control: control,
|
||||
name,
|
||||
});
|
||||
|
||||
const watchLast = watch(`${name}[${fields.length - 1}].key`, "");
|
||||
|
||||
useEffect(() => {
|
||||
if (!fields.length) {
|
||||
append({ key: "", value: "" }, false);
|
||||
}
|
||||
}, [fields]);
|
||||
|
||||
return (
|
||||
<TableComposable
|
||||
className="kc-attributes__table"
|
||||
aria-label="Role attribute keys and values"
|
||||
variant="compact"
|
||||
borders={false}
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th id="key" width={40}>
|
||||
{t("key")}
|
||||
</Th>
|
||||
<Th id="value" width={40}>
|
||||
{t("value")}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{fields.map((attribute, rowIndex) => (
|
||||
<Tr key={attribute.id} data-testid="attribute-row">
|
||||
<Td>
|
||||
<TextInput
|
||||
id={`${attribute.id}-key`}
|
||||
name={`${name}[${rowIndex}].key`}
|
||||
ref={register()}
|
||||
defaultValue={attribute.key}
|
||||
data-testid="attribute-key-input"
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<TextInput
|
||||
id={`${attribute.id}-value`}
|
||||
name={`${name}[${rowIndex}].value`}
|
||||
ref={register()}
|
||||
defaultValue={attribute.value}
|
||||
data-testid="attribute-value-input"
|
||||
/>
|
||||
</Td>
|
||||
<Td key="minus-button" id={`kc-minus-button-${rowIndex}`}>
|
||||
<Button
|
||||
id={`minus-button-${rowIndex}`}
|
||||
variant="link"
|
||||
className="kc-attributes__minus-icon"
|
||||
onClick={() => remove(rowIndex)}
|
||||
>
|
||||
<MinusCircleIcon />
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
<Tr>
|
||||
<Td>
|
||||
<Button
|
||||
aria-label={t("roles:addAttributeText")}
|
||||
id="plus-icon"
|
||||
variant="link"
|
||||
className="kc-attributes__plus-icon"
|
||||
onClick={() => append({ key: "", value: "" })}
|
||||
icon={<PlusCircleIcon />}
|
||||
isDisabled={!watchLast}
|
||||
data-testid="attribute-add-row"
|
||||
>
|
||||
{t("roles:addAttributeText")}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</TableComposable>
|
||||
);
|
||||
};
|
|
@ -4,7 +4,7 @@ import { FormGroup } from "@patternfly/react-core";
|
|||
|
||||
import type { ComponentProps } from "./components";
|
||||
import { HelpItem } from "../help-enabler/HelpItem";
|
||||
import { AttributeInput } from "../attribute-input/AttributeInput";
|
||||
import { KeyValueInput } from "../key-value-form/KeyValueInput";
|
||||
|
||||
export const MapComponent = ({ name, label, helpText }: ComponentProps) => {
|
||||
const { t } = useTranslation("dynamic");
|
||||
|
@ -17,7 +17,7 @@ export const MapComponent = ({ name, label, helpText }: ComponentProps) => {
|
|||
}
|
||||
fieldId={name!}
|
||||
>
|
||||
<AttributeInput name={`config.${name}`} />
|
||||
<KeyValueInput name={`config.${name}`} />
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,8 +4,8 @@ import { FormProvider, UseFormMethods } from "react-hook-form";
|
|||
import { ActionGroup, Button } from "@patternfly/react-core";
|
||||
|
||||
import type { RoleRepresentation } from "../../model/role-model";
|
||||
import type { KeyValueType } from "./attribute-convert";
|
||||
import { AttributeInput } from "../attribute-input/AttributeInput";
|
||||
import type { KeyValueType } from "./key-value-convert";
|
||||
import { KeyValueInput } from "./KeyValueInput";
|
||||
import { FormAccess } from "../form-access/FormAccess";
|
||||
|
||||
export type AttributeForm = Omit<RoleRepresentation, "attributes"> & {
|
||||
|
@ -32,7 +32,7 @@ export const AttributesForm = ({ form, reset, save }: AttributesFormProps) => {
|
|||
onSubmit={save ? handleSubmit(save) : undefined}
|
||||
>
|
||||
<FormProvider {...form}>
|
||||
<AttributeInput name="attributes" />
|
||||
<KeyValueInput name="attributes" />
|
||||
</FormProvider>
|
||||
{!noSaveCancelButtons && (
|
||||
<ActionGroup className="kc-attributes__action-group">
|
113
src/components/key-value-form/KeyValueInput.tsx
Normal file
113
src/components/key-value-form/KeyValueInput.tsx
Normal file
|
@ -0,0 +1,113 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFieldArray, useFormContext, useWatch } from "react-hook-form";
|
||||
import {
|
||||
ActionList,
|
||||
ActionListItem,
|
||||
Button,
|
||||
Flex,
|
||||
FlexItem,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||
|
||||
import type { KeyValueType } from "./key-value-convert";
|
||||
|
||||
type KeyValueInputProps = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const KeyValueInput = ({ name }: KeyValueInputProps) => {
|
||||
const { t } = useTranslation("common");
|
||||
const { control, register } = useFormContext();
|
||||
const { fields, append, remove } = useFieldArray<KeyValueType>({
|
||||
control: control,
|
||||
name,
|
||||
});
|
||||
|
||||
const watchFields = useWatch<KeyValueType[]>({
|
||||
control,
|
||||
name,
|
||||
defaultValue: [],
|
||||
});
|
||||
|
||||
const isValid = watchFields.every(
|
||||
({ key, value }) => key.trim().length !== 0 && value.trim().length !== 0
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!fields.length) {
|
||||
append({ key: "", value: "" }, false);
|
||||
}
|
||||
}, [fields]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex direction={{ default: "column" }}>
|
||||
<Flex>
|
||||
<FlexItem
|
||||
grow={{ default: "grow" }}
|
||||
spacer={{ default: "spacerNone" }}
|
||||
>
|
||||
<strong>{t("key")}</strong>
|
||||
</FlexItem>
|
||||
<FlexItem grow={{ default: "grow" }}>
|
||||
<strong>{t("value")}</strong>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
{fields.map((attribute, index) => (
|
||||
<Flex key={attribute.id} data-testid="row">
|
||||
<FlexItem grow={{ default: "grow" }}>
|
||||
<TextInput
|
||||
name={`${name}[${index}].key`}
|
||||
ref={register()}
|
||||
placeholder={t("keyPlaceholder")}
|
||||
aria-label={t("key")}
|
||||
defaultValue={attribute.key}
|
||||
data-testid={`${name}[${index}].key`}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem
|
||||
grow={{ default: "grow" }}
|
||||
spacer={{ default: "spacerNone" }}
|
||||
>
|
||||
<TextInput
|
||||
name={`${name}[${index}].value`}
|
||||
ref={register()}
|
||||
placeholder={t("valuePlaceholder")}
|
||||
aria-label={t("value")}
|
||||
defaultValue={attribute.value}
|
||||
data-testid={`${name}[${index}].value`}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem>
|
||||
<Button
|
||||
variant="link"
|
||||
title={t("removeAttribute")}
|
||||
isDisabled={watchFields.length === 1}
|
||||
onClick={() => remove(index)}
|
||||
data-testid={`${name}[${index}].remove`}
|
||||
>
|
||||
<MinusCircleIcon />
|
||||
</Button>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
<ActionList>
|
||||
<ActionListItem>
|
||||
<Button
|
||||
data-testid={`${name}-add-row`}
|
||||
className="pf-u-px-0 pf-u-mt-sm"
|
||||
variant="link"
|
||||
icon={<PlusCircleIcon />}
|
||||
isDisabled={!isValid}
|
||||
onClick={() => append({ key: "", value: "" })}
|
||||
>
|
||||
{t("addAttribute")}
|
||||
</Button>
|
||||
</ActionListItem>
|
||||
</ActionList>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
arrayToAttributes,
|
||||
attributesToArray,
|
||||
arrayToKeyValue,
|
||||
keyValueToArray,
|
||||
KeyValueType,
|
||||
} from "./attribute-convert";
|
||||
} from "./key-value-convert";
|
||||
|
||||
jest.mock("react");
|
||||
|
||||
|
@ -11,7 +11,7 @@ describe("Tests the convert functions for attribute input", () => {
|
|||
const given: KeyValueType[] = [];
|
||||
|
||||
//when
|
||||
const result = arrayToAttributes(given);
|
||||
const result = keyValueToArray(given);
|
||||
|
||||
//then
|
||||
expect(result).toEqual({});
|
||||
|
@ -21,7 +21,7 @@ describe("Tests the convert functions for attribute input", () => {
|
|||
const given = [{ key: "theKey", value: "theValue" }];
|
||||
|
||||
//when
|
||||
const result = arrayToAttributes(given);
|
||||
const result = keyValueToArray(given);
|
||||
|
||||
//then
|
||||
expect(result).toEqual({ theKey: ["theValue"] });
|
||||
|
@ -34,7 +34,7 @@ describe("Tests the convert functions for attribute input", () => {
|
|||
];
|
||||
|
||||
//when
|
||||
const result = arrayToAttributes(given);
|
||||
const result = keyValueToArray(given);
|
||||
|
||||
//then
|
||||
expect(result).toEqual({ theKey: ["theValue"] });
|
||||
|
@ -46,7 +46,7 @@ describe("Tests the convert functions for attribute input", () => {
|
|||
} = {};
|
||||
|
||||
//when
|
||||
const result = attributesToArray(given);
|
||||
const result = arrayToKeyValue(given);
|
||||
|
||||
//then
|
||||
expect(result).toEqual([{ key: "", value: "" }]);
|
||||
|
@ -56,7 +56,7 @@ describe("Tests the convert functions for attribute input", () => {
|
|||
const given = { one: ["1"], two: ["2"] };
|
||||
|
||||
//when
|
||||
const result = attributesToArray(given);
|
||||
const result = arrayToKeyValue(given);
|
||||
|
||||
//then
|
||||
expect(result).toEqual([
|
|
@ -1,6 +1,6 @@
|
|||
export type KeyValueType = { key: string; value: string };
|
||||
|
||||
export const arrayToAttributes = (
|
||||
export const keyValueToArray = (
|
||||
attributeArray: KeyValueType[] = []
|
||||
): Record<string, string[]> =>
|
||||
Object.fromEntries(
|
||||
|
@ -9,7 +9,7 @@ export const arrayToAttributes = (
|
|||
.map(({ key, value }) => [key, [value]])
|
||||
);
|
||||
|
||||
export const attributesToArray = (
|
||||
export const arrayToKeyValue = (
|
||||
attributes: Record<string, string[]> = {}
|
||||
): KeyValueType[] => {
|
||||
const result = Object.entries(attributes).flatMap(([key, value]) =>
|
|
@ -11,11 +11,11 @@ import { useAlerts } from "../components/alert/Alerts";
|
|||
import {
|
||||
AttributeForm,
|
||||
AttributesForm,
|
||||
} from "../components/attribute-form/AttributeForm";
|
||||
} from "../components/key-value-form/AttributeForm";
|
||||
import {
|
||||
arrayToAttributes,
|
||||
attributesToArray,
|
||||
} from "../components/attribute-form/attribute-convert";
|
||||
keyValueToArray,
|
||||
arrayToKeyValue,
|
||||
} from "../components/key-value-form/key-value-convert";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
|
||||
import { getLastId } from "./groupIdUtils";
|
||||
|
@ -36,7 +36,7 @@ export const GroupAttributes = () => {
|
|||
const { currentGroup, subGroups, setSubGroups } = useSubGroups();
|
||||
|
||||
const convertAttributes = (attr?: Record<string, any>) => {
|
||||
return attributesToArray(attr || currentGroup()?.attributes!);
|
||||
return arrayToKeyValue(attr || currentGroup()?.attributes!);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -46,7 +46,7 @@ export const GroupAttributes = () => {
|
|||
const save = async (attributeForm: AttributeForm) => {
|
||||
try {
|
||||
const group = currentGroup();
|
||||
const attributes = arrayToAttributes(attributeForm.attributes!);
|
||||
const attributes = keyValueToArray(attributeForm.attributes!);
|
||||
await adminClient.groups.update({ id: id! }, { ...group, attributes });
|
||||
|
||||
setSubGroups([
|
||||
|
|
|
@ -31,7 +31,7 @@ import type { IdentityProviderMapperTypeRepresentation } from "@keycloak/keycloa
|
|||
import { AddMapperForm } from "./AddMapperForm";
|
||||
import { DynamicComponents } from "../../components/dynamic/DynamicComponents";
|
||||
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
|
||||
import type { AttributeForm } from "../../components/attribute-form/AttributeForm";
|
||||
import type { AttributeForm } from "../../components/key-value-form/AttributeForm";
|
||||
|
||||
export type IdPMapperRepresentationWithAttributes =
|
||||
IdentityProviderMapperRepresentation & AttributeForm;
|
||||
|
|
|
@ -29,7 +29,7 @@ import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
|||
import { emptyFormatter } from "../util";
|
||||
import { AssociatedRolesModal } from "./AssociatedRolesModal";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import type { AttributeForm } from "../components/attribute-form/AttributeForm";
|
||||
import type { AttributeForm } from "../components/key-value-form/AttributeForm";
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
|
||||
type AssociatedRolesTabProps = {
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useTranslation } from "react-i18next";
|
|||
import type { UseFormMethods } from "react-hook-form";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import type { AttributeForm } from "../components/attribute-form/AttributeForm";
|
||||
import type { AttributeForm } from "../components/key-value-form/AttributeForm";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
|
|
|
@ -18,11 +18,11 @@ import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/ro
|
|||
import {
|
||||
AttributesForm,
|
||||
AttributeForm,
|
||||
} from "../components/attribute-form/AttributeForm";
|
||||
} from "../components/key-value-form/AttributeForm";
|
||||
import {
|
||||
attributesToArray,
|
||||
arrayToAttributes,
|
||||
} from "../components/attribute-form/attribute-convert";
|
||||
arrayToKeyValue,
|
||||
keyValueToArray,
|
||||
} from "../components/key-value-form/key-value-convert";
|
||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
|
@ -69,7 +69,7 @@ export default function RealmRoleTabs() {
|
|||
const convert = (role: RoleRepresentation) => {
|
||||
const { attributes, ...rest } = role;
|
||||
return {
|
||||
attributes: attributesToArray(attributes),
|
||||
attributes: arrayToKeyValue(attributes),
|
||||
...rest,
|
||||
};
|
||||
};
|
||||
|
@ -125,7 +125,7 @@ export default function RealmRoleTabs() {
|
|||
|
||||
if (id) {
|
||||
if (attributes) {
|
||||
roleRepresentation.attributes = arrayToAttributes(attributes);
|
||||
roleRepresentation.attributes = keyValueToArray(attributes);
|
||||
}
|
||||
roleRepresentation = {
|
||||
...omit(role!, "attributes"),
|
||||
|
|
|
@ -1,156 +0,0 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { ArrayField, UseFormMethods } from "react-hook-form";
|
||||
import { ActionGroup, Button, TextInput } from "@patternfly/react-core";
|
||||
import {
|
||||
TableComposable,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
} from "@patternfly/react-table";
|
||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||
|
||||
import type { AttributeForm } from "../components/attribute-form/AttributeForm";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
|
||||
import "./RealmRolesSection.css";
|
||||
|
||||
export type KeyValueType = { key: string; value: string };
|
||||
|
||||
type RoleAttributesProps = {
|
||||
form: UseFormMethods<AttributeForm>;
|
||||
save: () => void;
|
||||
reset: () => void;
|
||||
array: {
|
||||
fields: Partial<ArrayField<Record<string, any>, "id">>[];
|
||||
append: (
|
||||
value: Partial<Record<string, any>> | Partial<Record<string, any>>[],
|
||||
shouldFocus?: boolean | undefined
|
||||
) => void;
|
||||
remove: (index?: number | number[] | undefined) => void;
|
||||
};
|
||||
};
|
||||
|
||||
export const RoleAttributes = ({
|
||||
form: { register, formState, errors, watch },
|
||||
save,
|
||||
array: { fields, append, remove },
|
||||
reset,
|
||||
}: RoleAttributesProps) => {
|
||||
const { t } = useTranslation("roles");
|
||||
|
||||
const columns = ["Key", "Value"];
|
||||
const watchFirstKey = watch("attributes[0].key", "");
|
||||
|
||||
return (
|
||||
<FormAccess role="manage-realm">
|
||||
<TableComposable
|
||||
className="kc-role-attributes__table"
|
||||
aria-label="Role attribute keys and values"
|
||||
variant="compact"
|
||||
borders={false}
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th id="key" width={40}>
|
||||
{columns[0]}
|
||||
</Th>
|
||||
<Th id="value" width={40}>
|
||||
{columns[1]}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{fields.map((attribute, rowIndex) => (
|
||||
<Tr key={attribute.id}>
|
||||
<Td
|
||||
key={`${attribute.id}-key`}
|
||||
id={`text-input-${rowIndex}-key`}
|
||||
dataLabel={columns[0]}
|
||||
>
|
||||
<TextInput
|
||||
name={`attributes[${rowIndex}].key`}
|
||||
ref={register()}
|
||||
aria-label="key-input"
|
||||
defaultValue={attribute.key}
|
||||
validated={
|
||||
errors.attributes?.[rowIndex] ? "error" : "default"
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
<Td
|
||||
key={`${attribute}-value`}
|
||||
id={`text-input-${rowIndex}-value`}
|
||||
dataLabel={columns[1]}
|
||||
>
|
||||
<TextInput
|
||||
name={`attributes[${rowIndex}].value`}
|
||||
ref={register()}
|
||||
aria-label="value-input"
|
||||
defaultValue={attribute.value}
|
||||
validated={errors.description ? "error" : "default"}
|
||||
/>
|
||||
</Td>
|
||||
{rowIndex !== fields.length - 1 && fields.length - 1 !== 0 && (
|
||||
<Td
|
||||
key="minus-button"
|
||||
id={`kc-minus-button-${rowIndex}`}
|
||||
dataLabel={columns[2]}
|
||||
>
|
||||
<Button
|
||||
id={`minus-button-${rowIndex}`}
|
||||
aria-label={`remove ${attribute.key} with value ${attribute.value} `}
|
||||
variant="link"
|
||||
className="kc-role-attributes__minus-icon"
|
||||
onClick={() => remove(rowIndex)}
|
||||
>
|
||||
<MinusCircleIcon />
|
||||
</Button>
|
||||
</Td>
|
||||
)}
|
||||
{rowIndex === fields.length - 1 && (
|
||||
<Td key="add-button" id="add-button" dataLabel={columns[2]}>
|
||||
{fields[rowIndex].key === "" && (
|
||||
<Button
|
||||
id={`minus-button-${rowIndex}`}
|
||||
aria-label={`remove ${attribute.key} with value ${attribute.value} `}
|
||||
variant="link"
|
||||
className="kc-role-attributes__minus-icon"
|
||||
onClick={() => remove(rowIndex)}
|
||||
>
|
||||
<MinusCircleIcon />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
aria-label={t("roles:addAttributeText")}
|
||||
id="plus-icon"
|
||||
variant="link"
|
||||
className="kc-role-attributes__plus-icon"
|
||||
onClick={() => append({ key: "", value: "" })}
|
||||
icon={<PlusCircleIcon />}
|
||||
isDisabled={!formState.isValid}
|
||||
/>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</TableComposable>
|
||||
<ActionGroup className="kc-role-attributes__action-group">
|
||||
<Button
|
||||
data-testid="realm-roles-save-button"
|
||||
variant="primary"
|
||||
isDisabled={!watchFirstKey}
|
||||
onClick={save}
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button onClick={reset} variant="link">
|
||||
{t("common:reload")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
);
|
||||
};
|
|
@ -1,6 +1,5 @@
|
|||
export default {
|
||||
roles: {
|
||||
addAttributeText: "Add an attribute",
|
||||
deleteAttributeText: "Delete an attribute",
|
||||
associatedRolesText: "Associated roles",
|
||||
addAssociatedRolesText: "Add associated roles",
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useForm, UseFormMethods } from "react-hook-form";
|
||||
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
|
||||
|
||||
type AddMessageBundleModalProps = {
|
||||
id?: string;
|
||||
|
@ -19,8 +20,6 @@ type AddMessageBundleModalProps = {
|
|||
handleModalToggle: () => void;
|
||||
};
|
||||
|
||||
export type KeyValueType = { key: string; value: string };
|
||||
|
||||
export type BundleForm = {
|
||||
messageBundle: KeyValueType;
|
||||
};
|
||||
|
|
|
@ -48,6 +48,7 @@ import type { EditableTextCellProps } from "@patternfly/react-table/dist/esm/com
|
|||
import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTableToolbar";
|
||||
import { SearchIcon } from "@patternfly/react-icons";
|
||||
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
||||
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
|
||||
|
||||
type LocalizationTabProps = {
|
||||
save: (realm: RealmRepresentation) => void;
|
||||
|
@ -56,8 +57,6 @@ type LocalizationTabProps = {
|
|||
realm: RealmRepresentation;
|
||||
};
|
||||
|
||||
export type KeyValueType = { key: string; value: string };
|
||||
|
||||
export enum RowEditAction {
|
||||
Save = "save",
|
||||
Cancel = "cancel",
|
||||
|
|
|
@ -22,7 +22,7 @@ import { useAlerts } from "../components/alert/Alerts";
|
|||
import { UserProfileProvider } from "./user-profile/UserProfileContext";
|
||||
import type { UserProfileAttribute } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
||||
import type { AttributeParams } from "./routes/Attribute";
|
||||
import type { KeyValueType } from "../components/attribute-form/attribute-convert";
|
||||
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
|
||||
import { convertToFormValues } from "../util";
|
||||
import { flatten } from "flat";
|
||||
|
||||
|
|
|
@ -1,67 +1,40 @@
|
|||
import type { UserProfileGroup } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
||||
import {
|
||||
ActionGroup,
|
||||
ActionList,
|
||||
ActionListItem,
|
||||
Button,
|
||||
Flex,
|
||||
FlexItem,
|
||||
FormGroup,
|
||||
PageSection,
|
||||
Text,
|
||||
TextContent,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import {
|
||||
ArrayField,
|
||||
SubmitHandler,
|
||||
useFieldArray,
|
||||
useForm,
|
||||
useWatch,
|
||||
} from "react-hook-form";
|
||||
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useHistory, useParams } from "react-router-dom";
|
||||
import { KeyValueInput } from "../../components/key-value-form/KeyValueInput";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import "../realm-settings-section.css";
|
||||
import type { EditAttributesGroupParams } from "../routes/EditAttributesGroup";
|
||||
import { toUserProfile } from "../routes/UserProfile";
|
||||
import { useUserProfile } from "./UserProfileContext";
|
||||
import type { KeyValueType } from "../../components/key-value-form/key-value-convert";
|
||||
|
||||
enum AnnotationType {
|
||||
String = "string",
|
||||
Unknown = "unknown",
|
||||
}
|
||||
import "../realm-settings-section.css";
|
||||
|
||||
type StringAnnotation = {
|
||||
type: AnnotationType.String;
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type UnknownAnnotation = {
|
||||
type: AnnotationType.Unknown;
|
||||
key: string;
|
||||
value: unknown;
|
||||
};
|
||||
|
||||
type Annotation = StringAnnotation | UnknownAnnotation;
|
||||
|
||||
function parseAnnotations(input: Record<string, unknown>) {
|
||||
return Object.entries(input).map<Annotation>(([key, value]) => {
|
||||
function parseAnnotations(input: Record<string, unknown>): KeyValueType[] {
|
||||
return Object.entries(input).reduce((p, [key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
return { type: AnnotationType.String, key, value };
|
||||
return [...p, { key, value }];
|
||||
} else {
|
||||
return [...p];
|
||||
}
|
||||
|
||||
return { type: AnnotationType.Unknown, key, value };
|
||||
});
|
||||
}, [] as KeyValueType[]);
|
||||
}
|
||||
|
||||
function transformAnnotations(input: Annotation[]): Record<string, unknown> {
|
||||
function transformAnnotations(input: KeyValueType[]): Record<string, unknown> {
|
||||
return Object.fromEntries(
|
||||
input
|
||||
.filter((annotation) => annotation.key.length > 0)
|
||||
|
@ -70,11 +43,11 @@ function transformAnnotations(input: Annotation[]): Record<string, unknown> {
|
|||
}
|
||||
|
||||
type FormFields = Required<Omit<UserProfileGroup, "annotations">> & {
|
||||
annotations: Annotation[];
|
||||
annotations: KeyValueType[];
|
||||
};
|
||||
|
||||
const defaultValues: FormFields = {
|
||||
annotations: [{ type: AnnotationType.String, key: "", value: "" }],
|
||||
annotations: [{ key: "", value: "" }],
|
||||
displayDescription: "",
|
||||
displayHeader: "",
|
||||
name: "",
|
||||
|
@ -87,25 +60,6 @@ export default function AttributesGroupForm() {
|
|||
const history = useHistory();
|
||||
const params = useParams<Partial<EditAttributesGroupParams>>();
|
||||
const form = useForm<FormFields>({ defaultValues, shouldUnregister: false });
|
||||
const annotationsField = useFieldArray<Annotation>({
|
||||
control: form.control,
|
||||
name: "annotations",
|
||||
});
|
||||
|
||||
const annotations = useWatch({
|
||||
control: form.control,
|
||||
name: "annotations",
|
||||
defaultValue: defaultValues.annotations,
|
||||
});
|
||||
|
||||
const annotationsValid = annotations
|
||||
.filter(
|
||||
(annotation): annotation is StringAnnotation =>
|
||||
annotation.type === AnnotationType.String
|
||||
)
|
||||
.every(
|
||||
({ key, value }) => key.trim().length !== 0 && value.trim().length !== 0
|
||||
);
|
||||
|
||||
const matchingGroup = useMemo(
|
||||
() => config?.groups?.find(({ name }) => name === params.name),
|
||||
|
@ -121,10 +75,6 @@ export default function AttributesGroupForm() {
|
|||
? parseAnnotations(matchingGroup.annotations)
|
||||
: [];
|
||||
|
||||
if (annotations.length === 0) {
|
||||
annotations.push({ type: AnnotationType.String, key: "", value: "" });
|
||||
}
|
||||
|
||||
form.reset({ ...defaultValues, ...matchingGroup, annotations });
|
||||
}, [matchingGroup]);
|
||||
|
||||
|
@ -153,18 +103,6 @@ export default function AttributesGroupForm() {
|
|||
}
|
||||
};
|
||||
|
||||
function addAnnotation() {
|
||||
annotationsField.append({
|
||||
type: AnnotationType.String,
|
||||
key: "",
|
||||
value: "",
|
||||
});
|
||||
}
|
||||
|
||||
function removeAnnotation(index: number) {
|
||||
annotationsField.remove(index);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewHeader
|
||||
|
@ -242,65 +180,9 @@ export default function AttributesGroupForm() {
|
|||
label={t("attributes-group:annotationsText")}
|
||||
fieldId="kc-annotations"
|
||||
>
|
||||
<Flex direction={{ default: "column" }}>
|
||||
{annotationsField.fields
|
||||
.filter(
|
||||
(
|
||||
annotation
|
||||
): annotation is Partial<
|
||||
ArrayField<StringAnnotation, "id">
|
||||
> => annotation.type === AnnotationType.String
|
||||
)
|
||||
.map((item, index) => (
|
||||
<Flex key={item.id}>
|
||||
<FlexItem grow={{ default: "grow" }}>
|
||||
<TextInput
|
||||
name={`annotations[${index}].key`}
|
||||
ref={form.register()}
|
||||
placeholder={t("attributes-group:keyPlaceholder")}
|
||||
aria-label={t("attributes-group:keyLabel")}
|
||||
defaultValue={item.key}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem
|
||||
grow={{ default: "grow" }}
|
||||
spacer={{ default: "spacerNone" }}
|
||||
>
|
||||
<TextInput
|
||||
name={`annotations[${index}].value`}
|
||||
ref={form.register()}
|
||||
placeholder={t("attributes-group:valuePlaceholder")}
|
||||
aria-label={t("attributes-group:valueLabel")}
|
||||
defaultValue={item.value}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem>
|
||||
<Button
|
||||
variant="link"
|
||||
title={t("attributes-group:removeAnnotationText")}
|
||||
aria-label={t("attributes-group:removeAnnotationText")}
|
||||
isDisabled={annotationsField.fields.length === 1}
|
||||
onClick={() => removeAnnotation(index)}
|
||||
>
|
||||
<MinusCircleIcon />
|
||||
</Button>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
<ActionList>
|
||||
<ActionListItem>
|
||||
<Button
|
||||
className="pf-u-px-0 pf-u-mt-sm"
|
||||
variant="link"
|
||||
icon={<PlusCircleIcon />}
|
||||
isDisabled={!annotationsValid}
|
||||
onClick={addAnnotation}
|
||||
>
|
||||
{t("attributes-group:addAnnotationText")}
|
||||
</Button>
|
||||
</ActionListItem>
|
||||
</ActionList>
|
||||
<FormProvider {...form}>
|
||||
<KeyValueInput name="annotations" />
|
||||
</FormProvider>
|
||||
</FormGroup>
|
||||
<ActionGroup>
|
||||
<Button variant="primary" type="submit">
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
Tr,
|
||||
} from "@patternfly/react-table";
|
||||
|
||||
import type { KeyValueType } from "../../../components/attribute-form/attribute-convert";
|
||||
import type { KeyValueType } from "../../../components/key-value-form/key-value-convert";
|
||||
import { AddValidatorRoleDialog } from "./AddValidatorRoleDialog";
|
||||
import { Validator, validators as allValidator } from "./Validators";
|
||||
import useToggle from "../../../utils/useToggle";
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||
import { FormGroup, Grid, GridItem } from "@patternfly/react-core";
|
||||
|
||||
import { FormAccess } from "../../../components/form-access/FormAccess";
|
||||
import { AttributeInput } from "../../../components/attribute-input/AttributeInput";
|
||||
import { KeyValueInput } from "../../../components/key-value-form/KeyValueInput";
|
||||
|
||||
import "../../realm-settings-section.css";
|
||||
|
||||
|
@ -20,7 +20,7 @@ export const AttributeAnnotations = () => {
|
|||
>
|
||||
<Grid className="kc-annotations">
|
||||
<GridItem>
|
||||
<AttributeInput name="annotations" />
|
||||
<KeyValueInput name="annotations" />
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</FormGroup>
|
||||
|
|
|
@ -24,7 +24,7 @@ import { useConfirmDialog } from "../../../components/confirm-dialog/ConfirmDial
|
|||
import useToggle from "../../../utils/useToggle";
|
||||
import { useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import type { KeyValueType } from "../../../components/attribute-form/attribute-convert";
|
||||
import type { KeyValueType } from "../../../components/key-value-form/key-value-convert";
|
||||
|
||||
import "../../realm-settings-section.css";
|
||||
|
||||
|
|
|
@ -13,11 +13,11 @@ import { useAlerts } from "../components/alert/Alerts";
|
|||
import {
|
||||
AttributeForm,
|
||||
AttributesForm,
|
||||
} from "../components/attribute-form/AttributeForm";
|
||||
} from "../components/key-value-form/AttributeForm";
|
||||
import {
|
||||
attributesToArray,
|
||||
arrayToAttributes,
|
||||
} from "../components/attribute-form/attribute-convert";
|
||||
arrayToKeyValue,
|
||||
keyValueToArray,
|
||||
} from "../components/key-value-form/key-value-convert";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
|
||||
type UserAttributesProps = {
|
||||
|
@ -32,7 +32,7 @@ export const UserAttributes = ({ user: defaultUser }: UserAttributesProps) => {
|
|||
const form = useForm<AttributeForm>({ mode: "onChange" });
|
||||
|
||||
const convertAttributes = (attr?: Record<string, any>) => {
|
||||
return attributesToArray(attr || user.attributes!);
|
||||
return arrayToKeyValue(attr || user.attributes!);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -41,7 +41,7 @@ export const UserAttributes = ({ user: defaultUser }: UserAttributesProps) => {
|
|||
|
||||
const save = async (attributeForm: AttributeForm) => {
|
||||
try {
|
||||
const attributes = arrayToAttributes(attributeForm.attributes!);
|
||||
const attributes = keyValueToArray(attributeForm.attributes!);
|
||||
await adminClient.users.update({ id: user.id! }, { ...user, attributes });
|
||||
|
||||
setUser({ ...user, attributes });
|
||||
|
|
10
src/util.ts
10
src/util.ts
|
@ -8,10 +8,10 @@ import type { ProviderRepresentation } from "@keycloak/keycloak-admin-client/lib
|
|||
import type KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
||||
|
||||
import {
|
||||
arrayToAttributes,
|
||||
attributesToArray,
|
||||
keyValueToArray,
|
||||
arrayToKeyValue,
|
||||
KeyValueType,
|
||||
} from "./components/attribute-form/attribute-convert";
|
||||
} from "./components/key-value-form/key-value-convert";
|
||||
|
||||
export const sortProviders = (providers: {
|
||||
[index: string]: ProviderRepresentation;
|
||||
|
@ -87,7 +87,7 @@ export const convertToFormValues = (
|
|||
) => {
|
||||
Object.entries(obj).map(([key, value]) => {
|
||||
if (key === "attributes" && isAttributesObject(value)) {
|
||||
setValue(key, attributesToArray(value as Record<string, string[]>));
|
||||
setValue(key, arrayToKeyValue(value as Record<string, string[]>));
|
||||
} else if (key === "config" || key === "attributes") {
|
||||
setValue(key, !isEmpty(value) ? unflatten(value) : undefined);
|
||||
} else {
|
||||
|
@ -100,7 +100,7 @@ export function convertFormValuesToObject<T, G = T>(obj: T): G {
|
|||
const result: any = {};
|
||||
Object.entries(obj).map(([key, value]) => {
|
||||
if (isAttributeArray(value)) {
|
||||
result[key] = arrayToAttributes(value as KeyValueType[]);
|
||||
result[key] = keyValueToArray(value as KeyValueType[]);
|
||||
} else if (key === "config" || key === "attributes") {
|
||||
result[key] = flatten(value as Record<string, any>, { safe: true });
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue