Grouped multiple credentials (#1700)

* expandable rows - wip

* expandable rows - wip

* expandable rows - wip

* expandable rows - wip

* expandable rows - wip

* expandable rows - wip

* expandable rows - wip

* expandable rows - css improvements

* expandable rows - css improvements

* expandable rows - css improvements

* expandable rows - css improvements

* expandable rows - css improvements

* expandable rows - css improvements

* expandable rows - css improvements

* expandable rows - css improvements

* expandable rows - css improvements

* expandable rows - css improvements

* removed unnecessary css

* css cleanup

* feedback fixes

* table refactor

* table refactor

* table refactor

* table refactor

* table refactor

* small css fix

Co-authored-by: Agnieszka Gancarczyk <agancarc@redhat.com>
This commit is contained in:
agagancarczyk 2022-01-10 10:31:50 +00:00 committed by GitHub
parent 6c32d69e46
commit 1f45fb89aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 406 additions and 198 deletions

View file

@ -1,4 +1,4 @@
import React, { FunctionComponent, useMemo, useState } from "react";
import React, { Fragment, FunctionComponent, useMemo, useState } from "react";
import {
AlertVariant,
Button,
@ -27,7 +27,6 @@ import {
TableComposable,
TableHeader,
TableVariant,
Tbody,
Td,
Th,
Thead,
@ -44,7 +43,7 @@ import { useWhoAmI } from "../context/whoami/WhoAmI";
import { Controller, useForm, UseFormMethods, useWatch } from "react-hook-form";
import { PasswordInput } from "../components/password-input/PasswordInput";
import { HelpItem } from "../components/help-enabler/HelpItem";
import "./user-section.css";
import "./user-credentials.css";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import type CredentialRepresentation from "@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation";
import { FormAccess } from "../components/form-access/FormAccess";
@ -90,6 +89,12 @@ const userLabelDefaultValues: UserLabelForm = {
userLabel: "",
};
type ExpandableCredentialRepresentation = {
key: string;
value: CredentialRepresentation[];
isExpanded: boolean;
};
const DisplayDialog: FunctionComponent<DisplayDialogProps> = ({
titleKey,
onClose,
@ -241,6 +246,9 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => {
const [userCredentials, setUserCredentials] = useState<
CredentialRepresentation[]
>([]);
const [groupedUserCredentials, setGroupedUserCredentials] = useState<
ExpandableCredentialRepresentation[]
>([]);
const [selectedCredential, setSelectedCredential] =
useState<CredentialRepresentation>({});
const [isResetPassword, setIsResetPassword] = useState(false);
@ -256,6 +264,23 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => {
() => adminClient.users.getCredentials({ id: user.id! }),
(credentials) => {
setUserCredentials(credentials);
const groupedCredentials = credentials.reduce((r, a) => {
r[a.type!] = r[a.type!] || [];
r[a.type!].push(a);
return r;
}, Object.create(null));
const groupedCredentialsArray = Object.keys(groupedCredentials).map(
(key) => ({ key, value: groupedCredentials[key] })
);
setGroupedUserCredentials(
groupedCredentialsArray.map((groupedCredential) => ({
...groupedCredential,
isExpanded: false,
}))
);
},
[key]
);
@ -671,7 +696,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => {
<>
<Button
key={`confirmSaveBtn-table-${user.id}`}
className="setPasswordBtn-table"
className="kc-setPasswordBtn-tbl"
data-testid="setPasswordBtn-table"
variant="primary"
form="userCredentials-form"
@ -684,7 +709,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => {
<Divider />
</>
)}
{userCredentials.length !== 0 ? (
{groupedUserCredentials.length !== 0 ? (
<>
{user.email && (
<Button
@ -712,24 +737,49 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => {
<Th />
</Tr>
</Thead>
<Tbody>
{userCredentials.map((credential) => (
<Tr key={`table-${credential.id}`}>
<>
{groupedUserCredentials.map((groupedCredential, rowIndex) => (
<Fragment key={`table-${groupedCredential.key}`}>
<Tr>
{groupedCredential.value.length > 1 ? (
<Td
draggableRow={{
id: `draggable-row-${credential.id}`,
className="kc-expandRow-btn"
expand={{
rowIndex,
isExpanded: groupedCredential.isExpanded,
onToggle: (_, rowIndex) => {
const rows = groupedUserCredentials.map(
(credential, index) =>
index === rowIndex
? {
...credential,
isExpanded: !credential.isExpanded,
}
: credential
);
setGroupedUserCredentials(rows);
},
}}
/>
) : (
<Td />
)}
<Td
key={`table-item-${credential.id}`}
dataLabel={`columns-${credential.id}`}
key={`table-item-${groupedCredential.key}`}
dataLabel={`columns-${groupedCredential.key}`}
className="kc-notExpandableRow-credentialType"
>
{credential.type?.charAt(0).toUpperCase()! +
credential.type?.slice(1)}
{groupedCredential.key.charAt(0).toUpperCase()! +
groupedCredential.key.slice(1)}
</Td>
{groupedCredential.value.length <= 1 &&
groupedCredential.value.map((credential) => (
<>
<Td>
<FormAccess isHorizontal role="view-users">
<FormAccess
isHorizontal
role="view-users"
className="kc-form-userLabel"
>
<FormGroup
fieldId="kc-userLabel"
className="kc-userLabel-row"
@ -840,7 +890,8 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => {
/>
}
isOpen={
kebabOpen.status && kebabOpen.rowKey === credential.id
kebabOpen.status &&
kebabOpen.rowKey === credential.id
}
onSelect={() => {
setSelectedCredential(credential);
@ -864,9 +915,152 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => {
/>
</Td>
</>
))}
</Tr>
{groupedCredential.isExpanded &&
groupedCredential.value.map((credential) => (
<Tr key={`child-key-${credential.id}`}>
<Td />
<Td
key={`child-item-${credential.id}`}
dataLabel={`child-columns-${credential.id}`}
className="kc-expandableRow-credentialType"
>
{credential.type!.charAt(0).toUpperCase()! +
credential.type!.slice(1)}
</Td>
<Td>
<FormAccess
isHorizontal
role="view-users"
className="kc-form-userLabel"
>
<FormGroup
fieldId="kc-userLabel"
className="kc-userLabel-row"
>
<div className="kc-form-group-userLabel">
{isUserLabelEdit?.status &&
isUserLabelEdit.rowKey === credential.id ? (
<>
<TextInput
name="userLabel"
ref={register1()}
type="text"
className="kc-userLabel"
aria-label={t("userLabel")}
data-testid="user-label-fld"
/>
<div className="kc-userLabel-actionBtns">
<Button
key={`editUserLabel-accept-${credential.id}`}
variant="link"
className="kc-editUserLabel-acceptBtn"
onClick={() => {
handleSubmit1(saveUserLabel)();
setIsUserLabelEdit({
status: false,
rowKey: credential.id!,
});
}}
data-testid="editUserLabel-acceptBtn"
icon={<CheckIcon />}
/>
<Button
key={`editUserLabel-cancel-${credential.id}`}
variant="link"
className="kc-editUserLabel-cancelBtn"
onClick={() =>
setIsUserLabelEdit({
status: false,
rowKey: credential.id!,
})
}
data-testid="editUserLabel-cancelBtn"
icon={<TimesIcon />}
/>
</div>
</>
) : (
<>
{credential.userLabel ?? ""}
<Button
key={`editUserLabel-${credential.id}`}
variant="link"
className="kc-editUserLabel-btn"
onClick={() => {
setEditedUserCredential(credential);
setIsUserLabelEdit({
status: true,
rowKey: credential.id!,
});
}}
data-testid="editUserLabelBtn"
icon={<PencilAltIcon />}
/>
</>
)}
</div>
</FormGroup>
</FormAccess>
</Td>
<Td>
<Button
className="kc-showData-btn"
variant="link"
data-testid="showDataBtn"
onClick={() => {
setShowData(true);
setSelectedCredential(credential);
}}
>
{t("showDataBtn")}
</Button>
</Td>
<Td />
<Td>
<Dropdown
isPlain
position={DropdownPosition.right}
toggle={
<KebabToggle
onToggle={(status) =>
setKebabOpen({
status,
rowKey: credential.id!,
})
}
/>
}
isOpen={
kebabOpen.status &&
kebabOpen.rowKey === credential.id
}
onSelect={() => {
setSelectedCredential(credential);
}}
dropdownItems={[
<DropdownItem
key={`delete-dropdown-item-${credential.id}`}
data-testid="deleteDropdownItem"
component="button"
onClick={() => {
toggleDeleteDialog();
setKebabOpen({
status: false,
rowKey: credential.id!,
});
}}
>
{t("deleteBtn")}
</DropdownItem>,
]}
/>
</Td>
</Tr>
))}
</Tbody>
</Fragment>
))}
</TableComposable>
</>
) : (

View file

@ -0,0 +1,69 @@
.kc-edit-icon {
color: var(--pf-global--Color--200);
margin-left: 5px;
}
.kc-showData-btn {
padding-left: 0;
}
.kc-userLabel-row {
display: inline-block !important;
width: 100%;
}
.kc-form-group-userLabel, .kc-userLabel-actionBtns {
display: flex;
}
.kc-editUserLabel-btn, .kc-editUserLabel-cancelBtn {
color: var(--pf-global--Color--200) !important;
}
.kc-editUserLabel-btn {
padding-top: 0px;
}
.kc-editUserLabel-btn:hover {
filter: brightness(55%);
}
.kc-editUserLabel-acceptBtn {
padding-right: 8px;
}
.kc-editUserLabel-cancelBtn {
padding-left: 8px !important;
}
.pf-c-table.pf-m-compact tr:not(.pf-c-table__expandable-row)>:last-child {
overflow-wrap: anywhere;
}
.kc-setPasswordBtn-tbl {
margin: 25px 0 25px 25px;
}
.kc-form-userLabel {
max-height: 0px;
margin-bottom: 0px;
padding-bottom: 15px;;
}
.kc-notExpandableRow-credentialType {
padding: 15px 0px 15px 15px !important;
}
.kc-expandableRow-credentialType {
padding-left: 15px !important;
}
.kc-expandRow-btn {
vertical-align: middle;
}
.kc-temporaryPassword {
margin: 6px 0 10px 35px;
}

View file

@ -119,61 +119,6 @@ article.pf-c-card.pf-m-flat.kc-available-idps > div > div > h1 {
text-align: center;
}
.kc-temporaryPassword {
margin: 6px 0 10px 35px;
}
.keycloak__user-credentials__reset-form {
--pf-c-form--m-horizontal__group-label--md--GridColumnWidth: 13rem;
}
.kc-edit-icon {
color: var(--pf-global--Color--200);
margin-left: 5px;
}
.kc-showData-btn {
padding-left: 0;
}
.kc-userLabel-row {
display: inline-block !important;
width: 50%;
}
.kc-form-group-userLabel, .kc-userLabel-actionBtns {
display: flex;
}
.kc-editUserLabel-btn, .kc-editUserLabel-cancelBtn {
color: var(--pf-global--Color--200) !important;
}
.kc-editUserLabel-btn {
padding-top: 0px;
}
.kc-editUserLabel-btn:hover {
filter: brightness(55%);
}
.kc-editUserLabel-acceptBtn {
padding-right: 8px;
}
.kc-editUserLabel-cancelBtn {
padding-left: 8px !important;
}
.pf-c-table.pf-m-compact tr:not(.pf-c-table__expandable-row)>:last-child {
overflow-wrap: anywhere;
}
.setPasswordBtn-table {
margin: 25px 0 25px 25px;
}
.resetCredentialBtn-header {
margin: 10px 25px 10px 0;
float: right;
}