Added attributes tab to users (#1383)
This commit is contained in:
parent
d3c20812f0
commit
3fa13259e0
8 changed files with 127 additions and 24 deletions
|
@ -1,6 +1,7 @@
|
||||||
import ListingPage from "../support/pages/admin_console/ListingPage";
|
import ListingPage from "../support/pages/admin_console/ListingPage";
|
||||||
import GroupModal from "../support/pages/admin_console/manage/groups/GroupModal";
|
import GroupModal from "../support/pages/admin_console/manage/groups/GroupModal";
|
||||||
import GroupDetailPage from "../support/pages/admin_console/manage/groups/GroupDetailPage";
|
import GroupDetailPage from "../support/pages/admin_console/manage/groups/GroupDetailPage";
|
||||||
|
import AttributesTab from "../support/pages/admin_console/manage//AttributesTab";
|
||||||
import MoveGroupModal from "../support/pages/admin_console/manage/groups/MoveGroupModal";
|
import MoveGroupModal from "../support/pages/admin_console/manage/groups/MoveGroupModal";
|
||||||
import { SearchGroupPage } from "../support/pages/admin_console/manage/groups/SearchGroup";
|
import { SearchGroupPage } from "../support/pages/admin_console/manage/groups/SearchGroup";
|
||||||
import Masthead from "../support/pages/admin_console/Masthead";
|
import Masthead from "../support/pages/admin_console/Masthead";
|
||||||
|
@ -21,6 +22,7 @@ describe("Group test", () => {
|
||||||
const searchGroupPage = new SearchGroupPage();
|
const searchGroupPage = new SearchGroupPage();
|
||||||
const moveGroupModal = new MoveGroupModal();
|
const moveGroupModal = new MoveGroupModal();
|
||||||
const modalUtils = new ModalUtils();
|
const modalUtils = new ModalUtils();
|
||||||
|
const attributesTab = new AttributesTab();
|
||||||
|
|
||||||
let groupName = "group";
|
let groupName = "group";
|
||||||
|
|
||||||
|
@ -205,8 +207,8 @@ describe("Group test", () => {
|
||||||
|
|
||||||
it("Attributes CRUD test", () => {
|
it("Attributes CRUD test", () => {
|
||||||
clickGroup(groups[0]);
|
clickGroup(groups[0]);
|
||||||
detailPage
|
attributesTab
|
||||||
.clickAttributesTab()
|
.goToAttributesTab()
|
||||||
.fillAttribute("key", "value")
|
.fillAttribute("key", "value")
|
||||||
.saveAttribute();
|
.saveAttribute();
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import CreateUserPage from "../support/pages/admin_console/manage/users/CreateUs
|
||||||
import Masthead from "../support/pages/admin_console/Masthead";
|
import Masthead from "../support/pages/admin_console/Masthead";
|
||||||
import ListingPage from "../support/pages/admin_console/ListingPage";
|
import ListingPage from "../support/pages/admin_console/ListingPage";
|
||||||
import UserDetailsPage from "../support/pages/admin_console/manage/users/UserDetailsPage";
|
import UserDetailsPage from "../support/pages/admin_console/manage/users/UserDetailsPage";
|
||||||
|
import AttributesTab from "../support/pages/admin_console/manage/AttributesTab";
|
||||||
import ModalUtils from "../support/util/ModalUtils";
|
import ModalUtils from "../support/util/ModalUtils";
|
||||||
import { keycloakBefore } from "../support/util/keycloak_before";
|
import { keycloakBefore } from "../support/util/keycloak_before";
|
||||||
import GroupModal from "../support/pages/admin_console/manage/groups/GroupModal";
|
import GroupModal from "../support/pages/admin_console/manage/groups/GroupModal";
|
||||||
|
@ -53,6 +54,7 @@ describe("Users test", () => {
|
||||||
const modalUtils = new ModalUtils();
|
const modalUtils = new ModalUtils();
|
||||||
const listingPage = new ListingPage();
|
const listingPage = new ListingPage();
|
||||||
const userDetailsPage = new UserDetailsPage();
|
const userDetailsPage = new UserDetailsPage();
|
||||||
|
const attributesTab = new AttributesTab();
|
||||||
|
|
||||||
let itemId = "user_crud";
|
let itemId = "user_crud";
|
||||||
|
|
||||||
|
@ -116,6 +118,19 @@ describe("Users test", () => {
|
||||||
listingPage.searchItem(itemId).itemExist(itemId);
|
listingPage.searchItem(itemId).itemExist(itemId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("User attributes test", () => {
|
||||||
|
listingPage.searchItem(itemId).itemExist(itemId);
|
||||||
|
|
||||||
|
listingPage.goToItemDetails(itemId);
|
||||||
|
|
||||||
|
attributesTab
|
||||||
|
.goToAttributesTab()
|
||||||
|
.fillAttribute("key", "value")
|
||||||
|
.saveAttribute();
|
||||||
|
|
||||||
|
masthead.checkNotificationMessage("The user has been saved");
|
||||||
|
});
|
||||||
|
|
||||||
it("Add user to groups test", () => {
|
it("Add user to groups test", () => {
|
||||||
// Go to user groups
|
// Go to user groups
|
||||||
listingPage.searchItem(itemId).itemExist(itemId);
|
listingPage.searchItem(itemId).itemExist(itemId);
|
||||||
|
|
22
cypress/support/pages/admin_console/manage/AttributesTab.ts
Normal file
22
cypress/support/pages/admin_console/manage/AttributesTab.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
export default class AttributesTab {
|
||||||
|
private keyInput = '[name="attributes[0].key"]';
|
||||||
|
private valueInput = '[name="attributes[0].value"]';
|
||||||
|
private saveAttributeBtn = "save-attributes";
|
||||||
|
private attributesTab = "attributes";
|
||||||
|
|
||||||
|
goToAttributesTab() {
|
||||||
|
cy.findByTestId(this.attributesTab).click();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fillAttribute(key: string, value: string) {
|
||||||
|
cy.get(this.keyInput).type(key).get(this.valueInput).type(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAttribute() {
|
||||||
|
cy.findByTestId(this.saveAttributeBtn).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,17 +3,12 @@ const expect = chai.expect;
|
||||||
export default class GroupDetailPage {
|
export default class GroupDetailPage {
|
||||||
private groupNamesColumn = '[data-label="Group name"] > a';
|
private groupNamesColumn = '[data-label="Group name"] > a';
|
||||||
private memberTab = "members";
|
private memberTab = "members";
|
||||||
private attributesTab = "attributes";
|
|
||||||
private memberNameColumn = 'tbody > tr > [data-label="Name"]';
|
private memberNameColumn = 'tbody > tr > [data-label="Name"]';
|
||||||
private includeSubGroupsCheck = "includeSubGroupsCheck";
|
private includeSubGroupsCheck = "includeSubGroupsCheck";
|
||||||
private addMembers = "addMember";
|
private addMembers = "addMember";
|
||||||
private addMember = "add";
|
private addMember = "add";
|
||||||
private memberUsernameColumn = 'tbody > tr > [data-label="Username"]';
|
private memberUsernameColumn = 'tbody > tr > [data-label="Username"]';
|
||||||
|
|
||||||
private keyInput = '[name="attributes[0].key"]';
|
|
||||||
private valueInput = '[name="attributes[0].value"]';
|
|
||||||
private saveAttributeBtn = ".pf-c-form__actions > .pf-m-primary";
|
|
||||||
|
|
||||||
checkListSubGroup(subGroups: string[]) {
|
checkListSubGroup(subGroups: string[]) {
|
||||||
cy.get(this.groupNamesColumn).should((groups) => {
|
cy.get(this.groupNamesColumn).should((groups) => {
|
||||||
expect(groups).to.have.length(subGroups.length);
|
expect(groups).to.have.length(subGroups.length);
|
||||||
|
@ -70,23 +65,8 @@ export default class GroupDetailPage {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
clickAttributesTab() {
|
|
||||||
cy.findByTestId(this.attributesTab).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
clickAddMembers() {
|
clickAddMembers() {
|
||||||
cy.findByTestId(this.addMembers).click();
|
cy.findByTestId(this.addMembers).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
fillAttribute(key: string, value: string) {
|
|
||||||
cy.get(this.keyInput).type(key).get(this.valueInput).type(value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveAttribute() {
|
|
||||||
cy.get(this.saveAttributeBtn).click();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,7 +192,12 @@ export const AttributesForm = ({
|
||||||
</TableComposable>
|
</TableComposable>
|
||||||
{!noSaveCancelButtons && (
|
{!noSaveCancelButtons && (
|
||||||
<ActionGroup className="kc-attributes__action-group">
|
<ActionGroup className="kc-attributes__action-group">
|
||||||
<Button variant="primary" type="submit" isDisabled={!watchLast}>
|
<Button
|
||||||
|
data-testid="save-attributes"
|
||||||
|
variant="primary"
|
||||||
|
type="submit"
|
||||||
|
isDisabled={!watchLast}
|
||||||
|
>
|
||||||
{t("common:save")}
|
{t("common:save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|
71
src/user/UserAttributes.tsx
Normal file
71
src/user/UserAttributes.tsx
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useFieldArray, useForm } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
AlertVariant,
|
||||||
|
PageSection,
|
||||||
|
PageSectionVariants,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||||
|
|
||||||
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
import {
|
||||||
|
arrayToAttributes,
|
||||||
|
AttributeForm,
|
||||||
|
AttributesForm,
|
||||||
|
attributesToArray,
|
||||||
|
} from "../components/attribute-form/AttributeForm";
|
||||||
|
import { useAdminClient } from "../context/auth/AdminClient";
|
||||||
|
|
||||||
|
type UserAttributesProps = {
|
||||||
|
user: UserRepresentation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UserAttributes = ({ user }: UserAttributesProps) => {
|
||||||
|
const { t } = useTranslation("users");
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { addAlert, addError } = useAlerts();
|
||||||
|
const form = useForm<AttributeForm>({ mode: "onChange" });
|
||||||
|
const { fields, append, remove } = useFieldArray({
|
||||||
|
control: form.control,
|
||||||
|
name: "attributes",
|
||||||
|
});
|
||||||
|
|
||||||
|
const convertAttributes = (attr?: Record<string, any>) => {
|
||||||
|
const attributes = attributesToArray(attr || user.attributes!);
|
||||||
|
attributes.push({ key: "", value: "" });
|
||||||
|
return attributes;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setValue("attributes", convertAttributes());
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const save = async (attributeForm: AttributeForm) => {
|
||||||
|
try {
|
||||||
|
const attributes = arrayToAttributes(attributeForm.attributes);
|
||||||
|
await adminClient.users.update({ id: user.id! }, { ...user, attributes });
|
||||||
|
|
||||||
|
form.setValue("attributes", convertAttributes(attributes));
|
||||||
|
addAlert(t("userSaved"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addError("groups:groupUpdateError", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageSection variant={PageSectionVariants.light}>
|
||||||
|
<AttributesForm
|
||||||
|
form={form}
|
||||||
|
save={save}
|
||||||
|
array={{ fields, append, remove }}
|
||||||
|
reset={() =>
|
||||||
|
form.reset({
|
||||||
|
attributes: convertAttributes(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
};
|
|
@ -26,6 +26,7 @@ import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import { toUser } from "./routes/User";
|
import { toUser } from "./routes/User";
|
||||||
import { toUsers } from "./routes/Users";
|
import { toUsers } from "./routes/Users";
|
||||||
import { UserRoleMapping } from "./UserRoleMapping";
|
import { UserRoleMapping } from "./UserRoleMapping";
|
||||||
|
import { UserAttributes } from "./UserAttributes";
|
||||||
|
|
||||||
export const UsersTabs = () => {
|
export const UsersTabs = () => {
|
||||||
const { t } = useTranslation("users");
|
const { t } = useTranslation("users");
|
||||||
|
@ -175,6 +176,13 @@ export const UsersTabs = () => {
|
||||||
)}
|
)}
|
||||||
</PageSection>
|
</PageSection>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
eventKey="attributes"
|
||||||
|
data-testid="attributes"
|
||||||
|
title={<TabTitleText>{t("common:attributes")}</TabTitleText>}
|
||||||
|
>
|
||||||
|
<UserAttributes user={user} />
|
||||||
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
eventKey="groups"
|
eventKey="groups"
|
||||||
data-testid="user-groups-tab"
|
data-testid="user-groups-tab"
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { generatePath } from "react-router-dom";
|
||||||
import type { RouteDef } from "../../route-config";
|
import type { RouteDef } from "../../route-config";
|
||||||
import { UsersTabs } from "../UsersTabs";
|
import { UsersTabs } from "../UsersTabs";
|
||||||
|
|
||||||
export type UserTab = "settings" | "groups" | "consents";
|
export type UserTab = "settings" | "groups" | "consents" | "attributes";
|
||||||
|
|
||||||
export type UserParams = {
|
export type UserParams = {
|
||||||
realm: string;
|
realm: string;
|
||||||
|
|
Loading…
Reference in a new issue