Decouple Send Email node from workflows (#13322)

- Renamed `WorkflowActionAdapter` to `ToolExecutorWorkflowAction`
- Renamed `settingPermission` table to `permissionFlag` and `setting`
column to `flag`
- Decoupled the send email logic from workflows to tools
- Add new `Tools Permission` section in FE

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Abdul Rahman
2025-07-24 16:01:33 +05:30
committed by GitHub
parent eb404478c3
commit e93adde4b8
98 changed files with 1076 additions and 705 deletions

View File

@ -4,7 +4,7 @@ import { Route, Routes } from 'react-router-dom';
import { SettingsProtectedRouteWrapper } from '@/settings/components/SettingsProtectedRouteWrapper';
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
import { SettingsPath } from '@/types/SettingsPath';
import { SettingPermissionType } from '~/generated/graphql';
import { PermissionFlagType } from '~/generated/graphql';
const SettingsApiKeys = lazy(() =>
import('~/pages/settings/developers/api-keys/SettingsApiKeys').then(
@ -405,7 +405,7 @@ export const SettingsRoutes = ({
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={SettingPermissionType.WORKSPACE}
settingsPermission={PermissionFlagType.WORKSPACE}
/>
}
>
@ -416,7 +416,7 @@ export const SettingsRoutes = ({
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={SettingPermissionType.WORKSPACE_MEMBERS}
settingsPermission={PermissionFlagType.WORKSPACE_MEMBERS}
/>
}
>
@ -428,7 +428,7 @@ export const SettingsRoutes = ({
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={SettingPermissionType.DATA_MODEL}
settingsPermission={PermissionFlagType.DATA_MODEL}
/>
}
>
@ -458,7 +458,7 @@ export const SettingsRoutes = ({
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={SettingPermissionType.ROLES}
settingsPermission={PermissionFlagType.ROLES}
/>
}
>
@ -480,7 +480,7 @@ export const SettingsRoutes = ({
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={SettingPermissionType.API_KEYS_AND_WEBHOOKS}
settingsPermission={PermissionFlagType.API_KEYS_AND_WEBHOOKS}
/>
}
>
@ -555,7 +555,7 @@ export const SettingsRoutes = ({
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={SettingPermissionType.SECURITY}
settingsPermission={PermissionFlagType.SECURITY}
/>
}
>
@ -587,7 +587,7 @@ export const SettingsRoutes = ({
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={SettingPermissionType.WORKSPACE}
settingsPermission={PermissionFlagType.WORKSPACE}
/>
}
>

View File

@ -5,7 +5,7 @@ import { SettingsPath } from '@/types/SettingsPath';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import {
SettingPermissionType,
PermissionFlagType,
useBillingPortalSessionQuery,
} from '~/generated-metadata/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
@ -20,7 +20,7 @@ export const InformationBannerBillingSubscriptionPaused = () => {
});
const {
[SettingPermissionType.WORKSPACE]: hasPermissionToUpdateBillingDetails,
[PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails,
} = useSettingsPermissionMap();
const openBillingPortal = () => {

View File

@ -2,13 +2,13 @@ import { useEndSubscriptionTrialPeriod } from '@/billing/hooks/useEndSubscriptio
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { useSettingsPermissionMap } from '@/settings/roles/hooks/useSettingsPermissionMap';
import { useLingui } from '@lingui/react/macro';
import { SettingPermissionType } from '~/generated-metadata/graphql';
import { PermissionFlagType } from '~/generated-metadata/graphql';
export const InformationBannerEndTrialPeriod = () => {
const { endTrialPeriod, isLoading } = useEndSubscriptionTrialPeriod();
const { t } = useLingui();
const { [SettingPermissionType.WORKSPACE]: hasPermissionToEndTrialPeriod } =
const { [PermissionFlagType.WORKSPACE]: hasPermissionToEndTrialPeriod } =
useSettingsPermissionMap();
return (

View File

@ -5,7 +5,7 @@ import { SettingsPath } from '@/types/SettingsPath';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import {
SettingPermissionType,
PermissionFlagType,
useBillingPortalSessionQuery,
} from '~/generated-metadata/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
@ -20,7 +20,7 @@ export const InformationBannerFailPaymentInfo = () => {
});
const {
[SettingPermissionType.WORKSPACE]: hasPermissionToUpdateBillingDetails,
[PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails,
} = useSettingsPermissionMap();
const openBillingPortal = () => {

View File

@ -4,7 +4,7 @@ import { InformationBanner } from '@/information-banner/components/InformationBa
import { useSettingsPermissionMap } from '@/settings/roles/hooks/useSettingsPermissionMap';
import { SettingsPath } from '@/types/SettingsPath';
import { t } from '@lingui/core/macro';
import { SettingPermissionType } from '~/generated-metadata/graphql';
import { PermissionFlagType } from '~/generated-metadata/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
export const InformationBannerNoBillingSubscription = () => {
@ -15,7 +15,7 @@ export const InformationBannerNoBillingSubscription = () => {
successUrlPath: getSettingsPath(SettingsPath.Billing),
});
const { [SettingPermissionType.WORKSPACE]: hasPermissionToSubscribe } =
const { [PermissionFlagType.WORKSPACE]: hasPermissionToSubscribe } =
useSettingsPermissionMap();
return (

View File

@ -24,7 +24,7 @@ import {
IconEyeOff,
IconSettings,
} from 'twenty-ui/display';
import { SettingPermissionType } from '~/generated/graphql';
import { PermissionFlagType } from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
type UseRecordGroupActionsParams = {
@ -91,7 +91,7 @@ export const useRecordGroupActions = ({
]);
const hasAccessToDataModelSettings = useHasSettingsPermission(
SettingPermissionType.DATA_MODEL,
PermissionFlagType.DATA_MODEL,
);
const currentIndex = visibleRecordGroupIds.findIndex(
(id) => id === recordGroupDefinition.id,

View File

@ -3,12 +3,12 @@ import { SettingsPath } from '@/types/SettingsPath';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { ReactNode } from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { FeatureFlagKey, SettingPermissionType } from '~/generated/graphql';
import { FeatureFlagKey, PermissionFlagType } from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
type SettingsProtectedRouteWrapperProps = {
children?: ReactNode;
settingsPermission?: SettingPermissionType;
settingsPermission?: PermissionFlagType;
requiredFeatureFlag?: FeatureFlagKey;
};

View File

@ -7,7 +7,7 @@ import { MutableSnapshot, RecoilRoot } from 'recoil';
import {
Billing,
OnboardingStatus,
SettingPermissionType,
PermissionFlagType,
} from '~/generated/graphql';
import { currentUserState } from '@/auth/states/currentUserState';
@ -60,12 +60,12 @@ jest.mock('@/settings/roles/hooks/useSettingsPermissionMap', () => ({
describe('useSettingsNavigationItems', () => {
it('should hide workspace settings when no permissions', () => {
(useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({
[SettingPermissionType.WORKSPACE]: false,
[SettingPermissionType.WORKSPACE_MEMBERS]: false,
[SettingPermissionType.DATA_MODEL]: false,
[SettingPermissionType.API_KEYS_AND_WEBHOOKS]: false,
[SettingPermissionType.ROLES]: false,
[SettingPermissionType.SECURITY]: false,
[PermissionFlagType.WORKSPACE]: false,
[PermissionFlagType.WORKSPACE_MEMBERS]: false,
[PermissionFlagType.DATA_MODEL]: false,
[PermissionFlagType.API_KEYS_AND_WEBHOOKS]: false,
[PermissionFlagType.ROLES]: false,
[PermissionFlagType.SECURITY]: false,
}));
const { result } = renderHook(() => useSettingsNavigationItems(), {
@ -81,12 +81,12 @@ describe('useSettingsNavigationItems', () => {
it('should show workspace settings when has permissions', () => {
(useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({
[SettingPermissionType.WORKSPACE]: true,
[SettingPermissionType.WORKSPACE_MEMBERS]: true,
[SettingPermissionType.DATA_MODEL]: true,
[SettingPermissionType.API_KEYS_AND_WEBHOOKS]: true,
[SettingPermissionType.ROLES]: true,
[SettingPermissionType.SECURITY]: true,
[PermissionFlagType.WORKSPACE]: true,
[PermissionFlagType.WORKSPACE_MEMBERS]: true,
[PermissionFlagType.DATA_MODEL]: true,
[PermissionFlagType.API_KEYS_AND_WEBHOOKS]: true,
[PermissionFlagType.ROLES]: true,
[PermissionFlagType.SECURITY]: true,
}));
const { result } = renderHook(() => useSettingsNavigationItems(), {

View File

@ -30,7 +30,7 @@ import {
IconUsers,
IconWebhook,
} from 'twenty-ui/display';
import { SettingPermissionType } from '~/generated/graphql';
import { PermissionFlagType } from '~/generated/graphql';
export type SettingsNavigationSection = {
label: string;
@ -107,46 +107,45 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
label: t`General`,
path: SettingsPath.Workspace,
Icon: IconSettings,
isHidden: !permissionMap[SettingPermissionType.WORKSPACE],
isHidden: !permissionMap[PermissionFlagType.WORKSPACE],
},
{
label: t`Members`,
path: SettingsPath.WorkspaceMembersPage,
Icon: IconUsers,
isHidden: !permissionMap[SettingPermissionType.WORKSPACE_MEMBERS],
isHidden: !permissionMap[PermissionFlagType.WORKSPACE_MEMBERS],
},
{
label: t`Roles`,
path: SettingsPath.Roles,
Icon: IconLock,
isHidden: !permissionMap[SettingPermissionType.ROLES],
isHidden: !permissionMap[PermissionFlagType.ROLES],
},
{
label: t`Billing`,
path: SettingsPath.Billing,
Icon: IconCurrencyDollar,
isHidden:
!isBillingEnabled ||
!permissionMap[SettingPermissionType.WORKSPACE],
!isBillingEnabled || !permissionMap[PermissionFlagType.WORKSPACE],
},
{
label: t`Data model`,
path: SettingsPath.Objects,
Icon: IconHierarchy2,
isHidden: !permissionMap[SettingPermissionType.DATA_MODEL],
isHidden: !permissionMap[PermissionFlagType.DATA_MODEL],
},
{
label: t`Integrations`,
path: SettingsPath.Integrations,
Icon: IconApps,
isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS],
isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS],
},
{
label: t`Security`,
path: SettingsPath.Security,
Icon: IconKey,
isAdvanced: true,
isHidden: !permissionMap[SettingPermissionType.SECURITY],
isHidden: !permissionMap[PermissionFlagType.SECURITY],
},
],
},
@ -159,14 +158,14 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
path: SettingsPath.APIs,
Icon: IconApi,
isAdvanced: true,
isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS],
isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS],
},
{
label: t`Webhooks`,
path: SettingsPath.Webhooks,
Icon: IconWebhook,
isAdvanced: true,
isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS],
isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS],
},
{
label: t`Functions`,
@ -192,7 +191,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
Icon: IconFlask,
isHidden:
!labPublicFeatureFlags.length ||
!permissionMap[SettingPermissionType.WORKSPACE],
!permissionMap[PermissionFlagType.WORKSPACE],
},
{
label: t`Releases`,

View File

@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const PERMISSION_FLAG_FRAGMENT = gql`
fragment PermissionFlagFragment on PermissionFlag {
id
flag
roleId
}
`;

View File

@ -7,6 +7,7 @@ export const ROLE_FRAGMENT = gql`
description
icon
canUpdateAllSettings
canAccessAllTools
isEditable
canReadAllObjectRecords
canUpdateAllObjectRecords

View File

@ -1,9 +0,0 @@
import { gql } from '@apollo/client';
export const SETTING_PERMISSION_FRAGMENT = gql`
fragment SettingPermissionFragment on SettingPermission {
id
setting
roleId
}
`;

View File

@ -0,0 +1,15 @@
import { PERMISSION_FLAG_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagFragment';
import { gql } from '@apollo/client';
export const UPSERT_PERMISSION_FLAGS = gql`
${PERMISSION_FLAG_FRAGMENT}
mutation UpsertPermissionFlags(
$upsertPermissionFlagsInput: UpsertPermissionFlagsInput!
) {
upsertPermissionFlags(
upsertPermissionFlagsInput: $upsertPermissionFlagsInput
) {
...PermissionFlagFragment
}
}
`;

View File

@ -1,15 +0,0 @@
import { SETTING_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/settingPermissionFragment';
import { gql } from '@apollo/client';
export const UPSERT_SETTING_PERMISSIONS = gql`
${SETTING_PERMISSION_FRAGMENT}
mutation UpsertSettingPermissions(
$upsertSettingPermissionsInput: UpsertSettingPermissionsInput!
) {
upsertSettingPermissions(
upsertSettingPermissionsInput: $upsertSettingPermissionsInput
) {
...SettingPermissionFragment
}
}
`;

View File

@ -1,13 +1,13 @@
import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment';
import { PERMISSION_FLAG_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagFragment';
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
import { SETTING_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/settingPermissionFragment';
import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment';
import { gql } from '@apollo/client';
export const GET_ROLES = gql`
${WORKSPACE_MEMBER_QUERY_FRAGMENT}
${ROLE_FRAGMENT}
${SETTING_PERMISSION_FRAGMENT}
${PERMISSION_FLAG_FRAGMENT}
${OBJECT_PERMISSION_FRAGMENT}
query GetRoles {
getRoles {
@ -15,8 +15,8 @@ export const GET_ROLES = gql`
workspaceMembers {
...WorkspaceMemberQueryFragment
}
settingPermissions {
...SettingPermissionFragment
permissionFlags {
...PermissionFlagFragment
}
objectPermissions {
...ObjectPermissionFragment

View File

@ -2,20 +2,20 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceSta
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useRecoilValue } from 'recoil';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { SettingPermissionType } from '~/generated/graphql';
import { PermissionFlagType } from '~/generated/graphql';
export const useHasSettingsPermission = (
settingsPermission?: SettingPermissionType,
permissionFlag?: PermissionFlagType,
) => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
if (!settingsPermission) {
if (!permissionFlag) {
return true;
}
if (
settingsPermission === SettingPermissionType.WORKSPACE &&
permissionFlag === PermissionFlagType.WORKSPACE &&
currentWorkspace?.activationStatus ===
WorkspaceActivationStatus.PENDING_CREATION
) {
@ -28,5 +28,5 @@ export const useHasSettingsPermission = (
return false;
}
return currentUserWorkspaceSetting.includes(settingsPermission);
return currentUserWorkspaceSetting.includes(permissionFlag);
};

View File

@ -1,10 +1,10 @@
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { useRecoilValue } from 'recoil';
import { SettingPermissionType } from '~/generated/graphql';
import { PermissionFlagType } from '~/generated/graphql';
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
export const useSettingsPermissionMap = (): Record<
SettingPermissionType,
PermissionFlagType,
boolean
> => {
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
@ -13,7 +13,7 @@ export const useSettingsPermissionMap = (): Record<
currentUserWorkspace?.settingsPermissions;
const initialPermissions = buildRecordFromKeysWithSameValue(
Object.values(SettingPermissionType),
Object.values(PermissionFlagType),
false,
);

View File

@ -1,6 +1,7 @@
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 { SettingsRolePermissionsSettingsSection } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection';
import { SettingsRolePermissionsToolSection } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsToolSection';
import styled from '@emotion/styled';
const StyledRolePermissionsContainer = styled.div`
@ -32,6 +33,10 @@ export const SettingsRolePermissions = ({
roleId={roleId}
isEditable={isEditable}
/>
<SettingsRolePermissionsToolSection
roleId={roleId}
isEditable={isEditable}
/>
</StyledRolePermissionsContainer>
);
};

View File

@ -1,7 +1,7 @@
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableHeader';
import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableRow';
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission';
import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableHeader';
import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableRow';
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
@ -17,7 +17,7 @@ import {
IconUsers,
} from 'twenty-ui/display';
import { AnimatedExpandableContainer, Card, Section } from 'twenty-ui/layout';
import { SettingPermissionType } from '~/generated-metadata/graphql';
import { PermissionFlagType } from '~/generated-metadata/graphql';
const StyledTable = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
@ -48,43 +48,43 @@ export const SettingsRolePermissionsSettingsSection = ({
const settingsPermissionsConfig: SettingsRolePermissionsSettingPermission[] =
[
{
key: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
key: PermissionFlagType.API_KEYS_AND_WEBHOOKS,
name: t`API Keys & Webhooks`,
description: t`Manage API keys and webhooks`,
Icon: IconCode,
},
{
key: SettingPermissionType.WORKSPACE,
key: PermissionFlagType.WORKSPACE,
name: t`Workspace`,
description: t`Set global workspace preferences`,
Icon: IconSettings,
},
{
key: SettingPermissionType.WORKSPACE_MEMBERS,
key: PermissionFlagType.WORKSPACE_MEMBERS,
name: t`Users`,
description: t`Add or remove users`,
Icon: IconUsers,
},
{
key: SettingPermissionType.ROLES,
key: PermissionFlagType.ROLES,
name: t`Roles`,
description: t`Define user roles and access levels`,
Icon: IconLockOpen,
},
{
key: SettingPermissionType.DATA_MODEL,
key: PermissionFlagType.DATA_MODEL,
name: t`Data Model`,
description: t`Edit CRM data structure and fields`,
Icon: IconHierarchy,
},
{
key: SettingPermissionType.SECURITY,
key: PermissionFlagType.SECURITY,
name: t`Security`,
description: t`Manage security policies`,
Icon: IconKey,
},
{
key: SettingPermissionType.WORKFLOWS,
key: PermissionFlagType.WORKFLOWS,
name: t`Workflows`,
description: t`Manage workflows`,
Icon: IconSettingsAutomation,

View File

@ -4,7 +4,7 @@ import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { Checkbox } from 'twenty-ui/input';
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission';
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { useRecoilState } from 'recoil';
import { v4 } from 'uuid';
@ -32,15 +32,15 @@ export const SettingsRolePermissionsSettingsTableHeader = ({
);
const allSettingsPermissionsEnabled = settingsPermissionsConfig.every(
(permission) =>
settingsDraftRole.settingPermissions?.some(
(settingPermission) => settingPermission.setting === permission.key,
settingsDraftRole.permissionFlags?.some(
(permissionFlag) => permissionFlag.flag === permission.key,
),
);
const someSettingsPermissionsEnabled = settingsPermissionsConfig.some(
(permission) =>
settingsDraftRole.settingPermissions?.some(
(settingPermission) => settingPermission.setting === permission.key,
settingsDraftRole.permissionFlags?.some(
(permissionFlag) => permissionFlag.flag === permission.key,
),
);
@ -61,10 +61,10 @@ export const SettingsRolePermissionsSettingsTableHeader = ({
setSettingsDraftRole({
...settingsDraftRole,
settingPermissions: newValue
permissionFlags: newValue
? settingsPermissionsConfig.map((permission) => ({
id: v4(),
setting: permission.key,
flag: permission.key,
roleId,
}))
: [],

View File

@ -1,4 +1,4 @@
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission';
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
@ -44,6 +44,7 @@ type SettingsRolePermissionsSettingsTableRowProps = {
roleId: string;
permission: SettingsRolePermissionsSettingPermission;
isEditable: boolean;
isToolPermission?: boolean;
};
export const SettingsRolePermissionsSettingsTableRow = ({
@ -55,27 +56,35 @@ export const SettingsRolePermissionsSettingsTableRow = ({
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
settingsDraftRoleFamilyState(roleId),
);
const canUpdateAllSettings = settingsDraftRole.canUpdateAllSettings;
const isSettingPermissionEnabled =
settingsDraftRole.settingPermissions?.some(
(settingPermission) => settingPermission.setting === permission.key,
const isPermissionEnabled =
settingsDraftRole.permissionFlags?.some(
(permissionFlag) => permissionFlag.flag === permission.key,
) ?? false;
const isChecked = isSettingPermissionEnabled || canUpdateAllSettings;
const isDisabled = !isEditable || canUpdateAllSettings;
const isAllSettingsOverride =
!permission.isToolPermission &&
settingsDraftRole.canUpdateAllSettings === true;
const isAllToolsOverride =
permission.isToolPermission && settingsDraftRole.canAccessAllTools === true;
const isChecked = Boolean(
isPermissionEnabled || isAllSettingsOverride || isAllToolsOverride,
);
const isDisabled = Boolean(
!isEditable || isAllSettingsOverride || isAllToolsOverride,
);
const handleChange = (value: boolean) => {
const currentPermissions = settingsDraftRole.settingPermissions ?? [];
const currentPermissions = settingsDraftRole.permissionFlags ?? [];
if (value === true) {
setSettingsDraftRole({
...settingsDraftRole,
settingPermissions: [
permissionFlags: [
...currentPermissions,
{
id: v4(),
setting: permission.key,
flag: permission.key,
roleId,
},
],
@ -83,8 +92,8 @@ export const SettingsRolePermissionsSettingsTableRow = ({
} else {
setSettingsDraftRole({
...settingsDraftRole,
settingPermissions: currentPermissions.filter(
(p) => p.setting !== permission.key,
permissionFlags: currentPermissions.filter(
(p) => p.flag !== permission.key,
),
});
}

View File

@ -0,0 +1,101 @@
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableHeader';
import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableRow';
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useRecoilState } from 'recoil';
import { H2Title, IconMail, IconTool } from 'twenty-ui/display';
import { AnimatedExpandableContainer, Card, Section } from 'twenty-ui/layout';
import { PermissionFlagType } from '~/generated-metadata/graphql';
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 SettingsRolePermissionsToolSectionProps = {
roleId: string;
isEditable: boolean;
};
export const SettingsRolePermissionsToolSection = ({
roleId,
isEditable,
}: SettingsRolePermissionsToolSectionProps) => {
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
settingsDraftRoleFamilyState(roleId),
);
const toolPermissionsConfig: SettingsRolePermissionsSettingPermission[] = [
{
key: PermissionFlagType.SEND_EMAIL_TOOL,
name: t`Send Email`,
description: t`Allow sending emails using connected accounts`,
Icon: IconMail,
isToolPermission: true,
},
];
return (
<Section>
<H2Title
title={t`Action Permissions`}
description={t`Permissions for performing automated actions.`}
/>
<StyledCard rounded>
<SettingsOptionCardContentToggle
Icon={IconTool}
title={t`All Actions Access`}
description={t`Grants permission to perform all available actions without restriction.`}
checked={settingsDraftRole.canAccessAllTools}
disabled={!isEditable}
onChange={() => {
setSettingsDraftRole({
...settingsDraftRole,
canAccessAllTools: !settingsDraftRole.canAccessAllTools,
});
}}
/>
</StyledCard>
<AnimatedExpandableContainer
isExpanded={!settingsDraftRole.canAccessAllTools}
dimension="height"
animationDurations={{
opacity: 0.2,
size: 0.4,
}}
mode="scroll-height"
containAnimation={false}
>
<StyledTable>
<SettingsRolePermissionsSettingsTableHeader
roleId={roleId}
settingsPermissionsConfig={toolPermissionsConfig}
isEditable={isEditable}
/>
<StyledTableRows>
{toolPermissionsConfig.map((permission) => (
<SettingsRolePermissionsSettingsTableRow
key={permission.key}
roleId={roleId}
permission={permission}
isEditable={isEditable}
/>
))}
</StyledTableRows>
</StyledTable>
</AnimatedExpandableContainer>
</Section>
);
};

View File

@ -1,9 +1,10 @@
import { IconComponent } from 'twenty-ui/display';
import { SettingPermissionType } from '~/generated-metadata/graphql';
import { PermissionFlagType } from '~/generated-metadata/graphql';
export type SettingsRolePermissionsSettingPermission = {
key: SettingPermissionType;
key: PermissionFlagType;
name: string;
description: string;
Icon: IconComponent;
isToolPermission?: boolean;
};

View File

@ -28,7 +28,7 @@ import {
useCreateOneRoleMutation,
useUpdateOneRoleMutation,
useUpsertObjectPermissionsMutation,
useUpsertSettingPermissionsMutation,
useUpsertPermissionFlagsMutation,
} from '~/generated-metadata/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getDirtyFields } from '~/utils/getDirtyFields';
@ -45,6 +45,7 @@ const ROLE_BASIC_KEYS: Array<keyof Role> = [
'description',
'icon',
'canUpdateAllSettings',
'canAccessAllTools',
'canReadAllObjectRecords',
'canUpdateAllObjectRecords',
'canSoftDeleteAllObjectRecords',
@ -61,7 +62,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
const [createRole] = useCreateOneRoleMutation();
const [updateRole] = useUpdateOneRoleMutation();
const [upsertSettingPermissions] = useUpsertSettingPermissionsMutation();
const [upsertPermissionFlags] = useUpsertPermissionFlagsMutation();
const [upsertObjectPermissions] = useUpsertObjectPermissionsMutation();
const [isSaving, setIsSaving] = useState(false);
@ -144,6 +145,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
description: settingsDraftRole.description,
icon: settingsDraftRole.icon,
canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings,
canAccessAllTools: settingsDraftRole.canAccessAllTools,
canReadAllObjectRecords:
settingsDraftRole.canReadAllObjectRecords,
canUpdateAllObjectRecords:
@ -161,14 +163,14 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
return;
}
if (isDefined(dirtyFields.settingPermissions)) {
await upsertSettingPermissions({
if (isDefined(dirtyFields.permissionFlags)) {
await upsertPermissionFlags({
variables: {
upsertSettingPermissionsInput: {
upsertPermissionFlagsInput: {
roleId: data.createOneRole.id,
settingPermissionKeys:
settingsDraftRole.settingPermissions?.map(
(settingPermission) => settingPermission.setting,
permissionFlagKeys:
settingsDraftRole.permissionFlags?.map(
(permissionFlag) => permissionFlag.flag,
) ?? [],
},
},
@ -214,14 +216,14 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
roleId: data.createOneRole.id,
});
} else {
if (isDefined(dirtyFields.settingPermissions)) {
await upsertSettingPermissions({
if (isDefined(dirtyFields.permissionFlags)) {
await upsertPermissionFlags({
variables: {
upsertSettingPermissionsInput: {
upsertPermissionFlagsInput: {
roleId: roleId,
settingPermissionKeys:
settingsDraftRole.settingPermissions?.map(
(settingPermission) => settingPermission.setting,
permissionFlagKeys:
settingsDraftRole.permissionFlags?.map(
(permissionFlag) => permissionFlag.flag,
) ?? [],
},
},
@ -239,6 +241,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
description: settingsDraftRole.description,
icon: settingsDraftRole.icon,
canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings,
canAccessAllTools: settingsDraftRole.canAccessAllTools,
canReadAllObjectRecords:
settingsDraftRole.canReadAllObjectRecords,
canUpdateAllObjectRecords:

View File

@ -42,6 +42,7 @@ export const SettingsRoleCreateEffect = ({
description: '',
icon: 'IconUser',
canUpdateAllSettings: true,
canAccessAllTools: true,
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true,

View File

@ -13,9 +13,10 @@ export const settingsDraftRoleFamilyState = createFamilyState<Role, string>({
canSoftDeleteAllObjectRecords: false,
canUpdateAllObjectRecords: false,
canUpdateAllSettings: false,
canAccessAllTools: false,
isEditable: false,
workspaceMembers: [],
settingPermissions: [],
permissionFlags: [],
objectPermissions: [],
},
});