Add object level permission permissions to role page (ReadOnly) (#11568)

## Context
This PR adds the display of object-level permissions. A following PR
will add the ability to update those permissions.
The PR contains the SettingsRoleObjectLevel page but it's not fully
implemented yet (save won't trigger the corresponding mutation)

<img width="616" alt="Screenshot 2025-04-14 at 18 02 40"
src="https://github.com/user-attachments/assets/f8c58193-31f3-468a-a96d-f06a9f2e1423"
/>
This commit is contained in:
Weiko
2025-04-15 18:46:36 +02:00
committed by GitHub
parent c23942ce6f
commit 43af5ceb5e
41 changed files with 1092 additions and 268 deletions

View File

@ -1,34 +1,9 @@
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { SettingsRolePermissionsObjectsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableHeader';
import { SettingsRolePermissionsObjectsTableRow } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableRow';
import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsSettingsTableHeader';
import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsSettingsTableRow';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/types/SettingsRolePermissionsSettingPermission';
import { SettingsRolePermissionsObjectLevelSection } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection';
import { SettingsRolePermissionsObjectsSection } from '@/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsSection';
import { SettingsRolePermissionsSettingsSection } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useRecoilState } from 'recoil';
import {
H2Title,
IconCode,
IconEye,
IconHierarchy,
IconKey,
IconLockOpen,
IconPencil,
IconServer,
IconSettings,
IconTrash,
IconTrashX,
IconUsers,
} from 'twenty-ui/display';
import { Card, Section } from 'twenty-ui/layout';
import {
FeatureFlagKey,
SettingPermissionType,
} from '~/generated-metadata/graphql';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
const StyledRolePermissionsContainer = styled.div`
display: flex;
@ -36,19 +11,6 @@ const StyledRolePermissionsContainer = styled.div`
gap: ${({ theme }) => theme.spacing(8)};
`;
const StyledTable = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
`;
const StyledTableRows = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(2)};
padding-top: ${({ theme }) => theme.spacing(2)};
`;
const StyledCard = styled(Card)`
margin-bottom: ${({ theme }) => theme.spacing(4)};
`;
type SettingsRolePermissionsProps = {
roleId: string;
isEditable: boolean;
@ -58,168 +20,26 @@ export const SettingsRolePermissions = ({
roleId,
isEditable,
}: SettingsRolePermissionsProps) => {
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
settingsDraftRoleFamilyState(roleId),
);
const objectPermissionsConfig: SettingsRolePermissionsObjectPermission[] = [
{
key: 'seeRecords',
label: t`See Records on All Objects`,
Icon: IconEye,
value: settingsDraftRole.canReadAllObjectRecords,
setValue: (value: boolean) => {
setSettingsDraftRole({
...settingsDraftRole,
canReadAllObjectRecords: value,
});
},
},
{
key: 'editRecords',
label: t`Edit Records on All Objects`,
Icon: IconPencil,
value: settingsDraftRole.canUpdateAllObjectRecords,
setValue: (value: boolean) => {
setSettingsDraftRole({
...settingsDraftRole,
canUpdateAllObjectRecords: value,
});
},
},
{
key: 'deleteRecords',
label: t`Delete Records on All Objects`,
Icon: IconTrash,
value: settingsDraftRole.canSoftDeleteAllObjectRecords,
setValue: (value: boolean) => {
setSettingsDraftRole({
...settingsDraftRole,
canSoftDeleteAllObjectRecords: value,
});
},
},
{
key: 'destroyRecords',
label: t`Destroy Records on All Objects`,
Icon: IconTrashX,
value: settingsDraftRole.canDestroyAllObjectRecords,
setValue: (value: boolean) => {
setSettingsDraftRole({
...settingsDraftRole,
canDestroyAllObjectRecords: value,
});
},
},
];
const settingsPermissionsConfig: SettingsRolePermissionsSettingPermission[] =
[
{
key: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
name: t`API Keys & Webhooks`,
description: t`Manage API keys and webhooks`,
Icon: IconCode,
},
{
key: SettingPermissionType.WORKSPACE,
name: t`Workspace`,
description: t`Set global workspace preferences`,
Icon: IconSettings,
},
{
key: SettingPermissionType.WORKSPACE_MEMBERS,
name: t`Users`,
description: t`Add or remove users`,
Icon: IconUsers,
},
{
key: SettingPermissionType.ROLES,
name: t`Roles`,
description: t`Define user roles and access levels`,
Icon: IconLockOpen,
},
{
key: SettingPermissionType.DATA_MODEL,
name: t`Data Model`,
description: t`Edit CRM data structure and fields`,
Icon: IconHierarchy,
},
{
key: SettingPermissionType.ADMIN_PANEL,
name: t`Admin Panel`,
description: t`Admin settings and system tools`,
Icon: IconServer,
},
{
key: SettingPermissionType.SECURITY,
name: t`Security`,
description: t`Manage security policies`,
Icon: IconKey,
},
];
const isPermissionsV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsPermissionsV2Enabled,
);
return (
<StyledRolePermissionsContainer>
<Section>
<H2Title
title={t`Objects`}
description={t`Ability to interact with each object`}
<SettingsRolePermissionsObjectsSection
roleId={roleId}
isEditable={isEditable}
/>
{isPermissionsV2Enabled && (
<SettingsRolePermissionsObjectLevelSection
roleId={roleId}
isEditable={isEditable}
/>
<StyledTable>
<SettingsRolePermissionsObjectsTableHeader
roleId={roleId}
objectPermissionsConfig={objectPermissionsConfig}
isEditable={isEditable}
/>
<StyledTableRows>
{objectPermissionsConfig.map((permission) => (
<SettingsRolePermissionsObjectsTableRow
key={permission.key}
permission={permission}
isEditable={isEditable}
/>
))}
</StyledTableRows>
</StyledTable>
</Section>
<Section>
<H2Title title={t`Settings`} description={t`Settings permissions`} />
{isPermissionsV2Enabled && (
<StyledCard rounded>
<SettingsOptionCardContentToggle
Icon={IconSettings}
title={t`Settings All Access`}
description={t`Ability to edit all settings`}
checked={settingsDraftRole.canUpdateAllSettings}
disabled={!isEditable}
onChange={() => {
setSettingsDraftRole({
...settingsDraftRole,
canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings,
});
}}
/>
</StyledCard>
)}
<StyledTable>
<SettingsRolePermissionsSettingsTableHeader />
<StyledTableRows>
{settingsPermissionsConfig.map((permission) => (
<SettingsRolePermissionsSettingsTableRow
key={permission.key}
roleId={roleId}
permission={permission}
isEditable={isEditable}
/>
))}
</StyledTableRows>
</StyledTable>
</Section>
)}
<SettingsRolePermissionsSettingsSection
roleId={roleId}
isEditable={isEditable}
/>
</StyledRolePermissionsContainer>
);
};

View File

@ -1,68 +0,0 @@
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useRecoilState } from 'recoil';
import { Checkbox } from 'twenty-ui/input';
const StyledNameHeader = styled(TableHeader)`
flex: 1;
`;
const StyledActionsHeader = styled(TableHeader)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${({ theme }) => theme.spacing(4)};
`;
type SettingsRolePermissionsObjectsTableHeaderProps = {
roleId: string;
objectPermissionsConfig: SettingsRolePermissionsObjectPermission[];
isEditable: boolean;
};
export const SettingsRolePermissionsObjectsTableHeader = ({
roleId,
objectPermissionsConfig,
isEditable,
}: SettingsRolePermissionsObjectsTableHeaderProps) => {
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
settingsDraftRoleFamilyState(roleId),
);
const allPermissionsEnabled = objectPermissionsConfig.every(
(permission) => permission.value,
);
const somePermissionsEnabled = objectPermissionsConfig.some(
(permission) => permission.value,
);
return (
<TableRow>
<StyledNameHeader>{t`Name`}</StyledNameHeader>
<StyledActionsHeader aria-label={t`Actions`}>
<Checkbox
checked={allPermissionsEnabled}
indeterminate={somePermissionsEnabled && !allPermissionsEnabled}
disabled={!isEditable}
aria-label={t`Toggle all object permissions`}
onChange={() => {
const newValue = !allPermissionsEnabled;
setSettingsDraftRole({
...settingsDraftRole,
canReadAllObjectRecords: newValue,
canUpdateAllObjectRecords: newValue,
canSoftDeleteAllObjectRecords: newValue,
canDestroyAllObjectRecords: newValue,
});
}}
/>
</StyledActionsHeader>
</TableRow>
);
};

