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:
parent
50920b3df2
commit
6ea4f88b5b
4 changed files with 73 additions and 28 deletions
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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={
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue