roles(associated roles+attributes): address UX feedback (#451)

* alphabetize and sort roles

* change delete to remove

* make col widths stationary

* remove duplicate identifier

* fix lint

* update attributes to match new design

* (-) button working

* remove log

* format

* add attribute wip

* fix attributes revert

* enable add button

* disable attributes button when last field empty

* add init field on role creation

* remove log stmts
This commit is contained in:
Eugenia 2021-03-29 07:35:13 -04:00 committed by GitHub
parent 50920b3df2
commit 6ea4f88b5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 28 deletions

View file

@ -65,7 +65,12 @@ export const AttributesForm = ({
const { t } = useTranslation("roles"); const { t } = useTranslation("roles");
const columns = ["Key", "Value"]; const columns = ["Key", "Value"];
const watchFirstKey = watch("attributes[0].key", "");
const watchLast = watch(`attributes[${fields.length - 1}].key`, "");
if (fields.length === 0) {
append({ key: "", value: "" });
}
return ( return (
<> <>
@ -137,7 +142,7 @@ export const AttributesForm = ({
)} )}
{rowIndex === fields.length - 1 && ( {rowIndex === fields.length - 1 && (
<Td key="add-button" id="add-button" dataLabel={columns[2]}> <Td key="add-button" id="add-button" dataLabel={columns[2]}>
{fields[rowIndex].key === "" && ( {fields.length !== 1 && (
<Button <Button
id={`minus-button-${rowIndex}`} id={`minus-button-${rowIndex}`}
aria-label={`remove ${attribute.key} with value ${attribute.value} `} aria-label={`remove ${attribute.key} with value ${attribute.value} `}
@ -148,6 +153,10 @@ export const AttributesForm = ({
<MinusCircleIcon /> <MinusCircleIcon />
</Button> </Button>
)} )}
</Td>
)}
</Tr>
))}
<Button <Button
aria-label={t("roles:addAttributeText")} aria-label={t("roles:addAttributeText")}
id="plus-icon" id="plus-icon"
@ -155,19 +164,25 @@ export const AttributesForm = ({
className="kc-attributes__plus-icon" className="kc-attributes__plus-icon"
onClick={() => append({ key: "", value: "" })} onClick={() => append({ key: "", value: "" })}
icon={<PlusCircleIcon />} icon={<PlusCircleIcon />}
isDisabled={!formState.isValid} isDisabled={!watchLast}
/> >
</Td> {t("roles:addAttributeText")}
)} </Button>
</Tr>
))}
</Tbody> </Tbody>
</TableComposable> </TableComposable>
<ActionGroup className="kc-attributes__action-group"> <ActionGroup className="kc-attributes__action-group">
<Button variant="primary" type="submit" isDisabled={!watchFirstKey}> <Button
variant="primary"
type="submit"
isDisabled={!formState.isDirty}
>
{t("common:save")} {t("common:save")}
</Button> </Button>
<Button onClick={reset} variant="link"> <Button
onClick={reset}
variant="link"
isDisabled={!formState.isDirty}
>
{t("common:revert")} {t("common:revert")}
</Button> </Button>
</ActionGroup> </ActionGroup>

View file

@ -56,7 +56,11 @@ function DataTable<T>({
} }
canSelectAll={canSelectAll} canSelectAll={canSelectAll}
cells={columns.map((column) => { cells={columns.map((column) => {
return { ...column, title: t(column.displayKey || column.name) }; return {
...column,
title: t(column.displayKey || column.name),
transforms: column.transforms,
};
})} })}
rows={rows} rows={rows}
actions={actions} actions={actions}

View file

@ -21,6 +21,8 @@ import { useAdminClient } from "../context/auth/AdminClient";
import { RoleFormType } from "./RealmRoleTabs"; import { RoleFormType } from "./RealmRoleTabs";
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
import { AliasRendererComponent } from "./AliasRendererComponent"; import { AliasRendererComponent } from "./AliasRendererComponent";
import _ from "lodash";
import { cellWidth } from "@patternfly/react-table";
type AssociatedRolesTabProps = { type AssociatedRolesTabProps = {
additionalRoles: RoleRepresentation[]; additionalRoles: RoleRepresentation[];
@ -83,12 +85,23 @@ export const AssociatedRolesTab = ({
return newRoles; return newRoles;
}; };
const loader = async () => { const alphabetize = (rolesList: RoleRepresentation[]) => {
return _.sortBy(rolesList, (role) => role.name?.toUpperCase());
};
const loader = async (first?: number, max?: number, search?: string) => {
if (isInheritedHidden) { if (isInheritedHidden) {
return additionalRoles; const filteredRoles = additionalRoles.filter(
(role) =>
!search ||
role.name?.toLowerCase().includes(search) ||
role.description?.toLowerCase().includes(search)
);
const roles = alphabetize(filteredRoles);
return roles;
} }
const allRoles: Promise<RoleRepresentation[]> = additionalRoles.reduce( const fetchedRoles: Promise<RoleRepresentation[]> = additionalRoles.reduce(
async (acc: Promise<RoleRepresentation[]>, role) => { async (acc: Promise<RoleRepresentation[]>, role) => {
const resolvedRoles = await acc; const resolvedRoles = await acc;
resolvedRoles.push(role); resolvedRoles.push(role);
@ -99,7 +112,16 @@ export const AssociatedRolesTab = ({
Promise.resolve([] as RoleRepresentation[]) Promise.resolve([] as RoleRepresentation[])
); );
return allRoles; return fetchedRoles.then((results: RoleRepresentation[]) => {
const filteredRoles = results.filter(
(role) =>
!search ||
role.name?.toLowerCase().includes(search) ||
role.description?.toLowerCase().includes(search)
);
const roles = alphabetize(filteredRoles);
return roles;
});
}; };
useEffect(() => { useEffect(() => {
@ -128,7 +150,7 @@ export const AssociatedRolesTab = ({
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
titleKey: "roles:roleRemoveAssociatedRoleConfirm", titleKey: "roles:roleRemoveAssociatedRoleConfirm",
messageKey: t("roles:roleRemoveAssociatedText"), messageKey: t("roles:roleRemoveAssociatedText"),
continueButtonLabel: "common:delete", continueButtonLabel: t("common:remove"),
continueButtonVariant: ButtonVariant.danger, continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => { onConfirm: async () => {
try { try {
@ -150,7 +172,7 @@ export const AssociatedRolesTab = ({
messageKey: t("roles:removeAllAssociatedRolesConfirmDialog", { messageKey: t("roles:removeAllAssociatedRolesConfirmDialog", {
name: parentRole?.name || t("createRole"), name: parentRole?.name || t("createRole"),
}), }),
continueButtonLabel: "common:delete", continueButtonLabel: "common:remove",
continueButtonVariant: ButtonVariant.danger, continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => { onConfirm: async () => {
try { try {
@ -239,17 +261,20 @@ export const AssociatedRolesTab = ({
displayKey: "roles:roleName", displayKey: "roles:roleName",
cellRenderer: AliasRenderer, cellRenderer: AliasRenderer,
cellFormatters: [formattedLinkTableCell(), emptyFormatter()], cellFormatters: [formattedLinkTableCell(), emptyFormatter()],
transforms: [cellWidth(40)],
}, },
{ {
name: "containerId", name: "containerId",
displayKey: "roles:inheritedFrom", displayKey: "roles:inheritedFrom",
cellRenderer: InheritedRoleName, cellRenderer: InheritedRoleName,
cellFormatters: [emptyFormatter()], cellFormatters: [emptyFormatter()],
transforms: [cellWidth(30)],
}, },
{ {
name: "description", name: "description",
displayKey: "common:description", displayKey: "common:description",
cellFormatters: [emptyFormatter()], cellFormatters: [emptyFormatter()],
transforms: [cellWidth(30)],
}, },
]} ]}
emptyState={ emptyState={

View file

@ -92,7 +92,7 @@ export const RealmRoleTabs = () => {
name: "attributes", name: "attributes",
}); });
useEffect(() => append({ key: "", value: "" }), [append, role]); //useEffect(() => append({ key: "", value: "" }), [append, role]);
const save = async () => { const save = async () => {
try { try {
@ -130,6 +130,7 @@ export const RealmRoleTabs = () => {
); );
setRole(role); setRole(role);
form.reset(role);
} else { } else {
let createdRole; let createdRole;
if (!clientId) { if (!clientId) {
@ -277,13 +278,6 @@ export const RealmRoleTabs = () => {
] ]
: id : id
? [ ? [
<DropdownItem
key="delete-role"
component="button"
onClick={() => toggleDeleteDialog()}
>
{t("deleteRole")}
</DropdownItem>,
<DropdownItem <DropdownItem
key="toggle-modal" key="toggle-modal"
data-testid="add-roles" data-testid="add-roles"
@ -292,6 +286,13 @@ export const RealmRoleTabs = () => {
> >
{t("addAssociatedRolesText")} {t("addAssociatedRolesText")}
</DropdownItem>, </DropdownItem>,
<DropdownItem
key="delete-role"
component="button"
onClick={() => toggleDeleteDialog()}
>
{t("deleteRole")}
</DropdownItem>,
] ]
: undefined : undefined
} }