View File

@ -1,80 +0,0 @@
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Checkbox } from 'twenty-ui/input';
const StyledIconWrapper = styled.div`
align-items: center;
background: ${({ theme }) => theme.adaptiveColors.blue1};
border: 1px solid ${({ theme }) => theme.adaptiveColors.blue3};
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
height: ${({ theme }) => theme.spacing(4)};
justify-content: center;
width: ${({ theme }) => theme.spacing(4)};
`;
const StyledIcon = styled.div`
align-items: center;
display: flex;
color: ${({ theme }) => theme.color.blue};
justify-content: center;
`;
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledPermissionCell = styled(TableCell)`
align-items: center;
display: flex;
flex: 1;
gap: ${({ theme }) => theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${({ theme }) => theme.spacing(4)};
`;
const StyledTableRow = styled(TableRow)`
align-items: center;
display: flex;
`;
type SettingsRolePermissionsObjectsTableRowProps = {
permission: SettingsRolePermissionsObjectPermission;
isEditable: boolean;
};
export const SettingsRolePermissionsObjectsTableRow = ({
permission,
isEditable,
}: SettingsRolePermissionsObjectsTableRowProps) => {
const theme = useTheme();
return (
<StyledTableRow>
<StyledPermissionCell>
<StyledIconWrapper>
<StyledIcon>
<permission.Icon size={theme.icon.size.sm} />
</StyledIcon>
</StyledIconWrapper>
<StyledLabel>{permission.label}</StyledLabel>
</StyledPermissionCell>
<StyledCheckboxCell>
<Checkbox
checked={permission.value}
onChange={() => permission.setValue(!permission.value)}
disabled={!isEditable}
/>
</StyledCheckboxCell>
</StyledTableRow>
);
};

View File

@ -1,11 +0,0 @@
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { t } from '@lingui/core/macro';
export const SettingsRolePermissionsSettingsTableHeader = () => (
<TableRow gridAutoColumns="3fr 4fr 24px">
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>{t`Description`}</TableHeader>
<TableHeader></TableHeader>
</TableRow>
);

View File

@ -1,117 +0,0 @@
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/types/SettingsRolePermissionsSettingPermission';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { Checkbox } from 'twenty-ui/input';
import { v4 } from 'uuid';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
const StyledName = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledDescription = styled(StyledName)`
color: ${({ theme }) => theme.font.color.secondary};
`;
const StyledPermissionCell = styled(TableCell)`
align-items: center;
display: flex;
flex: 1;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
justify-content: flex-end;
padding-right: ${({ theme }) => theme.spacing(4)};
`;
const StyledIconContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
`;
type SettingsRolePermissionsSettingsTableRowProps = {
roleId: string;
permission: SettingsRolePermissionsSettingPermission;
isEditable: boolean;
};
export const SettingsRolePermissionsSettingsTableRow = ({
roleId,
permission,
isEditable,
}: SettingsRolePermissionsSettingsTableRowProps) => {
const theme = useTheme();
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
settingsDraftRoleFamilyState(roleId),
);
const isPermissionsV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsPermissionsV2Enabled,
);
const canUpdateAllSettings = settingsDraftRole.canUpdateAllSettings;
const isSettingPermissionEnabled =
settingsDraftRole.settingPermissions?.some(
(settingPermission) => settingPermission.setting === permission.key,
) ?? false;
const handleChange = (value: boolean) => {
const currentPermissions = settingsDraftRole.settingPermissions ?? [];
if (value === true) {
setSettingsDraftRole({
...settingsDraftRole,
settingPermissions: [
...currentPermissions,
{
id: v4(),
setting: permission.key,
roleId,
},
],
});
} else {
setSettingsDraftRole({
...settingsDraftRole,
settingPermissions: currentPermissions.filter(
(p) => p.setting !== permission.key,
),
});
}
};
return (
<TableRow key={permission.key} gridAutoColumns="3fr 4fr 24px">
<StyledPermissionCell>
<StyledIconContainer>
<permission.Icon
size={theme.icon.size.md}
color={theme.font.color.primary}
stroke={theme.icon.stroke.sm}
/>
</StyledIconContainer>
<StyledName>{permission.name}</StyledName>
</StyledPermissionCell>
<StyledPermissionCell>
<StyledDescription>{permission.description}</StyledDescription>
</StyledPermissionCell>
<StyledCheckboxCell>
<Checkbox
checked={isSettingPermissionEnabled || canUpdateAllSettings}
disabled={
!isEditable || canUpdateAllSettings || !isPermissionsV2Enabled
}
onChange={(event) => handleChange(event.target.checked)}
/>
</StyledCheckboxCell>
</TableRow>
);
};