Feat 17408 : Add remove option for object permissions rule (#17601)

This PR aims to fix: #17408 

Main modification includes adding a new column for dropdown in the
object permission rule table (containing options for editing and
removal). Removal logic is implemented using existing pattern with hook
```useResetObjectPermission``` (relies on existing hooks
```useUpsertFieldPermissionInDraftRole``` and
```useUpsertObjectPermissionInDraftRole```).

Feel free to suggest any necessary changes. Functionality (unrestricted
access is allowed when permission removal is applied) is already tested.

Demo video:
https://drive.google.com/file/d/1M4RYHw-JEhDdJksKkL3MY_VyXAV3aS9I/view?usp=sharing

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
This commit is contained in:
Tan Le
2026-02-13 04:16:36 -05:00
committed by GitHub
parent 5c2c588885
commit fdb5d2fbcd
5 changed files with 168 additions and 14 deletions
@@ -16,5 +16,6 @@ export const SettingsRolePermissionsObjectLevelTableHeader = ({
<TableHeader>{showPermissionsLabel ? t`See Fields` : ''}</TableHeader>
<TableHeader>{showPermissionsLabel ? t`Edit Fields` : ''}</TableHeader>
<TableHeader></TableHeader>
<TableHeader></TableHeader>
</TableRow>
);
@@ -1,6 +1,7 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SettingsRolePermissionsObjectLevelOverrideCellContainer } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelOverrideCellContainer';
import { SettingsRolePermissionsObjectLevelSeeFieldsValueForObject } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSeeFieldsValueForObject';
import { SettingsRolePermissionsObjectLevelTableRowOptionsDropdown } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRowOptionsDropdown';
import { SettingsRolePermissionsObjectLevelUpdateFieldsValueForObject } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelUpdateFieldsValueForObject';
import { OBJECT_LEVEL_PERMISSION_TABLE_GRID_AUTO_COLUMNS } from '@/settings/roles/role-permissions/object-level-permissions/constants/ObjectLevelPermissionTableGridAutoColumns';
import { TableCell } from '@/ui/layout/table/components/TableCell';
@@ -9,11 +10,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import {
IconChevronRight,
OverflowingTextWithTooltip,
useIcons,
} from 'twenty-ui/display';
import { OverflowingTextWithTooltip, useIcons } from 'twenty-ui/display';
const StyledNameTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.primary};
@@ -26,6 +23,11 @@ const StyledNameLabel = styled.div`
overflow: hidden;
`;
const StyledOptionsTableCell = styled(TableCell)`
justify-content: flex-end;
padding-right: ${({ theme }) => theme.spacing(1)};
`;
type SettingsRolePermissionsObjectLevelTableRowProps = {
objectMetadataItem: ObjectMetadataItem;
roleId: string;
@@ -90,14 +92,15 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
objectMetadataItem={objectMetadataItem}
/>
</TableCell>
{isEditable && (
<TableCell align="right">
<IconChevronRight
size={theme.icon.size.md}
color={theme.font.color.tertiary}
/>
</TableCell>
)}
<TableCell></TableCell>
<StyledOptionsTableCell>
<SettingsRolePermissionsObjectLevelTableRowOptionsDropdown
roleId={roleId}
objectMetadataId={objectMetadataItem.id}
objectPermissionDetailUrl={navigationUrl}
isEditable={isEditable}
/>
</StyledOptionsTableCell>
</TableRow>
);
};
@@ -0,0 +1,72 @@
import { useResetObjectPermission } from '@/settings/roles/role-permissions/object-level-permissions/hooks/useResetObjectPermission';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { t } from '@lingui/core/macro';
import { IconDotsVertical, IconPencil, IconTrash } from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';
import { MenuItem, UndecoratedLink } from 'twenty-ui/navigation';
type SettingsRolePermissionsObjectLevelTableRowOptionsDropdownProps = {
roleId: string;
objectMetadataId: string;
objectPermissionDetailUrl: string;
isEditable: boolean;
};
export const SettingsRolePermissionsObjectLevelTableRowOptionsDropdown = ({
roleId,
objectMetadataId,
objectPermissionDetailUrl,
isEditable,
}: SettingsRolePermissionsObjectLevelTableRowOptionsDropdownProps) => {
const dropdownId = `settings-role-object-level-options-${objectMetadataId}`;
const { closeDropdown } = useCloseDropdown();
const { resetObjectPermission } = useResetObjectPermission(roleId);
const handleRemove = () => {
closeDropdown(dropdownId);
resetObjectPermission(objectMetadataId);
};
return (
<Dropdown
dropdownId={dropdownId}
clickableComponent={
<IconButton
aria-label={t`Object permission options`}
variant="tertiary"
size="small"
Icon={IconDotsVertical}
/>
}
dropdownComponents={
<DropdownContent>
{isEditable && (
<DropdownMenuItemsContainer>
<UndecoratedLink
fullWidth
to={objectPermissionDetailUrl}
onClick={() => closeDropdown(dropdownId)}
>
<MenuItem text={t`Edit`} LeftIcon={IconPencil} />
</UndecoratedLink>
</DropdownMenuItemsContainer>
)}
<DropdownMenuItemsContainer>
<MenuItem
text={t`Remove rule`}
onClick={handleRemove}
LeftIcon={IconTrash}
accent="danger"
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownPlacement="bottom-end"
/>
);
};
@@ -4,7 +4,7 @@ const {
} = {
OBJECT_LEVEL_PERMISSION_TABLE_GRID_AUTO_COLUMNS_WITHOUT_FIELD_PERMISSIONS:
'180px 1fr 1fr',
OBJECT_LEVEL_PERMISSION_TABLE_GRID_AUTO_COLUMNS: '30% 20% 20% 20% 10%',
OBJECT_LEVEL_PERMISSION_TABLE_GRID_AUTO_COLUMNS: '28% 18% 18% 18% 8% 10%',
};
export {
@@ -0,0 +1,78 @@
import { useUpsertFieldPermissionInDraftRole } from '@/settings/roles/role-permissions/object-level-permissions/field-permissions/hooks/useUpsertFieldPermissionInDraftRole';
import { useUpsertObjectPermissionInDraftRole } from '@/settings/roles/role-permissions/object-level-permissions/hooks/useUpsertObjectPermissionInDraftRole';
import { type SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/SettingsRoleObjectPermissionIconConfig';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import {
type FieldPermission,
type ObjectPermission,
} from '~/generated-metadata/graphql';
const OBJECT_PERMISSION_KEYS: SettingsRoleObjectPermissionKey[] = [
'canReadObjectRecords',
'canUpdateObjectRecords',
'canSoftDeleteObjectRecords',
'canDestroyObjectRecords',
];
export const useResetObjectPermission = (roleId: string) => {
const settingsDraftRole = useRecoilValue(
settingsDraftRoleFamilyState(roleId),
);
const { upsertObjectPermissionInDraftRole } =
useUpsertObjectPermissionInDraftRole(roleId);
const { upsertFieldPermissionInDraftRole } =
useUpsertFieldPermissionInDraftRole(roleId);
const resetObjectPermission = (objectMetadataItemId: string) => {
const fieldPermissionsForCurrentObject =
settingsDraftRole.fieldPermissions?.filter(
(permission) => permission.objectMetadataId === objectMetadataItemId,
) ?? [];
const existingObjectPermission = settingsDraftRole.objectPermissions?.find(
(objectPermissionToFind) =>
objectPermissionToFind.objectMetadataId === objectMetadataItemId,
);
const resetPermissions = OBJECT_PERMISSION_KEYS.reduce(
(acc, permissionKey) => {
acc[permissionKey] = null;
return acc;
},
{} as Record<SettingsRoleObjectPermissionKey, null>,
);
if (!isDefined(existingObjectPermission)) {
const newObjectPermission = {
objectMetadataId: objectMetadataItemId,
...resetPermissions,
} satisfies ObjectPermission;
upsertObjectPermissionInDraftRole(newObjectPermission);
} else {
const updatedObjectPermission = {
...existingObjectPermission,
...resetPermissions,
};
upsertObjectPermissionInDraftRole(updatedObjectPermission);
}
fieldPermissionsForCurrentObject.forEach(
(fieldPermission: FieldPermission) => {
upsertFieldPermissionInDraftRole({
...fieldPermission,
canUpdateFieldValue: null,
canReadFieldValue: null,
});
},
);
};
return {
resetObjectPermission,
};
};