Skip to content

feat: improve custom roles create/edit page #14456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 28, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add parent checkbox for grouped resource permissions
  • Loading branch information
jaaydenh committed Aug 27, 2024
commit 800a49ee6f23ed3469162b909db82cd2a302e2ad
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,6 @@ export const CreateEditRolePageView: FC<CreateEditRolePageViewProps> = ({
);
};

interface ActionCheckboxesProps {
permissions: readonly Permission[] | undefined;
form: ReturnType<typeof useFormik<Role>> & { values: Role };
allResources: boolean;
}

const ResourceActionComparator = (
p: Permission,
resource: string,
Expand All @@ -160,6 +154,7 @@ const ResourceActionComparator = (
p.resource_type === resource &&
(p.action.toString() === "*" || p.action === action);

// the subset of resources that are useful for most users
const DEFAULT_RESOURCES = [
"audit_log",
"group",
Expand All @@ -177,6 +172,12 @@ const filteredRBACResourceActions = Object.fromEntries(
),
);

interface ActionCheckboxesProps {
permissions: readonly Permission[];
form: ReturnType<typeof useFormik<Role>> & { values: Role };
allResources: boolean;
}

const ActionCheckboxes: FC<ActionCheckboxesProps> = ({
permissions,
form,
Expand All @@ -185,6 +186,10 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({
const [checkedActions, setCheckActions] = useState(permissions);
const [showAllResources, setShowAllResources] = useState(allResources);

const resourceActions = showAllResources
? RBACResourceActions
: filteredRBACResourceActions;

const handleActionCheckChange = async (
e: ChangeEvent<HTMLInputElement>,
form: ReturnType<typeof useFormik<Role>> & { values: Role },
Expand All @@ -194,7 +199,7 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({

const newPermissions = checked
? [
...(checkedActions ?? []),
...checkedActions,
{
negate: false,
resource_type: resource_type as RBACResource,
Expand All @@ -209,9 +214,36 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({
await form.setFieldValue("organization_permissions", newPermissions);
};

const resourceActions = showAllResources
? RBACResourceActions
: filteredRBACResourceActions;
const handleResourceCheckChange = async (
e: ChangeEvent<HTMLInputElement>,
form: ReturnType<typeof useFormik<Role>> & { values: Role },
indeterminate: boolean,
) => {
const { name, checked } = e.currentTarget;
const resource = name as RBACResource;

const resourceActionsForResource = resourceActions[resource] || {};

const newCheckedActions =
!checked || indeterminate
? checkedActions?.filter((p) => p.resource_type !== resource)
: checkedActions;

const newPermissions =
checked || indeterminate
? [
...newCheckedActions,
...Object.keys(resourceActionsForResource).map((resourceKey) => ({
negate: false,
resource_type: resource as RBACResource,
action: resourceKey as RBACAction,
})),
]
: [...newCheckedActions];

setCheckActions(newPermissions);
await form.setFieldValue("organization_permissions", newPermissions);
};

return (
<TableContainer>
Expand All @@ -233,36 +265,17 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({
<TableBody>
{Object.entries(resourceActions).map(([resourceKey, value]) => {
return (
<TableRow key={resourceKey}>
<TableCell sx={{ paddingLeft: 2 }} colSpan={2}>
<li key={resourceKey} css={styles.checkBoxes}>
{resourceKey}
<ul css={styles.checkBoxes}>
{Object.entries(value).map(([actionKey, value]) => (
<li key={actionKey}>
<span css={styles.actionText}>
<Checkbox
size="small"
name={`${resourceKey}:${actionKey}`}
checked={checkedActions?.some((p) =>
ResourceActionComparator(
p,
resourceKey,
actionKey,
),
)}
onChange={(e) => handleActionCheckChange(e, form)}
/>
{actionKey}
</span>{" "}
&ndash;{" "}
<span css={styles.actionDescription}>{value}</span>
</li>
))}
</ul>
</li>
</TableCell>
</TableRow>
<PermissionCheckboxGroup
key={resourceKey}
checkedActions={checkedActions?.filter(
(a) => a.resource_type === resourceKey,
)}
resourceKey={resourceKey}
value={value}
form={form}
handleActionCheckChange={handleActionCheckChange}
handleResourceCheckChange={handleResourceCheckChange}
/>
);
})}
</TableBody>
Expand All @@ -285,6 +298,76 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({
);
};

interface PermissionCheckboxGroupProps {
checkedActions: readonly Permission[];
resourceKey: string;
value: Partial<Record<RBACAction, string>>;
form: ReturnType<typeof useFormik<Role>> & { values: Role };
handleActionCheckChange: (
e: ChangeEvent<HTMLInputElement>,
form: ReturnType<typeof useFormik<Role>> & { values: Role },
) => Promise<void>;
handleResourceCheckChange: (
e: ChangeEvent<HTMLInputElement>,
form: ReturnType<typeof useFormik<Role>> & { values: Role },
indeterminate: boolean,
) => Promise<void>;
}

const PermissionCheckboxGroup: FC<PermissionCheckboxGroupProps> = ({
checkedActions,
resourceKey,
value,
form,
handleActionCheckChange,
handleResourceCheckChange,
}) => {
return (
<TableRow key={resourceKey}>
<TableCell sx={{ paddingLeft: 2 }} colSpan={2}>
<li key={resourceKey} css={styles.checkBoxes}>
<Checkbox
size="small"
name={`${resourceKey}`}
checked={checkedActions.length === Object.keys(value).length}
indeterminate={
checkedActions.length > 0 &&
checkedActions.length < Object.keys(value).length
}
onChange={(e) =>
handleResourceCheckChange(
e,
form,
checkedActions.length > 0 &&
checkedActions.length < Object.keys(value).length,
)
}
/>
{resourceKey}
<ul css={styles.checkBoxes}>
{Object.entries(value).map(([actionKey, value]) => (
<li key={actionKey}>
<span css={styles.actionText}>
<Checkbox
size="small"
name={`${resourceKey}:${actionKey}`}
checked={checkedActions.some((p) =>
ResourceActionComparator(p, resourceKey, actionKey),
)}
onChange={(e) => handleActionCheckChange(e, form)}
/>
{actionKey}
</span>{" "}
&ndash; <span css={styles.actionDescription}>{value}</span>
</li>
))}
</ul>
</li>
</TableCell>
</TableRow>
);
};

interface ShowAllResourcesCheckboxProps {
showAllResources: boolean;
setShowAllResources: React.Dispatch<React.SetStateAction<boolean>>;
Expand Down
Loading